這是最後的最後了,我會順便總結一下各類繼承方式的學習和理解。(老闆要求什麼的,管他呢)javascript
圖片來自:http://www.joyme.com/xinwen/2...html
這是一種將原型鏈和借用構造函數的技術結合起來的一種繼承模式。不是假合體,是真合體!java
核心思想是:瀏覽器
很像以前說過的 組合使用構造函數模式和原型模式。
// 父類構造函數 function Food(name) { this.name = name; this.colors = ["red", "blue"]; } // 父類原型對象的方法 Food.prototype.sayName = function() { console.log("我是" + this.name); }; // 子類構造函數 function Fruit(name, place) { // 在構造函數裏面調用父類搞糟函數,實現屬性繼承 Food.call(this, name); this.place = place; } // 將父類的實例賦值給子類的原型對象,實現方法繼承 Fruit.prototype = new Food(); // 添加子類原型對象的方法 Fruit.prototype.sayPlace = function() { console.log(this.place); }; var food1 = new Fruit("蘋果", "非洲"); food1.colors.push("black"); console.log(food1.colors); // 返回 [ 'red', 'blue', ' black' ] food1.sayName(); // 返回 我是蘋果 food1.sayPlace(); // 返回 非洲 var food2 = new Fruit("香蕉", "亞洲"); food2.colors.push("yellow"); console.log(food2.colors); // 返回 [ 'red', 'blue', 'yellow' ] food2.sayName(); // 返回 我是香蕉 food2.sayPlace(); // 返回 亞洲
name
和 colors
)和超類構造函數的原型對象的方法( sayName
)都可以被繼承,而且對於引用類型的值也不會出現相互影響的狀況,而子類構造函數的屬性(place)和子類構造函數的原型對象的方法( sayPlace
)也可以很好的使用,不會被覆蓋,他們相互共享又相互獨立。call
方式,將父類的屬性放到子類的構造函數裏面,也就是借用構造函數模式。constructor
屬性,由於原型對象被重寫了,constructor
就丟失了// 。。。。。。。。 // 子類構造函數 function Fruit(name, place) { // 在構造函數裏面調用父類搞糟函數,實現屬性繼承 Food.call(this, name); // 第二次調用父類構造函數 this.place = place; } // 將父類的實例賦值給子類的原型對象,實現方法繼承 Fruit.prototype = new Food(); // 第一次調用父類構造函數 Fruit.prototype.constrcutor=Fruit;//因重寫原型而失去constructor屬性,因此要對constrcutor從新賦值 // 添加子類原型對象的方法 Fruit.prototype.sayPlace = function() { console.log(this.place); }; // 。。。。。。。
在通常狀況下,這是咱們在 javascript 程序開發設計中比較經常使用的繼承模式了。
基於以上緣由,咱們須要引入寄生組合式繼承來解決它的存在的問題,實現完美的繼承。可是在瞭解它以前,須要先了解寄生式繼承,而瞭解寄生式繼承以前,須要瞭解原型式繼承,他們是一個接一個的推導出來的。less
圖片來自:http://acg.shunwang.com/2014/...函數
核心思想是藉助原型能夠基於已有的對象建立新對象,同時沒必要所以建立自定義類型。學習
// 原型式繼承的關鍵-複製 function object(o) { function F() {} F.prototype = o; return new F(); } var food1 = { name: "蘋果", colors: ["red", "blue"] }; // 繼承 var food2 = object(food1); food2.name = "香蕉"; food2.colors.push("black"); //。。。。。。無限增殖 console.log(food1.name); // 返回 蘋果 console.log(food2.name); // 返回 香蕉 console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ] console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]
Object.create()
:Object.create()
方法會建立一個新對象,使用現有的對象來提供新建立的對象的__proto__
。ui
Object.create()
是es5新增的,用來規範原型式繼承。
若是單純使用的話,效果跟以前的差異不大,參考下面例子:this
var food1 = { name: "蘋果", colors: ["red", "blue"] }; var food2 = Object.create(food1); food2.name = "香蕉"; food2.colors.push("black"); console.log(food1.name); // 返回 蘋果 console.log(food2.name); // 返回 香蕉 console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ] console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]
若是注意使用它的第二個參數的話,差異就不同了:google
var food1 = { name: "蘋果", colors: ["red", "blue"] }; var food2 = Object.create(food1, { name: { value: "香蕉" }, colors: { // !!!!! value: ["red", "blue", "black"] } }); console.log(food1.name); // 返回 蘋果 console.log(food2.name); // 返回 香江 console.log(food1.colors); // 返回 [ 'red', 'blue' ] !!!!! console.log(food2.colors); // 返回 [ 'red', 'blue', 'black' ]
能夠看到引用類型的數值不會被共享,實現了很好的繼承效果。
出現這個狀況主要是由於若是使用 push 的話,仍是操做同一個內存指針,使用
Object.create
的話,會從新添加到新建立對象的可枚舉屬性,不是同一個內存指針了。
圖片來自:http://www.cifnews.com/articl...
參考 mdn 裏面的介紹,會發現一些更有價值的東西,能夠用 Object.create
實現類式繼承:
// Shape - 父類(superclass) function Shape() { this.x = 0; this.y = 0; } // 父類的方法 Shape.prototype.move = function(x, y) { this.x += x; this.y += y; console.info('Shape moved.'); }; // Rectangle - 子類(subclass) function Rectangle() { Shape.call(this); // call super constructor. } // 子類續承父類 Rectangle.prototype = Object.create(Shape.prototype); Rectangle.prototype.constructor = Rectangle; var rect = new Rectangle(); console.log(rect instanceof Rectangle); // true console.log(rect instanceof Shape); // true rect.move(1, 1); // Outputs, 'Shape moved.'
Object.create
會將參數裏的對象添加到它返回的新對象的原型對象裏面去,這樣首先生成了一個新對象,而且該對象的原型對象是參數裏的值,即Shape.prototype
,新對象是臨時的,暫時看不到,這個臨時的新對象裏面就包含了父類原型對象。Object.create
返回的新對象放到子類的原型對象裏面,這樣子類就擁有了父類的原型對象,也就實現了方法的繼承。constructor
,是爲了從新指定子類的構造函數名字,這樣子類實例對象就能夠查看到他的構造函數是誰,證實是某個實例來自於哪個構造函數,這樣代碼和結構都會清晰。call
實現。還有更屌炸飛的東西,若是你但願能繼承到多個對象,則可使用混入的方式。
function MyClass() { SuperClass.call(this); OtherSuperClass.call(this); } // 繼承一個類 MyClass.prototype = Object.create(SuperClass.prototype); // 混合其它 Object.assign(MyClass.prototype, OtherSuperClass.prototype); // 從新指定constructor MyClass.prototype.constructor = MyClass; MyClass.prototype.myMethod = function() { // do a thing };
Object.assign
會把 OtherSuperClass
原型上的函數拷貝到 MyClass
原型上,使 MyClass
的全部實例均可用 OtherSuperClass
的方法。Object.assign
是在 ES2015
引入的,且可用 polyfilled
。要支持舊瀏覽器的話,可用使用 jQuery.extend()
或者 _.assign()
。與時俱進,紅寶書《javascript 高級程序設計第三版》 也並非無敵的,固然,一會兒知識量太大,咱們吸取不了,因此這裏不展開細說。
在引入寄生組合式繼承以前,須要瞭解什麼是寄生式繼承。
圖片來自:https://2ch.hk/b/arch/2017-01...
寄生式繼承的思路跟寄生構造函數模式和工廠模式很相似,核心思想是建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真得是它作了全部工做同樣返回對象。
感受像是原型式繼承的升級版!
// 原型式繼承的關鍵-複製 function object(o) { function F() {} F.prototype = o; return new F(); } function createFood(original) { var clone = object(original); clone.sayName = function(name) { console.log(name); }; return clone; } var food1 = { name: "蘋果" }; var food2 = createFood(food1); console.log(food2.name); // 返回蘋果 food2.sayName("香蕉"); // 返回香蕉
這是一種比較簡單的實現繼承的方式,在不考慮自定義類型和構造函數的狀況下,也算是一種有用的模式。
終於到了主角了。
圖片來自:https://www.9yread.com/book/1...
寄生組合式繼承的核心思想是:
好複雜的解釋,先看看代碼吧:
// object 函數能夠用 Object.create 來代替。 function object(o) { function F() {} F.prototype = o; return new F(); } // 這裏是關鍵 function inheritPrototype(subType, superType) { // ①將超類原型放到一個臨時的對象裏面(建立超類型圓形的副本) var prototype = object(superType.prototype); // ②從新指定這個臨時對象的constructor 爲 子類構造函數 prototype.constructor = subType; // ③將這個臨時對象賦值給子類的原型對象 subType.prototype = prototype; } function Food(name) { this.name = name; this.colors = ["red", "blue"]; } Food.prototype.sayName = function() { console.log(this.name); }; function Fruit(name, place) { Food.call(this, name); this.place = place; } inheritPrototype(Fruit, Food); Fruit.prototype.sayPlace = function() { console.log(this.place); }; var food1 = new Fruit("蘋果", "非洲"); var food2 = new Fruit("香蕉", "亞洲"); console.log(food1.sayName()); // 返回 蘋果 console.log(food1.sayPlace()); // 返回 非洲 food1.colors.push("black"); console.log(food1.colors); // 返回 [ 'red', 'blue', 'black' ] console.log(food2.colors); // 返回 [ 'red', 'blue' ] console.log(food1 instanceof Fruit); // 返回 true console.log(food1 instanceof Food); // 返回 true console.log(Fruit.prototype.isPrototypeOf(food1)); // 返回 true console.log(Food.prototype.isPrototypeOf(food1)); // 返回 true
object
函數能夠用Object.create
來代替。
藉助這個圖理解一下,這種繼承模式拆開來看就是寄生式(複製)+組合式(原型鏈+構造函數)
圖片來自https://www.jianshu.com/p/004...
幾乎涵蓋了全部 javascript 的繼承模式了:
圖片來自:https://zhuanlan.zhihu.com/p/...
有幾點是我以爲能夠總結一下,前人栽樹,後人乘涼: