JS創(chuàng)建對(duì)象的幾種模式及分析對(duì)比
工廠模式
function createPerson(name, age, job){
//創(chuàng)建對(duì)象
var o = new Object();
//添加屬性
o.name = name;
o.age = age;
o.job = job;
//添加方法
o.sayName = function(){
console.log(this.name);
};
//返回對(duì)象
return o;
}
實(shí)例化:
var person1 = createPerson('smith', 25, 'coder');
缺點(diǎn):沒有解決對(duì)象的識(shí)別問題.
構(gòu)造函數(shù)模式
function Person(name, age, job){ //Person也是普通的函數(shù), 但便于與普通函數(shù)區(qū)分, 通常將構(gòu)造函數(shù)首字母大寫
//添加屬性
this.name = name;
this.age = age;
this.job = job;
//添加方法
this.sayName = function(){
console.log(this.name);
};
}
通過對(duì)比工廠模式我們發(fā)現(xiàn):
- 沒有顯示創(chuàng)建對(duì)象;
- 直接將屬性和方法賦給了this對(duì)象,this就是new出來的對(duì)象;
- 沒有return語句.
實(shí)例化:
var person1 = new Person('smith', 25, 'coder');
判斷實(shí)例類型:
console.log(person1.constructor === Person); //true
console.log(person1 instanceof Person); //true
console.log(person1 instanceof Object); //true
優(yōu)點(diǎn): 創(chuàng)建自定義的構(gòu)造函數(shù)可以將實(shí)例標(biāo)識(shí)為一種特定的類型
缺點(diǎn): 每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍, 無法代碼共用.
原型模式
function Person(){}
//Person.prototype為原型對(duì)象,每個(gè)構(gòu)造函數(shù)的protot屬性指向其對(duì)應(yīng)的原型對(duì)象,以下為原型對(duì)象添加屬性和方法
Person.prototype.name = 'smith';
Person.prototype.age = 25;
Person.prototype.job = 'coder';
Person.prototype.sayName = function(){
console.log(this.name);
}
實(shí)例化:
var person1 = new Person();
獲取原型對(duì)象:
console.log(Object.getPrototypeOf(person1) === Person.prototype); //true
判斷原型對(duì)象:
console.log(Person.prototype isPrototypeOf(person1)) //true
若為實(shí)例添加的屬性或方法時(shí)與原型對(duì)象里的屬性或方法相同, 添加的屬性或方法會(huì)屏蔽原型對(duì)象中的那個(gè)屬性或方法
function Person(){}
Person.prototype.name = 'smith';
Person.prototype.age = 25;
Person.prototype.job = 'coder';
Person.prototype.sayName = function(){
console.log(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = 'Lihua';
console.log(person1.name) //'Lihua'
console.log(person2.name) //'smith'
通過以上結(jié)果發(fā)現(xiàn): person1.name
屬性屏蔽了原型對(duì)象里的name
屬性, 阻止了訪問原型中的那個(gè)屬性, 使用delete
操作符刪除實(shí)例屬性后, 能夠重新訪問原型中的屬性
function Person(){}
Person.prototype.name= 'smith';
Person.prototype.age = '25';
Person.prototype.job = 'coder';
Person.prototype.sayName = function(){
console.log(this.name);
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'Lihua';
console.log(person1.sayName()); //Lihua
console.log(person2.sayName()); //smith
delete person1.name;
console.log(person1.sayName()); //smith
delete Object.getPrototypeOf(person1).name; //刪除原型對(duì)象的name屬性
console.log(person1.sayName()); //undefined
console.log(person2.sayName()); //undefined
判斷屬性是否存在于實(shí)例中:
person1.name = 'Lihua';
console.log(person1.hasOwnProperty('name')) //ture
原型的動(dòng)態(tài)性
對(duì)原型對(duì)象所做的任何修改能夠立即從實(shí)例上反映出來, 即使是先實(shí)例化后修改原型對(duì)象也是如此
var friend = new Person();
Person.prototype.sayHi = function(){
console.log('hi');
}
friend.sayHi() //依然能夠執(zhí)行
JS高程寫到:
盡管對(duì)原型對(duì)象所做的任何修改能夠立即在所有實(shí)例中反映出來, 但重寫整個(gè)原型對(duì)象情況還是不一樣的, 調(diào)用構(gòu)造函數(shù)時(shí)會(huì)為實(shí)例添加一個(gè)指向最初原型的
[[prototype]]
指針, 把原型修改為另外一個(gè)對(duì)象就切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系
function Person(){}
var friend = new Person();
Person.prototype = {
constructor: Person,
name: 'Lihua',
job: 'student',
sayName: function(){
console.log(this.name);
}
};
friend.sayName(); //error
但覺得JS高程這種說法有點(diǎn)欠妥, 比如:
function Person(){}
Person.prototype.job = 'coder';
Person.prototype.sayJob = function(){
console.log(this.job);
}
var friend = new Person();
Person.prototype = {
constructor: Person,
name: 'Lihua',
job: 'student',
sayName: function(){
console.log(this.name);
}
};
var person = new Person();
person.sayName(); //Lihua
friend.sayJob(); //coder
// person.sayJob(); error
我的理解是:
- 原型是一種動(dòng)態(tài)動(dòng)態(tài)的關(guān)系泼各,添加一個(gè)新的屬性或方法到原型中時(shí)旱幼,該屬性或方法會(huì)立即對(duì)所有基于該原型創(chuàng)建的對(duì)象可見,哪怕是修改原型之前創(chuàng)建的對(duì)象;
- 將原型重寫為另外一個(gè)對(duì)象后, 會(huì)影響重寫后實(shí)例得到的對(duì)象, 切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系; 對(duì)于重寫之前已經(jīng)實(shí)例化的對(duì)象沒有影響, 因?yàn)檫@些對(duì)象里已經(jīng)保存了一個(gè)
[[prototype]]
指針, 該指針指向重寫之前的原型對(duì)象;
原型對(duì)象的問題
盡管可以在實(shí)例上添加一個(gè)同名屬性從而隱藏那些不需要共享的屬性, 然而對(duì)于包含引用類型的值的屬性來說, 問題就比較突出.
function Person(){}
Person.prototype = {
constructor: Person,
name: 'smith',
job: 'coder',
friends: ['shelby', 'court'],
sayName: function(){
console.log(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push('van');
console.log(person1.friends); //'shelby,court,van'
console.log(person2.friends); //'shelby,court,van'
console.log(person1.friends === person2.friends); //true
在這種情況下,person1
與person2
的friends
屬性完全相同, 這不符合常識(shí)(一個(gè)人的朋友怎么可能和另外一個(gè)人的朋友完全相同呢?).
優(yōu)點(diǎn): 所有實(shí)例共享原型對(duì)象的屬性和方法
缺點(diǎn): 原型模式的最大問題正如以上所述, 無法屏蔽原型中數(shù)據(jù)類型為引用類型的屬性
構(gòu)造函數(shù)模式+原型模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['shelby', 'court'];
}
Person.prototype = {
constructor: Person,
sayName: function(){
console.log(this.name);
}
}
var person1 = new Person('smith', 25, 'coder');
var person2 = new Person('Lihua', 28, 'worker');
person1.friends.push('van');
console.log(person1.friends); //"shelby, court, van"
console.log(person2.friends); //"shelby, court"
console.log(person1.friends === person2.friends); //false
console.log(person1.sayName === person2.sayName); //true
構(gòu)造函數(shù)模式+原型模式結(jié)合了構(gòu)造函數(shù)模式和原型模式的優(yōu)點(diǎn), 將需要實(shí)例單獨(dú)擁有的屬性放到構(gòu)造函數(shù)里, 將需要實(shí)例共享的方法放到原型里, 這種模式是目前使用最廣泛和認(rèn)同度最高的一種創(chuàng)建自定義類型的方法.
動(dòng)態(tài)原型模式
function Person(name, age, job){
//屬性
this.name = name;
this.age = age;
this.job = job;
//方法
if(typeof this.sayName != 'function'){ //在sayName方法不存在的情況下,將其添加到原型中
Person.prototype.sayName = function(){
console.log(this.name);
};
}
}
var friend = new Person('smith', 25, 'coder');
friend.sayName(); //smith
動(dòng)態(tài)原型模式把所有信息都封裝到了構(gòu)造函數(shù)中, 通過在構(gòu)造函數(shù)中初始化原型(可選), 保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn).
寄生構(gòu)造函數(shù)模式
function createPerson(name, age, job){
//創(chuàng)建對(duì)象
var o = new Object();
//添加屬性
o.name = name;
o.age = age;
o.job = job;
//添加方法
o.sayName = function(){
console.log(this.name);
};
//返回對(duì)象
return o;
}
var person1 = new Person('smith', 25, 'coder');
friend.sayName(); //'smith
寄生構(gòu)造函數(shù)模式除了調(diào)用方式與工廠模式不同外new Person('smith', 25, 'coder'),其他完全一樣, 通過在構(gòu)造函數(shù)末尾添加return語句, 重寫了調(diào)用構(gòu)造函數(shù)時(shí)返回的對(duì)象
寄生構(gòu)造函數(shù)模式適合在特殊情況下為對(duì)象創(chuàng)建構(gòu)造函數(shù). 因?yàn)椴煌扑]在產(chǎn)品化的程序中修改原生對(duì)象的原型, 但在某些場(chǎng)合下又的確需要?jiǎng)?chuàng)建一個(gè)具有額外方法的原生對(duì)象:
function SpecialArray(){
//創(chuàng)建數(shù)組
var values = new Array();
//添加值
values.push.apply(values, arguments);
//添加方法
values.toPipedString = function(){
return this.join('|');
}
//返回具有額外方法的數(shù)組
return values;
}
var colors = new SpecialArray('red', 'blue', 'green');
console.log(colors.toPipedString()) //'red|blue|green'
缺點(diǎn): 返回的對(duì)象與構(gòu)造函數(shù)或與構(gòu)造函數(shù)的原型屬性之間沒有關(guān)系, 因?yàn)?code>return語句重寫了調(diào)用構(gòu)造函數(shù)時(shí)返回的對(duì)象, 也即是構(gòu)造函數(shù)返回的對(duì)象與在構(gòu)造函數(shù)外部創(chuàng)建的對(duì)象沒有什么不同, 不能依賴instanceof
操作符確定對(duì)象類型
穩(wěn)妥構(gòu)造函數(shù)模式
function Person(name, age, job){
//創(chuàng)建要返回的對(duì)象
var o = new Object();
//這里定義私有變量和函數(shù)
//添加方法
o.sayName = function(){
console.log(name);
}
//返回對(duì)象
return o;
}