面試題(頭條)
利用原型和原型鏈相關知識,畫出
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
的原型鏈路徑
- obj 本身是一個普通對象,它的構造函數實際是Object,所以我們找到了創建obj的函數Object。具體推導流程如下面代碼所示,一直到頂層
null
obj.constructor === Object
obj.__proto__ === Object.prototype
Object.prototype.__proto__ === null
- 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
總結
按照上面的三個路徑,很容易記憶理解原型鏈,上面內容也基本上涵蓋了原型鏈的大部分場景,應付面試題綽綽有餘。記憶理解後,再也不用每次面試都要重新複習這個難點了。
推薦閱讀
- 2019年前端大事件回顧:流年笑擲,未來可期
- Single-Spa + Vue Cli 微前端落地指南
- BAT開源項目彙總
- 【第22題】理解 JS 模塊化
- 【深入vue】爲什麼Vue3.0不再使用defineProperty實現數據監聽?
- 拋棄jenkins,如何用node從零搭建自動化部署管理平臺
- 前端部署演化史
- 深入理解 ES6 Iterator
- 解讀HTTP/2與HTTP/3 的新特性(推薦)
關注我
掃一掃 關注我的公衆號【前端名獅】,更多精彩內容陪伴你!