寫在前面:這次來說下 JavaScript 中對象的創(chuàng)建,文中提到的方法均較為常用,其他方法請自行百度。
2.1 創(chuàng)建對象
2.1.1 工廠模式
工廠模式是一種較為簡單的設(shè)計(jì)模式,開發(fā)人員將創(chuàng)建對象的具體細(xì)節(jié)封裝在一個(gè)函數(shù)中。
// 工廠模式
function F(name, age) {
let o = {};
o.name = name;
o.age = age;
o.arr = ['a', 1, 2];
return o
}
let p1 = F('Devin', 34)
p1.arr.push(5)
let p2 = F('Joker', 22)
p2.arr.push(10)
console.log(p1.name, p2.name) //Devin Joker
console.log(p1.age, p2.age) //34 22
console.log(p1.arr, p2.arr) //[ 'a', 1, 2, 5 ] [ 'a', 1, 2, 10 ]
工廠模式解決了創(chuàng)建多個(gè)類似對象的問題,但卻沒有解決對象識(shí)別的問題。
2.1.2 構(gòu)造器模式
諸如Object和Array等原生構(gòu)造器一樣,我們可以創(chuàng)建自定義構(gòu)造器去生成自定義對象。
// 構(gòu)造器模式
function F(name, age) {
this.name = name;
this.age = age;
this.arr = ['a', 1, 2];
}
let p1 = new F('Devin', 34)
p1.arr.push(5)
let p2 = new F('Joker', 22)
p2.arr.push(10)
console.log(p1.name, p2.name) //Devin Joker
console.log(p1.age, p2.age) //22 22
console.log(p1.arr, p2.arr) //[ 'a', 1, 2, 5 ] [ 'a', 1, 2, 10 ]
與工廠模式相比,我們可以發(fā)現(xiàn)構(gòu)造器函數(shù)存在以下幾點(diǎn)不同:
- 沒有顯式地創(chuàng)建對象
- 直接將屬性和方法賦給了this對象
- 沒有return語句
使用構(gòu)造器模式時(shí),需要注意必須使用new操作符。
構(gòu)造器模式雖然好用,但也存在問題,即它沒創(chuàng)建一個(gè)對象,所有的屬性都要重新創(chuàng)建一邊,某些情況下這種做法是并不可取的。
// 構(gòu)造器模式
function F(name) {
this.name = name;
this.sayName = function () {
return this.name
};
}
let p1 = new F('Devin')
let p2 = new F('Joker')
console.log(p1.sayName()) //Devin
console.log(p2.sayName()) //Joker
console.log(p1.sayName === p2.sayName) //false
上面的代碼中sayName函數(shù)只是單純的返回當(dāng)前對象中的名字,因此重復(fù)創(chuàng)建這個(gè)函數(shù)本身并沒有太大的意義,如果可以將sayName函數(shù)放入到一個(gè)共享的環(huán)境中就好了。我們一般會(huì)想到將sayName函數(shù)放入全局環(huán)境中,這種做法可行。
function sayName() {
return this.name
}
function F(name) {
this.name = name;
this.sayName = sayName;
}
console.log(p1.sayName === p2.sayName) //false
這樣就很好的解決了浪費(fèi)內(nèi)存的問題,但是新的問題又來了,如果有很多個(gè)這樣的函數(shù),那就不得不在全局作用域中創(chuàng)建很多共享函數(shù),這么做會(huì)增加風(fēng)險(xiǎn),且構(gòu)造器本身也就沒有封裝性可言了。好在,這些問題我們可以用過原型模式去解決。
2.1.3 原型模式
我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性指向一個(gè)對象,這個(gè)對象包含所有實(shí)例共享的屬性和方法。
// 原型模式
function F() {}
F.prototype.name = 'Joker';
F.prototype.age = 22;
let p1 = new F()
let p2 = new F()
console.log(p1,p2) //{ } { }
console.log(p1.name, p2.name) //Joker Joker
p1.name = 'Devin';
p1.age = 34;
console.log(p1.name, p2.name) //Devin Joker
console.log(p1.age, p2.age) //34 22
我們創(chuàng)建了兩個(gè)不同的實(shí)例,從打印結(jié)果中可以看出,他們本身是兩個(gè)空對象,但是訪問各自的name屬性,均可以打印出Joker,可見,在它們本身不具備該屬性時(shí),他們會(huì)到constructor的prototype中去找,當(dāng)為他們定義相同的屬性時(shí),實(shí)例本身的屬性會(huì)屏蔽原型上面的屬性。
原型模式也有不足之處,其根本來源是由于原型的共享性所致。
// 原型模式
function F() {}
F.prototype.arr = ['a', 1, 2];
let p1 = new F()
let p2 = new F()
p1.arr.push(5)
p2.arr.push(10)
console.log(p1.arr, p2.arr) //[ 'a', 1, 2, 5, 10 ] [ 'a', 1, 2, 5, 10 ]
console.log(F.prototype.arr) //[ 'a', 1, 2, 5, 10 ]
對于prototype中的引用類型,實(shí)例中均采用指針的方式對其進(jìn)行訪問。任何實(shí)例對其修改都會(huì)影響到所有其他的實(shí)例對象。因此,在實(shí)際生產(chǎn)中,也很少單獨(dú)使用原型模式。
2.1.4 組合使用原型模式和構(gòu)造器模式
見題知意,就是將原型模式和構(gòu)造器模式組合在一起使用。
// 構(gòu)造器與原型組合模式
function F(name, age) {
this.name = name;
this.age = age;
this.arr = [1, 2, 4]
}
F.prototype.sayName = function () {
return this.name
}
let p1 = new F('Devin', 34)
p1.arr.push(5)
let p2 = new F('Joker', 22)
p2.arr.push(10)
console.log(p1) //{ name: 'Devin', age: 34, arr: [ 1, 2, 4, 5 ] }
console.log(p2) //{ name: 'Joker', age: 22, arr: [ 1, 2, 4, 10 ] }
console.log(p1.sayName()) //Devin
console.log(p2.sayName()) //Joker
在這個(gè)例子中,實(shí)例屬性都是在構(gòu)造函數(shù)中定義的,而共享屬性均在prototype屬性中??梢钥吹剑薷母髯缘腶rr并不會(huì)影響到其他實(shí)例對象。
2.1.5 其他方法
出上述幾種方法以外,還有動(dòng)態(tài)原型模式、寄生構(gòu)造函數(shù)模式、穩(wěn)妥構(gòu)造函數(shù)模式,這里不做介紹。