太行、王屋二山,方七百里,高萬仞。本在冀州之南,河陽之北.......javascript
嗯,按照慣例,第一句話就是騙大家點進來的。在讀本文以前,但願你對Javascript的原型和原型鏈有必定了解,這有助於你更好的理解本文,以前有寫過一篇相關文章,點此閱讀。但這並非必須的。java
都退後,我要繼續講故事了。數組
北山愚公者,年且九十,面山而居。app
var person = { name : '愚公', age: 90, address: '北山腳下', whereToLive: function () { alert(this.address) } };
......北山愚公曰:「雖我之死,有子存焉;子又生孫,孫又生子;子又有子,子又有孫;子子孫孫無窮匱也」。函數
看到這兒,問題來了,愚公的子子孫孫那麼多,顯然使用對象字面量去建立是不合理的。咱們介紹第一種建立方式。post
function createPerson (name, age, address){ var o = new Object(); o.name = name; o.age = age; o.address = address; o.whereToLive = function () { alert(this.address) }; return o; } var son = createPerson('愚小公', 30, '北山'); var grandSon = createPerson('愚小小公', 5, '北山');
工廠模式比較明顯的一個缺點就是因爲生成並返回了一箇中間對象,因此不能判斷對象的類型。this
function Person(name, age, address) { this.name = name; this.age = age; this.address = address; this.whereToLive = function(){ alert(this.address); }; } var son = new Person('愚小公', 30, '北山'); var grandSon = new Person('愚小小公', 5, '北山');
構造函數與普通函數沒有異處,沒有語法上的任何差異,只是在調用的時候使用了new關鍵字。因此咱們有必要說一下new到底幹了什麼:spa
以這裏的代碼爲例,實際上第二步和第三步的操做能夠總結爲Person.apply(newObject,arguments)
,這裏順便說一句bind與call/apply的一個區別,bind返回的是一個函數,call/apply是順帶把這個函數給執行了,返回的是執行後的結果。prototype
那麼,構造函數模式有什麼問題呢,其實也是顯而易見的,若是愚公有一千個子子孫孫,那麼每一個子孫都會自帶一個whereToLive
的方法,顯然這種作法不文藝範兒code
function Person () { } Person.prototype.name = '愚公'; Person.prototype.age = 90; Person.prototype.address = '北山'; Person.prototype.whereToLive = function () { alert(this.address); }; var son = new Person(); var grandSon = new Person(); son.name = '愚小公'; son.address = '山的那邊'; son.whereToLive(); // '山的那邊' grandSon.whereToLive(); // '北山'
咱們在son對象上試圖修改address屬性,而且彷佛看起來也修改爲功了,可是沒有影響到grandSon的屬性。因此其實這兩個address其實並不同。爲何呢?咱們在作以下操做:
delete son.address; son.whereToLive(); // '北山'
咱們刪掉了son的address屬性,這時候son的address又成了原型中定義的值。因此咱們在修改address屬性的時候並無動到原型中的值,而是在這個對象上新建了一個屬性。而且在試圖獲取這個屬性的時候會優先返回對象上的屬性值。咱們管這個現象叫屬性屏蔽。
另外多提一點,就是在讀取對象屬性的時候,首先會查看該對象自己有沒有,沒有的話會順着原型鏈一直向上查找,若是達到原型鏈頂層都沒有找到,則返回undefined。這裏再穿插一個知識點。不少剛入門的開發者會犯這樣的錯誤:
var a = {}; console.log(a.b.c)
在沒有校驗b屬性是否存在便去試圖獲取c屬性。若是到了原型鏈的頂端都沒有找到b,a.b的值則爲undefined,因此獲取undefined的c屬性必定會報錯。正確的作法是在不肯定是否存在對應屬性的時候,應當先作判斷。
可是在寫入基本類型屬性的時候有所不一樣,在當前對象沒有找到要寫入的屬性時,不會向上查找,而是在當前對象裏新建一個屬性,這麼作的緣由是防止污染其餘對象的屬性值。細心的你可能發現了我在開頭的時候強調了基本類型屬性。若是是引用類型會怎麼樣呢?
function Person () { } Person.prototype.name = '愚公'; Person.prototype.age = 90; Person.prototype.address = ['北山']; Person.prototype.whereToLive = function () { alert(this.address); }; var son = new Person(); var grandSon = new Person(); son.address.push('山的那邊'); grandSon.whereToLive(); // '北山','山的那邊'
這裏又有一個小知識點,引用類型是存在堆內存中的,不一樣地方的應用其實指向的是同一塊堆內存。因此若是試圖修改原型對象中的應用類型,會形成全局污染,這也就是原型模式的一個致命缺點。
坐穩,我又要穿插新的知識點了。咱們能夠採用簡寫的方式避免原型模式賦予原型對象方法時囉嗦的問題。
function Person(name, age, address) { this.name = name; this.age = age; this.address = address; } Person.prototype = { constructor : Person, // 手動修改構造函數指向 whereToLive : function () { alert(this.address); }, howOld : function () { alert(this.age); } }
組合使用構造函數模式和原型模式的寫法是否是同時規避掉了構造函數模式和原型模式的問題呢?既能夠共享公用的函數,又可讓每一個對象獨享本身的屬性。
須要注意的是,咱們在重寫Person.prototype
的時候,實際上使得constructor指向了Object,因此我這裏進行了手動修正。
function PersonList (name, age, address){ var o = new Array(); o.push.apply(o, arguments); o.consoleString = function () { return this.join(","); }; return o; } var list = new PersonList('愚小公', '愚小小公'); alert(list.consoleString());
是否是很眼熟,跟工廠模式如出一轍,只不過是在調用的時候使用了new關鍵字。利用這種模式,咱們能夠爲對象添加額外的能力。本例中,就是給數組添加一個自定義的方法,使其能夠擁有咱們賦予的新能力。
實際開發中仍是得根據實際場景靈活運用,總有適合你的那一款。今天就聊到這,歡迎你們補充和指正。