我來從新學習js 的面向對象(part 5)

這是最後的最後了,我會順便總結一下各類繼承方式的學習和理解。(老闆要求什麼的,管他呢)javascript

1、繼承-組合繼承、僞經典繼承


圖片來自: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(); // 返回 亞洲
  • 能夠看到超類構造函數 Food裏的屬性(namecolors)和超類構造函數的原型對象的方法( 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

2、繼承-原型式繼承


圖片來自:http://acg.shunwang.com/2014/...函數

核心思想是藉助原型能夠基於已有的對象建立新對象,同時沒必要所以建立自定義類型。學習

  • 以一個對象實例來作模板進行復制,而且是藉助原型鏈模式進行特殊複製
  • 這種複製的方式會有一些特別的地方,例如,引用類型的值問題也是沒法解決,複製能夠藉助 es5語法也能夠不借助,前者更增強大一些。
// 原型式繼承的關鍵-複製
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' ]

2.1 使用 es5的新語法: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的話,會從新添加到新建立對象的可枚舉屬性,不是同一個內存指針了。

2.2 發現一些有價值的東西


圖片來自: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 高級程序設計第三版》 也並非無敵的,固然,一會兒知識量太大,咱們吸取不了,因此這裏不展開細說。

3、繼承-寄生式繼承

在引入寄生組合式繼承以前,須要瞭解什麼是寄生式繼承。


圖片來自: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("香蕉"); // 返回香蕉
  • 能夠看到 name 屬性是沒有變化的,能夠將一些共享的屬性放在裏面來造成複製。
  • 這裏須要注意若是須要給添加的新函數傳參的話,是不能夠在」克隆「的時候傳的,須要在外面使用的時候傳。
這是一種比較簡單的實現繼承的方式,在不考慮自定義類型和構造函數的狀況下,也算是一種有用的模式。

4、繼承-寄生組合式繼承

終於到了主角了。


圖片來自: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/...

有幾點是我以爲能夠總結一下,前人栽樹,後人乘涼:

  1. 書不要讀死,若是單純讀《javascript 高級程序設計第三版》是不可能完整了解 javascript 的,起碼在面向對象這部分是不行的,不少網上的大(zhuang)牛(bi)都會叫你認真閱讀這本書,可是對於初學者來講,基本是很難理解獲得做者的思路和意思的,不是資質問題,是閱歷和經驗和知識含量不足的限制。
  2. 看不懂,沒關係,多看,多查閱資料,記得用 google 查,baidu 只會讓你多瞭解一些廣告罷了。
  3. 網上的文章質量也是良莠不齊的,就算是我這篇裝逼文,也是我本身以爲很好,可是未必可以面面俱到,可是人生原本就難以面面俱到,不是嗎?重要的是,我用個人經驗寫了,你能看明白一些是一些,看不明白就當飯後爾爾罷了,不用糾結。
  4. 要本身作實驗,本身輸出一些結果,對比理論,對比別人的結果和分析,這樣才能理解得好一些。
  5. 學習第一次發現徹底懵逼的話,就嘗試去組織一個脈絡結構,就好像我這樣,嘗試作一個故事代入,一環扣一環來理解,雖然《javascript 高級程序設計第三版》這本書裏面也有,可是感受後面開始省略不少一部分了,以至迷失了。
  6. 不要怕,多學習,莫道前路無知己,天下誰人不識君,加油加油,也是自勉。

參考內容

  1. 紅寶書,javascript 高級程序設計第三版

原文轉載:
https://www.godblessyuan.com/...

相關文章
相關標籤/搜索