JavaScript從原型到原型鏈,細緻講解

reade發表於2018-12-11

在這裡插入圖片描述

一、建構函式、原型物件、例項物件的關係 及延伸


function Foo(){}   	  			//Foo為建構函式
Foo.prototype.name = 'Feidian';  //Foo.prototype即原型物件
var f1 = new Foo()	 	        //f1 為 例項物件
複製程式碼

要想了解Foo、Foo.prototype、f1 之間的關係,我們就要理解 prototype、__proto __、constructor 。


1、prototype


函式都有 prototype屬性(即原型物件):該原型物件儲存著所有例項物件的 公共屬性和方法。

在這裡插入圖片描述

function Foo(){}   	  			
Foo.prototype.name = 'Feidian';  
var f1 = new Foo();	 	      

console.log(f1.name);   //'Feidian'
複製程式碼

f1是Foo函式new出來的例項,訪問 f1.name,實際訪問的 是Foo.prototype中的name屬性。

注: 基本上所有函式都有 prototype 屬性,除了下面例子: let fun = Function.prototype.bind()


2、__proto __


每個js物件(除了 null )都有 __proto__屬性(指標): __proto__指向著創造該js物件的建構函式 的prototype.

在這裡插入圖片描述

function Foo(){}   	  			
Foo.prototype.name = 'Feidian';  
var f1 = new Foo();

console.log(f1.__proto__ === Foo.prototype);  // true
複製程式碼

f1通過__proto__,指向著Foo的原型物件。


3、constructor


原型物件都有 constructor屬性(指標):它指向著該原型物件的擁有者。

在這裡插入圖片描述

function Foo(){}   	  			
Foo.prototype.name = 'Feidian';  
var f1 = new Foo();

console.log(Foo.prototype.constructor === Foo)     // true
複製程式碼

Foo.prototype的constructor,指向著建構函式Foo

(1)注意:

若完全重寫prototype,則要重指向constructor

console.log(Foo.prototype.constructor);  //ƒ Foo(){}
Foo.prototype = {};    //重寫prototype
console.log(Foo.prototype.constructor);  //ƒ Object() { [native code] }
複製程式碼

改:

console.log(Foo.prototype.constructor);  //ƒ Foo(){}
Foo.prototype = {constructor:Foo}        //重寫,讓constructor重新指向Foo
console.log(Foo.prototype.constructor);  //ƒ Foo(){}
複製程式碼
(2)注意:

例項物件呼叫constructor,實際上呼叫的是原型物件的constructor。

console.log(f1.constructor === Foo.prototype.constructor); //true
複製程式碼

4、繼續擴充關係


(1)Foo.prototype.__proto __指向哪裡?

我們知道:原型物件是一個物件,物件是由Object建構函式創造的;因而得知Foo.prototype是由建構函式Object創造的。

前面又提到:__proto__指向著創造該js物件的建構函式 的prototype。所以Foo.prototype.__proto __ 指向著Object.prototype

再根據前面講的prototype、constructor,可以將圖更新為:

在這裡插入圖片描述

(2)Object.prototype.__proto __指向哪裡?

我們用 (1) 的理論推測Object.prototype.__proto __的指向,發現按上面理論講它應該指向它自己,但實際並不是。

console.log(Object.prototype.__proto__ === null) // true
複製程式碼

通過上面程式碼,我們發現Object.prototype.__proto __指向為null。意味著Object.prototype沒有原型。

所有物件都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個物件,但是這個物件卻不是 Object 創造的,而是引擎自己建立了 Object.prototype

在這裡插入圖片描述

(3)Object.__proto __指向哪裡?

我們知道:Object()是一個建構函式,函式都是由new Function()生成的;因而Object()也是由建構函式Function創造的。

前面又提到:__proto__指向著創造該js物件的建構函式 的prototype。所以Object.__proto__ 指向著Function.prototype

再根據前面講的prototype、constructor,可以將圖更新為:

在這裡插入圖片描述

