更新:謝謝你們的支持,最近折騰了一個博客官網出來,方便你們系統閱讀,後續會有更多內容和更多優化,猛戳這裏查看javascript
------ 如下是正文 ------html
上篇文章介紹了構造函數、原型和原型鏈的關係,而且說明了 prototype
、[[Prototype]]
和 __proto__
之間的區別,今天這篇文章用圖解的方式向你們介紹原型鏈及其繼承方案,在介紹原型鏈繼承的過程當中講解原型鏈運做機制以及屬性遮蔽等知識。前端
建議閱讀上篇文章後再來閱讀本文,連接:【進階5-1期】從新認識構造函數、原型和原型鏈java
有什麼想法或者意見均可以在評論區留言。下圖是本文的思惟導圖,高清思惟導圖和更多文章請看個人 Github。webpack
上篇文章中咱們介紹了原型鏈的概念,即每一個對象擁有一個原型對象,經過 __proto__
指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null
,這種關係被稱爲原型鏈(prototype chain)。git
根據規範不建議直接使用 __proto__
,推薦使用 Object.getPrototypeOf()
,不過爲了行文方便邏輯清晰,下面都以 __proto__
代替。github
注意上面的說法,原型上的方法和屬性被 繼承 到新對象中,並非被複制到新對象,咱們看下面這個例子。web
// 木易楊
function Foo(name) {
this.name = name;
}
Foo.prototype.getName = function() {
return this.name;
}
Foo.prototype.length = 3;
let foo = new Foo('muyiy'); // 至關於 foo.__proto__ = Foo.prototype
console.dir(foo);
複製代碼
原型上的屬性和方法定義在 prototype
對象上,而非對象實例自己。當訪問一個對象的屬性 / 方法時,它不只僅在該對象上查找,還會查找該對象的原型,以及該對象的原型的原型,一層一層向上查找,直到找到一個名字匹配的屬性 / 方法或到達原型鏈的末尾(null
)。面試
好比調用 foo.valueOf()
會發生什麼?算法
foo
對象是否具備可用的 valueOf()
方法。foo
對象的原型對象(即 Foo.prototype
)是否具備可用的 valueof()
方法。Foo.prototype
所指向的對象的原型對象(即 Object.prototype
)是否具備可用的 valueOf()
方法。這裏有這個方法,因而該方法被調用。prototype
和 __proto__
上篇文章介紹了 prototype
和 __proto__
的區別,其中原型對象 prototype
是構造函數的屬性,__proto__
是每一個實例上都有的屬性,這兩個並不同,但 foo.__proto__
和 Foo.prototype
指向同一個對象。
此次咱們再深刻一點,原型鏈的構建是依賴於 prototype
仍是 __proto__
呢?
Foo.prototype
中的 prototype
並無構建成一條原型鏈,其只是指向原型鏈中的某一處。原型鏈的構建依賴於 __proto__
,如上圖經過 foo.__proto__
指向 Foo.prototype
,foo.__proto__.__proto__
指向 Bichon.prototype
,如此一層一層最終連接到 null
。
能夠這麼理解 Foo,我是一個 constructor,我也是一個 function,我身上有着 prototype 的 reference,只要隨時調用 foo = new Foo(),我就會將
foo.__proto__
指向到個人 prototype 對象。
不要使用 Bar.prototype = Foo
,由於這不會執行 Foo
的原型,而是指向函數 Foo
。 所以原型鏈將會回溯到 Function.prototype
而不是 Foo.prototype
,所以 method
方法將不會在 Bar 的原型鏈上。
// 木易楊
function Foo() {
return 'foo';
}
Foo.prototype.method = function() {
return 'method';
}
function Bar() {
return 'bar';
}
Bar.prototype = Foo; // Bar.prototype 指向到函數
let bar = new Bar();
console.dir(bar);
bar.method(); // Uncaught TypeError: bar.method is not a function
複製代碼
instanceof
運算符用來檢測 constructor.prototype
是否存在於參數 object
的原型鏈上。
// 木易楊
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,由於 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,由於 D.prototype 不在 o 的原型鏈上
複製代碼
instanceof 原理就是一層一層查找 __proto__
,若是和 constructor.prototype
相等則返回 true,若是一直沒有查找成功則返回 false。
instance.[__proto__...] === instance.constructor.prototype
複製代碼
知道了原理後咱們來實現 instanceof,代碼以下。
// 木易楊
function instance_of(L, R) {//L 表示左表達式,R 表示右表達式
var O = R.prototype;// 取 R 的顯示原型
L = L.__proto__;// 取 L 的隱式原型
while (true) {
// Object.prototype.__proto__ === null
if (L === null)
return false;
if (O === L)// 這裏重點:當 O 嚴格等於 L 時,返回 true
return true;
L = L.__proto__;
}
}
// 測試
function C(){}
function D(){}
var o = new C();
instance_of(o, C); // true
instance_of(o, D); // false
複製代碼
原型鏈繼承的本質是重寫原型對象,代之以一個新類型的實例。以下代碼,新原型 Cat
不只有 new Animal()
實例上的所有屬性和方法,而且因爲指向了 Animal
原型,因此還繼承了Animal
原型上的屬性和方法。
// 木易楊
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
// 這裏是關鍵,建立 Animal 的實例,並將該實例賦值給 Cat.prototype
// 至關於 Cat.prototype.__proto__ = Animal.prototype
Cat.prototype = new Animal();
var instance = new Cat();
instance.value = 'cat'; // 建立 instance 的自身屬性 value
console.log(instance.run()); // cat is runing
複製代碼
原型鏈繼承方案有如下缺點:
原型鏈繼承方案中,原型實際上會變成另外一個類型的實例,以下代碼,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 = new Animal();
Cat.prototype.constructor === Animal
// true
複製代碼
解決辦法就是重寫 Cat.prototype.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
Cat.prototype.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;
// 新增
Cat.prototype.getValue = function() {
return this.value;
}
var instance = new Cat();
instance.value = 'cat';
console.log(instance.getValue()); // cat
複製代碼
改造上面的代碼,在 Cat.prototype
上添加 run
方法,可是 Animal.prototype
上也有一個 run
方法,不過它不會被訪問到,這種狀況稱爲屬性遮蔽 (property shadowing)。
// 木易楊
function Animal() {
this.value = 'animal';
}
Animal.prototype.run = function() {
return this.value + ' is runing';
}
function Cat() {}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// 新增
Cat.prototype.run = function() {
return 'cat cat cat';
}
var instance = new Cat();
instance.value = 'cat';
console.log(instance.run()); // cat cat cat
複製代碼
那如何訪問被遮蔽的屬性呢?經過 __proto__
調用原型鏈上的屬性便可。
// 接上
console.log(instance.__proto__.__proto__.run()); // undefined is runing
複製代碼
原型鏈繼承方案有不少問題,實踐中不多會單獨使用,平常工做中使用 ES6 Class extends(模擬原型)繼承方案便可,更多更詳細的繼承方案能夠閱讀我以前寫的一篇文章,歡迎拍磚。
有如下 3 個判斷數組的方法,請分別介紹它們之間的區別和優劣
Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()
參考答案:點擊查看
__proto__
指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null
,這種關係被稱爲原型鏈null
)。__proto__
,一層一層最終連接到 null
。__proto__
,若是和 constructor.prototype
相等則返回 true,若是一直沒有查找成功則返回 false。進階系列文章彙總以下,內有優質前端資料,以爲不錯點個star。
我是木易楊,公衆號「高級前端進階」做者,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!