重學JS系列:原型繼承

在 ES2015/ES6 中引入了 class 關鍵字,但那只是語法糖,JavaScript 仍然是基於原型的, 所以本文未用 類 實現 構造函數 和 原型

目錄

  • 原型
  • example
  • 繼承的優缺點

原型的概念:

  • 所有對象都有一個屬性 __proto__指向一個對象, 也就是原型
  • 每個對象的原型都可以通過 constructor找到構造函數,構造函數也可以通過 prototype找到原型
  • 所有函數都可以通過__proto__找到 Function對象
  • 所有對象都可以通過__proto__找到Object對象
  • 對象之間通過__proto__連接起來, 這樣稱之爲原型鏈。當前對象上不存在的屬性可以通過原型鏈一層層往上查找,直至頂層Object對象。
  • 如果還沒有 則爲 null。根據定義, null沒有原型。所以null爲原型鏈的最後一個環節。

儘管這種原型繼承通常被認爲是 JavaScript 的弱點之一,但是原型繼承模型本身實際上比經典模型更強大。例如,在原型模型的基礎上構建經典模型相當簡單。

實原型的最重要的概念就是這些了,如果看很多大篇幅介紹原型的文章,會很容易迷糊。


example 常見的兩種繼承

1.引用類型的屬性被所有實例共享。

function Parent() {
    this.names= ['gavin', 'kevin']
}

function Child() {

}

Child.prototype = new Parent()

var Child1 = new Child()

Child1.names.push('yayu')
console.log(Child1.names)   //output: gavin、 kevin、 yayu

var Child2 = new Child()
console.log(Child2.names)   //output: gavin、 kevin、 yayu

2.通過 new操作符 (經典繼承) 子對象可以向父對象傳遞參數

let F = function (a, b) {
   this.a = a;
   this.b = b;
}
F.prototype.getVal = function () {
	console.log(this.a, this.b)
}
let o = new F(1, 2);
console.log(o.a, o.b) //output: 1, 2

3.通過 Object.create繼承一個對象

var o = {
  a: 2,
  m: function(){
    return this.a + 1;
  }
};

console.log(o.m()); // 3
// 當調用 o.m 時,'this' 指向了 o.

var p = Object.create(o);
// p是一個繼承自 o 的對象

p.a = 4; // 創建 p 的自身屬性 'a'
console.log(p.m()); // 5
// 調用 p.m 時,'this' 指向了 p
// 又因爲 p 繼承了 o 的 m 函數
// 所以,此時的 'this.a' 即 p.a,就是 p 的自身屬性 'a'

繼承的優缺點

優點:

  • 原型中的方法,子對象都可以訪問。

問題:

  • 1、多個實例對引用類型的操作會被篡改
  • 2、子類型的原型上的 constructor 屬性被重寫了
  • 3、給子類型原型添加屬性和方法必須在替換原型之後

問題一

原型鏈繼承方案中,原型實際上會變成另一個類型的實例,如下代碼,Cat.prototype變成了 Animal的一個實例,所以 Animal的實例屬性 names就變成了 Cat.prototype的屬性。
而原型屬性上的引用類型值會被所有實例共享,所以多個實例對引用類型的操作會被篡改。如下代碼,改變了 instance1.names後影響了 instance2

function Animal(){
  this.names = ["cat", "dog"];
}
function Cat(){}

Cat.prototype = new Animal();

var instance1 = new Cat();
instance1.names.push("tiger");
console.log(instance1.names); // ["cat", "dog", "tiger"]

var instance2 = new Cat(); 
console.log(instance2.names); // ["cat", "dog", "tiger"]

問題二、

子類型原型上的 constructor 屬性被重寫了,執行 Cat.prototype = new Animal()後原型被覆蓋, Cat.prototype上丟失了 constructor屬性,Cat.prototype指向了 Animal.prototype,而Animal.prototype.constructor指向了Animal,所以Cat.prototype.constructor指向了 Animal
在這裏插入圖片描述

// 新增,重寫 Cat.prototype 的 constructor 屬性,指向自己的構造函數 Cat
Cat.prototype.constructor = Cat; 

我們可以看到,雖然 已經有 value 屬性,但是 constructor現在是指向 Cat
在這裏插入圖片描述

問題三、

給子類型原型添加屬性和方法必須在替換原型之後,因爲子類型的原型會被覆蓋。

// 木易楊
function Animal() {
    this.value = 'animal';
}

Animal.prototype.run = function() {
    return this.value + ' is runing';
}

function Cat() {}
Cat.prototype = new Animal(); 
Cat.prototype.constructor = Cat; 

// 新增  在 constructor 指向 cat 之後
Cat.prototype.getValue = function() {
  return this.value;
}

var instance = new Cat();
instance.value = 'cat'; 
console.log(instance.getValue()); // cat

哦 對了,既然說是順着原型鏈往上找,那麼自己的方法是優先使用的。所以即使原型頂層有相同的方法,也不會用。所以引入了 繼承的第三個概念 多態

多態 即 繼承而來的方法 不好用, 那就不用你的, 我自己定義一個。



小結

  • 每個對象都擁有一個原型對象,通過 __proto__指針指向一個原型,並從中 繼承方法和屬性,同時原型對象也可能擁有原型, 這樣一層一層, 最終指向 null,這種關係被稱爲 原型鏈
  • 當訪問一個對象的屬性 || 方法 時,它會以自身爲起點,向上遍歷整條 原型鏈 知道找到 匹配的屬性 || 方法 / 到達原型鏈的尾端 null
  • 原型鏈的構建依賴於 __proto__,一層一層最終鏈接到 null
  • instanceof 原理就是一層一層查找 __proto__,如果和 constructor.prototype相同則返回 true,如果一直沒有查找成功則返回 false
  • 原型鏈接繼承的本質是 重寫原型對象,代之以一個新類型的實例
參考
掘金 木易楊說: https://juejin.im/post/5ca9cebb6fb9a05e505c5f81
掘進 yck:     https://juejin.im/post/5c99cb69e51d4556a91f87af
web docs:    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章