引言
建立自定義物件最簡單的方式就是建立一個Object
的例項,然後再為它新增屬性和方法,早期的開發人員經常使用這種模式來建立物件,後來物件字面量的方法成了建立物件的首選模式。雖然object建構函式
或者物件字面量
的方法都可以用來建立物件,但是這些方法使用同一個介面建立很多物件,會產生大量的重複程式碼。為了解決這個問題,人們開始使用各種模式來建立物件,在這些模式中,一般推薦使用四種方式,包括建構函式模式
、原型模式
、建構函式和原型組合模式
,動態原型模式
,其他的方式,包括工廠模
、寄生建構函式模
、穩妥建構函式模式
平時使用的較少。而這些方式中,用的最多最推薦的應是組合模式和動態原型模式。下面先介紹下各個模式的優缺點:
-
工廠模式的優點就是確實是解決了建立多個相似物件的問題,但是卻沒有解決物件識別的問題,即不知道建立例項的是哪一個物件(建構函式),因為所有的例項都是object創造的;
-
建構函式模式最大的優點就是可以通過contructor或者instanceof可以識別物件例項的類別(即它的建構函式),但是每次建立例項時,每個方法都要被建立一次;
-
原型模式的優點是例項的方法是共享的,所有的例項的同一個方法都指向同一個,這樣使得每次建立例項時方法不會重新建立,但是這也是缺點,原型模式中所有的屬性和方法都共享,這樣造成兩個問題一是沒有辦法建立例項自己的屬性和方法,二是例項改變屬性裡的資料時其他例項也會跟著改變,另外它也不能像建立建構函式那樣可以傳遞引數;
-
建構函式和原型組合模式優點就是該共享的共享,該私有的私有,即用建構函式定義物件的所有非函式屬性,用原型方式定義方法和共享的屬性,結果,每個例項都會有自己的一份例項屬性的副本,但同時又共享著對方法的引用,最大限度地節省了記憶體。另外,這種混成模式還支援向建構函式傳遞引數;這種建構函式和原型混成的模式,是目前在ECMAScript中使用最廣泛、認同度最高的一種建立定義型別的方法。
注意組合模式解決了原型模式沒有辦法傳遞引數的缺點,也解決了建構函式模式不能共享方法的缺點。
-
動態原型模式是目前極為推薦的一種形式,但是在使用時要注意不能用物件字面量重寫原型
-
寄生建構函式模式和工廠模式基本一樣,除了多了個new操作符,因此它也不能識別物件,或者說不能識別例項的類別,該方法不推薦使用
-
穩妥建構函式模式適合在一些安全的環境中,穩妥物件是指沒有公共屬性,而且其方法也不引用this的物件,它也無法識別物件的所屬型別,該方法不推薦使用
工廠模式
function Person(name) {
var o = new Object();
o.name = name;
o.say = function() {
alert(this.name);
}
return o;
}
var person1 = Person("yawei");
複製程式碼
缺點:
- 物件無法識別,所有例項都指向一個原型;無法通過constructor識別物件,因為都是來自Object。
- 每次通過Person建立物件的時候,所有的say方法都是一樣的,但是卻儲存了多次,浪費資源。
建構函式模式
function Person() {
this.name = 'hanmeimei';
this.say = function() {
alert(this.name)
}
}
var person1 = new Person();
複製程式碼
優點:
- 通過constructor或者instanceof可以識別物件例項的類別
- 可以通過new 關鍵字來建立物件例項,更像OO語言中建立物件例項
缺點:
- 多個例項的say方法都是實現一樣的效果,但是卻儲存了很多次(兩個物件例項的say方法是不同的,因為存放的地址不同)
注意:
- 建構函式模式隱試的在最後返回
return this
所以在缺少new的情況下,會將屬性和方法新增給全域性物件,瀏覽器端就會新增給window物件。 - 也可以根據
return this
的特性呼叫call或者apply指定this。這一點在後面的繼承有很大幫助。
原型模式
function Person() {}
Person.prototype.name = 'hanmeimei';
Person.prototype.say = function() {
alert(this.name);
}
Person.prototype.friends = ['lilei'];
var person1 = new Person();
複製程式碼
優點:
-
say方法是共享的了,所有的例項的say方法都指向同一個。
-
可以動態的新增原型物件的方法和屬性,並直接反映在物件例項上。
var person1 = new Person() Person.prototype.showFriends = function() { console.log(this.friends) } person1.showFriends() //['lilei'] 複製程式碼
缺點:
-
出現引用的情況下會出現問題具體見下面程式碼:
var person1 = new Person(); var person2 = new Person(); person1.friends.push('xiaoming'); console.log(person2.friends) //['lilei', 'xiaoming'] 複製程式碼
因為js對引用型別的賦值都是將地址儲存在變數中,所以person1和person2的friends屬性指向的是同一塊儲存區域。
-
第一次呼叫say方法或者name屬性的時候會搜尋兩次,第一次是在例項上尋找say方法,沒有找到就去原型物件(Person.prototype)上找say方法,找到後就會在實力上新增這些方法or屬性。
-
所有的方法都是共享的,沒有辦法建立例項自己的屬性和方法,也沒有辦法像建構函式那樣傳遞引數。
注意:
-
優點②中存在一個問題就是直接通過物件字面量給
Person.prototype
進行賦值的時候會導致constructor
改變,所以需要手動設定,其次就是通過物件字面量給Person.prototype
進行賦值,會無法作用在之前建立的物件例項上var person1 = new Person() Person.prototype = { name: 'hanmeimei2', setName: function(name){ this.name = name } } person1.setName() //Uncaught TypeError: person1.set is not a function(…) 複製程式碼
這是因為物件例項和物件原型直接是通過一個指標連結的,這個指標是一個內部屬性[[Prototype]],可以通過
__proto__
訪問。我們通過物件字面量修改了Person.prototype指向的地址,然而物件例項的__proto__
,並沒有跟著一起更新,所以這就導致,例項還訪問著原來的Person.prototype
,所以建議不要通過這種方式去改變Person.prototype
屬性
建構函式和原型組合模式
function Person(name) {
this.name = name
this.friends = ['lilei']
}
Person.prototype.say = function() {
console.log(this.name)
}
var person1 = new Person('hanmeimei')
person1.say() //hanmeimei
複製程式碼
優點:
- 解決了原型模式對於引用物件的缺點
- 解決了原型模式沒有辦法傳遞引數的缺點
- 解決了建構函式模式不能共享方法的缺點
缺點:
- 和原型模式中注意①一樣
動態原型模式
function Person(name) {
this.name = name
// 檢測say 是不是一個函式
// 實際上只在當前第一次時候沒有建立的時候在原型上新增sayName方法
//因為建構函式執行時,裡面的程式碼都會執行一遍,而原型有一個就行,不用每次都重複,所以僅在第一執行時生成一個原型,後面執行就不必在生成,所以就不會執行if包裹的函式,
//其次為什麼不能再使用字面量的寫法,我們都知道,使用建構函式其實是把new出來的物件作用域繫結在建構函式上,而字面量的寫法,會重新生成一個新物件,就切斷了兩者的聯絡!
if(typeof this.say != 'function') {
Person.prototype.say = function(
alert(this.name)
}
}
複製程式碼
優點:
- 可以在初次呼叫建構函式的時候就完成原型物件的修改
- 修改能體現在所有的例項中
**缺點:**紅寶書都說這個方案完美了。。。。
注意:
-
只用檢查一個在執行後應該存在的方法或者屬性就行了 假設除了:say方法外,你還定義了很多其他方法,比如:sayBye、cry、smile等等。此時你只需要把它們都放到對sayName判斷的if塊裡面就可以了。
if (typeof this.sayName != "function") { Person.prototype.sayName = function() {...}; Person.prototype.sayBye = function() {...}; Person.prototype.cry = function() {...}; ... } 複製程式碼
-
不能用物件字面量修改原型物件
寄生建構函式模式
function Person(name) {
var o = new Object()
o.name = name
o.say = function() {
alert(this.name)
}
return o
}
var peron1 = new Person('hanmeimei')
複製程式碼
優點:
- 和工廠模式基本一樣,除了多了個new操作符
缺點:
- 和工廠模式一樣,不能區分例項的類別
穩妥構造模式
function Person(name) {
var o = new Object()
o.say = function() {
alert(name)
}
}
var person1 = new Person('hanmeimei');
person1.name // undefined
person1.say() //hanmeimei
複製程式碼
優點:
- 安全,那麼好像成為了私有變數,只能通過say方法去訪問。所謂穩妥物件,指的是沒有公共睡醒,而且其方法也不引用this的物件。
缺點:
- 跟工廠模式一樣,不能區分例項的類別
參考文章: