【第24題】三條路徑理清原型鏈(定格記憶篇)

面試題(頭條)

利用原型和原型鏈相關知識,畫出 Object、 Function、Object.prototype、Function.prototype 四個對象間的關聯關係

答案解析:

原型和原型鏈,是javascript這門語言中的重要概念,同時也是面試過程中的必考知識點。小編對它也是日常迷惑,每次都需要查資料、重新理邏輯,近日重新換了個角度思考,對原型和原型鏈有了更加深刻的認識和理解。

被忽視的點

有一些比較重要的知識點,往往容易被忽略,清醒的認識下面的幾個點,對於後面理解原型和原型鏈有很大幫助。

1. prototype是函數對象的獨有屬性,同時prototype對象是一個普通對象object(Function.prototype除外,它是一個function)

在JS的世界中,對象被分爲兩種:普通對象(object)、函數對象(function)。

只有函數對象纔有prototype屬性,普通對象是沒有prototype屬性的。

可以通過下面代碼進行驗證:

let obj = {
    name: '前端名獅'
};

function foo () {
    console.log('my name is 前端名獅');
}

console.log(obj.prototype); // undefined
console.log(foo.prototype); // {constructor: foo}

console.log(typeof(foo.prototype)) // object
console.log(foo.prototype.hasOwnProperty('constructor')) // true

由上我們能看出:

obj.prototype 輸出undefined,表明普通對象沒有prototype屬性。

foo.prototype的輸出結果是一個普通的object,同時裏面包含了constructor屬性,而constructor指向了foo函數本身。所以我們能得出下圖結論:

2. Object,Function 都是函數

由於原型和原型鏈之間的關係複雜,梳理過程中,很容易忽視Object、Function是函數,可以通過下面代碼加深理解

console.log(typeof(Object)); //打印出 Function
let obj = new Object(); // 可以使用new創建,表明是一個函數

console.log(typeof(Function));
let fn = new Function('console.log(88)'); // 通過new的方式創建函數,表明Function本身是一個函數

三條路徑覆蓋原型鏈

所謂的原型鏈,就是查找一個對象的屬性時,先查找它本身是否存在該屬性,如果本身不存在的話,會通過隱式原型屬性__proto__ 找到它的上一級對象,,然後再通過上一級對象的__proto__查找上一級的上一級,一直到最頂層對象null,這樣的一種查找途徑,通過__proto__形成了一條鏈路。

上面提到,prototype是函數對象的特有屬性,而__proto__屬性任何對象有。所以原型鏈實際是靠__proto__屬性實現的。

我們一定要牢記下面這句話,我們下面所有的內容都是基於這句話展開的,沒有爲什麼,JS這門語言實現時就是這麼指定的,記住就好了:
對象的__proto__指向的是創建它的函數的prototype

__proto__相當於C++中的指針,prototype相當於指針指向的對象,該prototye對象中包含了__proto__指針,指向上一級對象。

下面我們來分析一下原型鏈存在的三條路徑:

一. 普通對象

常見普通對象的生成其實有兩種形式,如下代碼:

// 第一種
let obj = {
    name: '前端名獅'
}

// 第二種,通過函數對象新建

function Foo () {
    console.log('my name is 前端名獅');
}

let foo = new Foo();

正常情況下,兩種情況實際屬於同一種情況,爲什麼呢?

new 本身經歷了一個複雜的過程,正常情況返回的實際是內部創建的一個object對象,具體可以閱讀【第15題】- new 操作符內部實現原理

下面我們分析一下obj,foo的原型鏈路徑

  1. obj 本身是一個普通對象,它的構造函數實際是Object,所以我們找到了創建obj的函數Object。具體推導流程如下面代碼所示,一直到頂層null
obj.constructor === Object

obj.__proto__ === Object.prototype

Object.prototype.__proto__ === null

  1. foo的構造函數是Foo,所以foo.__proto__ === Foo.prototype

由上方的分析可知,prototype是一個普通對象,所以Foo.prototype是一個普通對象object,再往上查找就回到了第一種情況Foo.prototype.__proto__ === Object.prototype,再往上就到達原型鏈的頂層了。

foo.constructor === Foo

foo.__proto__ === Foo.prototype

Foo.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

二. 函數對象

從函數Foo開始,分析下原型鏈的路徑

function Foo () {
    console.log('my name is 前端名獅');
}

Foo 是一個函數,所以它的構造函數是Function,即Foo.constructor === Function,可以得知Foo.__proto__ === Function.prototype。再往上就是Function.prototype.__proto__ === Object.prototype,剩下的就跟上面的情況一樣了。

Foo.constructor === Function

Foo.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

三. Object和Function

正如前面分析的那樣,Object、Function都是函數對象,這兩個對象是JS對象的源頭,所以單獨拎出來分析。

Object本身是一個函數對象,它的構造函數是Function,即 Object.constructor === Function,所以Object.__proto__ === Function.prototype,再往上查找就是Function.prototype.__proto__ === Object.prototype,再往上就是null。

Object.constructor === Function

Object.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

Function本身是一個函數,它的構造函數還是一個函數,所以Function.constructor === Function,所以Function.__proto__ === Function.prototype,後面的就和上面一樣了。

Function.constructor === Function

Function.__proto__ === Function.prototype

Function.prototype.__proto__ === Object.prototype

Object.prototype.__proto__ === null

總結

按照上面的三個路徑,很容易記憶理解原型鏈,上面內容也基本上涵蓋了原型鏈的大部分場景,應付面試題綽綽有餘。記憶理解後,再也不用每次面試都要重新複習這個難點了。

推薦閱讀

  1. 2019年前端大事件回顧:流年笑擲,未來可期
  2. Single-Spa + Vue Cli 微前端落地指南
  3. BAT開源項目彙總
  4. 【第22題】理解 JS 模塊化
  5. 【深入vue】爲什麼Vue3.0不再使用defineProperty實現數據監聽?
  6. 拋棄jenkins,如何用node從零搭建自動化部署管理平臺
  7. 前端部署演化史
  8. 深入理解 ES6 Iterator
  9. 解讀HTTP/2與HTTP/3 的新特性(推薦)

關注我

掃一掃 關注我的公衆號【前端名獅】,更多精彩內容陪伴你!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章