通常狀況下,JS中建立對象的方式能夠用構造函數Object或者對象字面量的方式,但須要建立幾個具備相同屬性或方法的對象時,就得寫大量的冗餘代碼。故而出現了下述幾種建立對象的方法。設計模式
工廠模式是一種常見的設計模式。這種模式把對象的建立過程抽象出來並封裝成一個函數。須要使用同類型的對象時,能夠不斷地調用此函數。例子以下:瀏覽器
function createPerson (name, age) {
var o = new Object();
o.name = name;
o.sayName = function(){
console.log(this.name)
}
return o;
}
複製代碼
工廠模式解決了建立多個相同類型對象的問題,即減小了代碼的冗餘,但沒有解決對象識別的問題。bash
JavaScript中的構造函數其實就是普通的函數,但構造函數的目的通常用來建立對象。按照約定俗成的慣例,構造函數應該以一個大寫字母開頭。例子以下:函數
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name);
}
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼
此例子與工廠模式的區別有:學習
此外,構造函數模式還解決了對象識別的問題:ui
xiaoming instanceof Person // true
xiaohong instanceof Person // true
複製代碼
特別地,由於構造函數也是函數,在不用new操做符調用時,函數內部的this對象實際上是指向了全局對象(瀏覽器環境是window對象),具體緣由請學習this的原理。this
構造函數並非沒有缺點,如上面例子的sayName方法,在每一個實例上都從新建立了一遍:spa
xiaoming.sayName === xiaohong.sayBane // false
複製代碼
像sayName這種函數都是爲了完成相同的任務(打印實例的名字),大可抽象出來做爲一個共享的函數prototype
function Person (name, age) {
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼
但這種方式使得sayName函數變爲了全局函數,而且只合適被Person new出來的實例調用。故而帶來下面幾個問題:設計
利用JS的原型對象和原型鏈,能夠解決構造函數模式中帶來的那幾個問題:
function Person(){};
Person.prototype = {
constructor: Person,
name: '小明',
age: 18,
sayName: function () {
console.log(this.name)
}
}
var xiaoming = new Person();
var xiaohong = new Person();
xiaoming.sayName(); //小明
xiaohong.sayName(); //小明
複製代碼
上面的例子能夠看到,雖然解決了構造函數模式存在的問題,但缺點也是大大的:
Person.prototype.friends = ['張三', '李四'];
xiaoming.friends === xiaohong.friends; //true
xiaoming.frinds.push('王五');
xiaohong.friends; // ['張三', '李四', '王五'];
複製代碼
組合使用構造函數模式和原型模式是ES5中建立對象最經常使用的模式,此方式用構造函數定義實例的公共屬性,使用原型模式定義實例的公共方法,最大限度下降了內存:
function Person(name, age){
this.name = name;
this.age = age;
this.friends = ['張三', '李四'];
};
Person.prototype.sayName = function () {
console.log(this.name);
}
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼
另外,對於引用類型的共享的屬性值,實例之間不會互相影響:
xiaoming.friends ==== xiaohong.friends; //false
xiaoming.frinds.push('王五');
xiaohong.friends; // ['張三', '李四'];
複製代碼
動態原型模式就是把全部信息封裝在構造函數裏:
function Person(name, age){
this.name = name;
this.age = age;
if(typeof this.sayName !== 'function'){
Person.prototype.sayName = function () {
console.log(this.name);
}
}
};
var xiaoming = new Person('小明', 18);
var xiaohong = new Person('小紅', 16);
xiaoming.sayName(); //小明
xiaohong.sayName(); //小紅
複製代碼
動態原型模式同事保持了構造函數模式和原型模式的優勢,且封裝性比構造函數和原型組合使用的方式更好。但要注意在構造函數執行後不能重寫原型對象,不然會切斷現有實例和新原型的關係。由於該模式中原型對象上的自定義方法是在構造函數中掛載的。
紅皮書還介紹了寄生構造函數模式和穩妥構造函數模式,但實踐中很是少用。有興趣的同窗能夠查閱。