玩轉 JavaScript 之不得不懂的原型

唐羲發表於2019-03-14

概述

本系列文章的第一篇中提到了物件型別,物件是 JavaScript 中的重要角色之一,本篇便從原型這個知識點切入,希望大家在閱讀過本篇文章之後腦海中都能夠建立起一張完整的思維導圖。

函式與物件的關係

首先我們要確定一點,函式是一種可呼叫的物件,也在本系列第一篇中曾提到過,我們可以做如下校驗:

(()=>{}) instanceof Object   // true
複製程式碼

其次我們要確定,物件都是通過函式建立的,比如我們平時寫的物件字面量,其實只是 new Object() 之類的語法糖而已。

知道以上兩點之後,我們可以說,函式是一種物件,物件又是通過函式建立,所以物件建立物件。是否會覺得有點不明覺厲的感覺?其實要弄清楚它們之間的關係,不用去管這些彎彎繞。

只要再明白一點,JavaScript 中除了 Object ,還存在一個角色,那就是 Function

我們先來看一下在瀏覽器控制檯列印出 ObjectFunction 的結果:

>  Object
<· ƒ Object() { [native code] }
>  Function
<· ƒ Function() { [native code] }
複製程式碼

原型

原型屬於 JavaScript 的核心,我們一步步來分析,瞭解原型的方方面面。

prototype[[Prototype]]

我們知道函式是可呼叫物件,既然是物件,函式也是屬性的集合。這些屬性中其中一個就是 prototype 屬性,也就是我們通常所說的原型。所有函式都有 prototype 屬性 (Function.prototype.bind() 例外)。而 prototype 是一個物件,它有個 constructor 屬性指向這個函式。這裡用思維導圖表示出來,大家可以自行在瀏覽器視窗列印相關資訊來印證。

玩轉 JavaScript 之不得不懂的原型

從上圖可以很清楚看出 function Foo()Foo.prototype 的關係,同時我們會看到例項物件 foo 有一個 __proto__ 屬性指向 Foo.prototype__proto__我們稱為隱式原型,只是物件內建屬性[[Prototype]]的非標準實現,雖然瀏覽器都支援但是不推薦使用。

下文為方便表達理解,[[Prototype]] 內建屬性會用 __proto__ 表示。

ES6中推薦使用 Object.getPrototypeOf()方法來返回一個物件的 [[Prototype]],使用 Object.setPrototypeOf() 方法來設定一個物件的 [[Prototype]]

更進一步

我們知道 Foo.prototype 也是一個物件,那它的 __proto__ 指向哪裡呢?

我們知道 function Object() 也是函式,所以它和普通建構函式擁有同樣的規則,不同點在於,Object.prototype 位於原型鏈頂端,看圖:

玩轉 JavaScript 之不得不懂的原型

由上圖可知,Foo.prototype__proto__指向了 Object.prototypeObject.prototype 也是一個物件,它的 __proto__ 指向了 null,這應該好理解,意思就是到頂了。

解開謎團

是時候解開謎團了,上文也提到 function Function() 也是函式,是不是和 function Foo()function Object()適用同樣的規則呢?答案是肯定的,看下圖:

玩轉 JavaScript 之不得不懂的原型

這裡除了上面的規則,我們還應注意到幾個點:

  • function Foo()function Object()__proto__屬性都指向了 Function.prototype,這說明函式都是通過 Function建構函式 new 出來的
  • function Function() 是個例外,雖然它的 __proto__ 也指向了Function.prototype,但是是引擎先內建了 Function.prototype, 然後才有 function Function(),所以並不是自己建立自己;
  • Function.prototype__proto__ 指向了 Object.prototype,是因為引擎先建立 Object.prototype,再建立 Function.prototype,並將兩者用 __proto__ 聯絡起來。

原型鏈

上面的圖已經畫出了一條條的箭頭指向的鏈條,通過 __proto__ 屬性連線,這就是原型鏈。

具體的可以理解為:當尋找一個物件的某個屬性時,如果沒有找到,則會順著 __proto__ 屬性指向的原型物件上查詢,一直往上直到 Object.prototype,這一條查詢的線路就被稱之為原型鏈。

總結

為方便理解,總結必不可少:

  1. Object.prototypeFunction.prototype 是兩個特殊物件,由引擎建立,所以不用糾結這倆物件怎麼來的了;
  2. 物件都能通過 __proto__ 屬性找到 Object.prototypeObject.create(null) 創造出的物件例外,因為沒有 __proto__ 屬性;
  3. 函式都能通過 __proto__ 屬性找到 Function.prototype;
  4. 物件都是函式 new 出來的,除了上面兩個特殊物件;
  5. 函式的 prototype 是物件,它有個 constructor 屬性指向建構函式本身;
  6. 物件的 __proto__ 指向原型, __proto__ 將物件和原型連線起來組成了原型鏈。

你或許會問,直到這些有什麼用呢?那就涉及到類和繼承方面的問題了,下篇再見!

玩轉 JavaScript 系列

寫作是一個學習的過程,嘗試寫這個系列也主要是為了鞏固 JavaScript 基礎,並嘗試理解其中的一些知識點,以便能靈活運用。本篇同步釋出在「端技」公眾號,如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!

整個系列會持續更新,不會完結。

全目錄

1. 玩轉 JavaScript 之 資料型別

2. 玩轉 JavaScript 之不得不懂的原型

相關文章