1. 程式人生 > >原來我不理解js原型鏈

原來我不理解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的步驟)