原來我不理解js原型鏈
再說原型鏈之前,我們先實現一個最簡單的js繼承;我們以動物類(Animal類)和人類(Person類)為例,用js實現Person繼承Animal的例子:
// 建構函式 function Animal (name, age, height, weight) { this.name = name; this.age = age; this.height = height; this.weight = weight; } Animal.prototype = { eat: function () { console.log(this.name + '正在吃飯'); }, drink: function() { console.log(this.name + '正在喝水'); } } function Person () { Animal.apply(this, arguments); } // 利用原型鏈(子類的原型指向父類的例項) let cat = new Animal(); Person.prototype = cat; if (!Person.prototype['think']) { Person.prototype.think = function () { console.log(this.name + "正在思考"); } } let stu = new Person("小明", 15, 160, 80); stu.eat(); stu.think(); let dog = new Animal("小黃", 3, 50, 60); dog.think();
上面程式碼中,將Person的原型指向了Animal的例項物件,由於例項物件擁有訪問自身構造器的原型物件的許可權,所以小明可以訪問cat,cat可以訪問Animal.prototype,所以小明可以間接的訪問到Animal.prototype,實現了Person繼承Animal原型裡的所有方法(可以理解為小明擁有了訪問Animal原型的許可權),如下圖所示:
我們再來看看Animal.prototype,它本身是一個物件,那它有沒有可能是某個物件的例項物件呢?我們來訪問下Animal.prototype.constructor,請看結果:
它的構造器是Object,那麼問題來了,既然他有構造器,那它的構造器一定有prototype原型,我們來看看這個構造器的原型是什麼鬼:
它指向的是一個空物件,也就是{},這個{}也是物件,因此,我們可以繼續上一個步驟,來訪問這個物件的構造器以及構造器關聯的原型物件,請看下圖,有點辣眼睛:
通過上面的結果,我們可以發現通過constructor來找原型物件,這好像永遠都是死迴圈:animal.prototype.constructor是一個Object,animal.prototype.constructor.prototype是一個{}物件,如此迴圈往復;既然此路不同,那就另尋出路……
我們可以利用物件的__proto__來查詢一個物件所關聯構造器的原型物件;以下是Animal.prototype.__proto__的結果:
Animal.prototype所關聯的原型物件是一個Object,Object作為構造器函式,他也有prototype也就是Object.prototype,指向的還是Object,
Object.prototype也是一個例項物件,它的構造器也是Object,關聯的原型物件是,Object.prototype.__proto__:
所以,原型鏈的終點就是Object.prototype,因為他關聯的原型物件是null;
以上我們是把Object當作構造器函式來看待的;如果把Object作為例項物件來看待呢?
既然Object是例項物件,那麼他應該有一個構造器,通過Object.constructor檢視:
對,你沒有看錯,Object居然是Function的例項物件。Object的構造器關聯的原型物件是Object.__proto__:
指向的是f,它的構造器也是Function;我們繼續將Object.__proto__當作例項物件探索它關聯的原型物件,也就是Object.__proto__.__proto__指向的還是Objcet:
按照例項物件的特點繼續探索:Object.__proto__.__proto__.__proto__:
最終,它的原型鏈結束了,原型物件成為了null。
所以,不管我們將Object當作建構函式也好,還是當作例項物件也好,最終他們都殊途同歸,走到了原型鏈的盡頭。以上過程,可以參考下圖:
總結一下:
一、prototype和__proto__
prototype: 所有函式都擁有的屬性
__proto__: 物件的屬性,可以訪問一個物件的構造器關聯的原型物件
二、例項物件可以訪問自身構造器的原型物件
三、一個物件obj既可以當作例項物件,也可以當作建構函式。
1、obj作為例項物件的情況
既然obj是例項物件,就必然有建立obj的構造器(constructor),而構造器又會預設有原型物件prototype;這時候,我們只需要使用obj.__proto__就可以獲取當前例項物件obj的構造器關聯的原型物件。
2、obj作為構造器函式
既然obj是構造器函式,就一定有prototype原型物件,也就是obj.prototype,obj.prototype又可以當作是某個物件的例項物件,既然是例項物件,就一定有相關聯的構造器的原型物件(也就是重複1的步驟)