(4)Function.__proto __指向哪兒?Function.prototype.__proto __又指向哪兒?
console.log(Function.__proto__ === Function.prototype); //true
console.log(Function.prototype.__proto__ === Object.prototype); //true
複製程式碼

通過上面程式碼,我們發現:

  • Function.__proto __ 指向 Function.prototype。難道Function自己創造了自己?
  • Function.prototype.__proto__ 指向 Object.prototype。用之前理論分析沒毛病,但具體原因不是我們分析的那樣。往後看?

如果你在瀏覽器將Function.prototype物件列印出來,會發現這個物件其實是一個函式:

在這裡插入圖片描述

我們知道函式都是通過 new Function() 生成的,難道Function.prototype 是通過 new Function() 產生的?答案也是否定的。Function.prototype也是引擎自己建立的

首先引擎建立了 Object.prototype ,然後建立了 Function.prototype ,並且通過 __proto __ 將兩者聯絡了起來。並且是:先有的 Function.prototype ,之後才有的 function Function()。

通過上面,我們就知道了為什麼 let fun = Function.prototype.bind() 沒有 prototype 屬性。 因為 Function.prototype 是引擎建立出來的物件,引擎認為不需要給這個物件新增 prototype 屬性。

對於為什麼 Function.__proto __ 會等於 Function.prototype ,有這麼種理解是:其他所有的建構函式都可以通過原型鏈找到 Function.prototype ,並且 function Function() 本質也是一個函式,為了不產生混亂就將 function Function() 的 __proto __ 聯絡到了 Function.prototype 上。


再更新一下圖:

在這裡插入圖片描述

(5)Foo.__proto __指向哪裡?

我們知道:Foo是一個建構函式,函式都是由new Function()生成的;因而Foo也是由建構函式Function創造的。

前面又提到:__proto__指向著創造該js物件的建構函式 的prototype。所以Foo.__proto__ 指向著Function.prototype

在這裡插入圖片描述

小結:

  • Object 是所有物件的爸爸,所有物件都可以通過 __proto __ 找到它
  • Function 是所有函式的爸爸,所有函式都可以通過 __proto __ 找到它
  • Function.prototype 和 Object.prototype 是由引擎來建立的。其中Function.prototype是個函式.
  • 除了上面兩個,其他物件都是被構造器new出來的。

二、原型鏈及操作


1、什麼是原型鏈?

簡單點理解,就是由若干__proto__連起來形成的鏈條,就是原型鏈。

在這裡插入圖片描述
拿上面例子來說:
在這裡插入圖片描述
就是一條原型鏈; (f1能使用Foo.prototype和Object.prototype裡的屬性和方法,即實現物件導向程式語言的繼承關係)
在這裡插入圖片描述
也是一條原型鏈;


2、原型鏈的操作

function Foo(){}   	  			
Foo.prototype.name = 'Feidian'; 
var f1 = new Foo();

f1.name = 'bty';
console.log(f1.name); 	   //'bty'
delete f1.name;
console.log(f1.name);       //'Feidian'
console.log(f1.toString()); //'[object Object]'
複製程式碼

上面例子的原型鏈如下:

在這裡插入圖片描述
下面我們看看執行後五句,發生了什麼:

  • f1.name = 'bty'; 先在f1物件上查詢有沒有name屬性,發現沒有,則直接在f1物件上新增name屬性,並賦值為’bty’ ; 若發現f1上有name屬性,則直接修改f1的name的值。
  • console.log(f1.name); 先在f1物件上查詢有沒有name屬性,發現有,則輸出 'bty'
  • delete f1.name; 刪除f1的name屬性。 若f1沒有name屬性,delete f1.name後,也不會對Foo原型上的屬性造成影響。
  • console.log(f1.name); 先在f1物件上查詢有沒有name屬性,發現沒有;再到看看Foo.prototype上有沒有,發現有,則輸出 'Feidian'
  • console.log(f1.toString()); 先在f1物件上找有沒有toString()方法,沒有;再看看Foo.prototype上有沒有,還有沒;再沿著原型鏈到Object.prototype上找,發現有,則呼叫該方法。

相關文章