特性:javascript
特性:java
for in
循環返回undefined
undefined
定義一個方法接受參數,用於建立對象,並將其返回es6
function createPerson(name, age) { var o = new Object(); o.name = name; o.age = age; return o; } var person1 = createPerson('andy_chen', 18); var person2 = createPerson('andy_chen', 18);
工廠模式能夠建立多個類似對象的問題,卻沒解決對象識別的問題。例如person1的類型是什麼
function Person(name, age) { this.name = name; this.age = age; this.sayName = function() { alert(this.name); }; } var person1 = new Person('andy_chen', 18); var person2 = new Person('andy_chen', 18); person1.sayName(); person2.sayName();
構造函數的問題在於,每一個方法都要在每一個實例中從新建立一遍。即例子中,person1和person2的sayName的不相等的。
可是,完成一樣的功能的方法,卻每一個實例都要建立一遍,這顯然不合理,因此,又出現了下面的原型模式
一圖勝千言:chrome
經過新函數建立的實例,有一個[[prototype]]屬性(在 chrome,firefox,safari 中該屬性即爲proto),指向了新函數的 prototype。編程
注意:該屬性僅僅是執行構造函數的 prototype,也便是說,他們與構造函數沒有直接聯繫了
ES5:Object.keys() 能夠返回一個包含全部可枚舉屬性的字符串數組
Object.getOwnPropertyNames() 能夠返回全部實例屬性,不管是否可枚舉
//原型模式的實現: function Person() {} Person.prototype.name = 'andy chen'; Person.prototype.sayName = function() { alert(this.name); };
重寫整個 prototype,不過會致使 constructor 改變。因此須要從新指定 constructor.數組
//更簡單的原型語法 function Person() {} Person.prototype = { constructor: Person, //由於這種寫法會覆蓋掉原來的Person.prototype,須要從新爲constructor賦值 name: 'andy chen', sayName: function() { alert(this.name); } }; var person1 = new Person(); var person2 = new Person();
原型模式的問題:全部實例都共享一個prototype,相似上面的例子,person1,person2的name屬性是共享的。若是修改其中一個,會致使另外一個也受影響。因此,纔會出現下面構造函數與原型模式組合使用
建立自定義類型最多見的方式就是組合使用構造函數和原型模式瀏覽器
構造函數定義實例屬性,而原型模式用於定義方法和共享的屬性. 因此,上面的例子能夠改寫成這樣:安全
function Person(name) { this.name = name; } Person.prototype = { constructor: Person, sayName: function() { alert(this.name); } }; var person1 = new Person('andy chen'); var person2 = new Person('andy chen');
除了使用組合模式建立對象,還有如下幾種方式,能夠針對不一樣的狀況選擇。函數
在構造方法中,判斷是不是第一次進入使用構造方法,若是是,則添加一系列的方法到原型上優化
類基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼而後再返回新建立的對象。
指的是沒有公共屬性,並且其方法也不引用 this 對象。最適合用於一些安全的環境或者在防止數據被其餘程序改動時使用
遵循與寄生構造函數相似的模式,但有兩點不一樣:
OO 語言通常擁有兩種繼承方式:接口繼承(只繼承方法簽名)以及實現繼承(繼承實際方法)
ES 沒法像其餘 OO 語言同樣支持接口繼承,只能依靠原型鏈實現 實現繼承
那麼,若是咱們有個新的構造函數,並讓它的原型對象等於另外一個類型的實例,結果會怎樣.
對於這個新的構造函數,它的原型對象就變成了另外一個類型的實例,而這個實例中,又包含一個內部屬性,指向了另外一個原型對象(該原型對象內部 constructor 指向另外一個構造函數),若是這個原型對象又是另外一個類型的實例,則它又包含了一個內部屬性,繼續指向上層的原型對象。這樣層層遞進,就造成了原型鏈。
以下圖:
function Animal() { this.name = 'animal'; } Animal.prototype.getName = function() { return this.name; }; function Cat() { this.catName = 'cat'; } Cat.prototype = new Animal(); var cat1 = new Cat(); var cat2 = new Cat(); alert(cat1.getName()); //因爲第10行,將Cat的原型指向Animal的實例,由於實例中有指向Animal.prototype的指針。因此,這裏能夠訪問到getName() cat1.name = 'changed name'; alert(cat2.getName());
因爲原型鏈存在問題,因此便出現了借用構造函數的方法
在子類型的構造方法中,調用父類型的構造方法:SuperType.call(this); 將父類型的屬性添加到子類型上,而且能夠傳遞參數給父類型
function Animal() { this.name = 'animal'; } function Cat() { Animal.call(this); } var cat1 = new Cat(); var cat2 = new Cat(); cat1.name = 'changed name'; alert(cat1.name); //changed name alert(cat2.name); //animal //借用構造函數的方式,各實例之間的屬性便不會互相影響
相似建立對象單純使用構造方法同樣,也會形成公有的方法沒法公用。因此通常也不多單獨使用此方式
組合原型鏈以及借用構造函數
function Animal() { this.name = 'animal'; } Animal.prototype.getName = function() { return this.name; }; function Cat() { Animal.call(this); //借用構造函數 } Cat.prototype = new Animal(); //原型鏈方式 Cat.prototype.constructor = Cat; //這裏能夠 var cat1 = new Cat(); var cat2 = new Cat(); cat1.name = 'changed name'; alert(cat1.getName()); //changed name alert(cat2.getName()); //animal
父類的屬性會存在於子類型的原型上,致使被不一樣實例共享。雖然因爲借用構造函數以後,致使實例上又重寫了這些屬性,因此每一個實例有各自的屬性。
另外,instanceof 和 isPrototypeOf 可以識別基於組合繼承建立的對象
**組合繼承,並不完美
由於咱們只須要繼承父類型原型上的屬性而已,不須要父類型實例的屬性。
還有更好的方法,但咱們首先要先了解一下其餘繼承方式**
//若是o爲某個對象的prototype,則object返回的 對象,包含了該對象原型上的全部方法 function object(o) { function F() {} F.prototype = o; return new F(); }
Es5 新增的 Object.create() ,相似這樣。 在沒有必要建立構造函數,只想讓一個對象與另外一個對象保持相似的狀況下,原型式繼承是徹底能夠勝任的。不過,包含引用類型值的屬性始終會共享
在複製新對象後,繼續以某種方式加強對象,即爲寄生式繼承。
function createAnother(original) { var clone = object(original); clone.sayHi = function() { doSomeThing(); }; return clone; }
在主要考慮對象而不是自定義類型和構造函數的時候,適合使用寄生式繼承
缺點: 相似單純的構造函數模式使用,函數不能複用
經過原型式繼承,繼承父類的原型方法。再經過構造函數方法,繼承父類的屬性。
function Animal() { this.name = 'animal'; } Animal.prototype.getName = function() { return this.name; }; function Cat() { Animal.call(this); //借用構造函數 } //原型繼承方式 function object(superProto) { function F() {} F.prototype = superProto; return new F(); } Cat.prototype = object(Animal.prototype); //經過一個空的函數做爲媒介,將空函數的原型指向父類型原型,並將子類型的原型指向這個空函數的實例。便只繼承父類原型上的屬性及方法 Cat.prototype.constructor = Cat; //這裏能夠以後添加子類的方法 Cat.prototype.run = function() { alert('cat run'); }; var cat1 = new Cat(); var cat2 = new Cat(); cat1.name = 'changed name'; alert(cat1.getName()); //changed name alert(cat2.getName()); //animal
最後,寄生組合式繼承是引用類型最理想的繼承範式。
上述代碼還能再進一步優化。
//原型繼承方式 function object(superProto) { function F() {} F.prototype = superProto; return new F(); } //公用的繼承方法 function inheritPrototype(subType, superType) { subType.prototype = object(superType.prototype); subType.prototype.constructor = subType; } function Animal() { this.name = 'animal'; } Animal.prototype.getName = function() { return this.name; }; function Cat() { Animal.call(this); //借用構造函數 } inheritPrototype(Cat, Animal); //調用此方法繼承原型 //這裏能夠以後添加子類的方法 Cat.prototype.run = function() { alert('cat run'); }; var cat1 = new Cat(); var cat2 = new Cat(); cat1.name = 'changed name'; alert(cat1.getName()); //changed name alert(cat2.getName()); //animal
這是 js 對象的建立以及繼承,es6 中新增了關鍵字class
和extend
。方便咱們進行面向對象的編程。
可是理解背後的繼承原理對咱們編程過程當中也是極有幫助的
:)
喜歡就收藏或者點個讚唄 !!