【2019經典面試題解答】看完秒懂原型和原型鏈!

幾個重要概念

__proto__(先後都是兩條槓)
prototype
constructor
普通對象
函數對象
構造器(構造函數)
實例(實例是個普通對象)
原型
原型對象

ps:第一次認真梳理知識點,有點淺顯,但願讀完你所收穫。若有錯誤,也望及時指出,感激涕零!
複製代碼

一. constructor / prototype 和__proto__

每一個對象都有__proto__ ,而prototype只有函數對象纔有
複製代碼

1. 有時候當前對象沒有咱們想用的方法,因此咱們就想看看它的原型上有木有,要怎麼樣才能獲取到當前對象的原型呢?

答:__proto__  

上圖中: obj.__proto__  === Object.prototype
複製代碼

首先 Object.prototype叫作obj的原型對象,這個對象裏包含了全部對外共享的屬性和方法。面試

舉個例子:數組

var function Person(name){
        this.name = name;
    }
    var p1 = new Person('林蛋大');
    
    console.log(p1.__proto__); //Person.prototype
    console.log(p1.__proto__ === Person.prototype); //true
複製代碼
其次,咱們在控制檯看到了很眼熟很扎眼的 constructor,它是什麼鬼?
複製代碼

2. constructor 被包含在大括號{}中,說明它是原型對象中的一個屬性。這個屬性是幹嗎的?請看如下代碼

如下寫法同 Person.prototype.constructor 直觀地用數學思惟理解:等號兩邊寫誰都同樣bash

console.log(p1.__proto__.constructor ); //Person
複製代碼

也就是說,原型對象裏有一個叫constructor的屬性,它指向本身的構造器(構造函數) 因此,通常用constructor屬性來獲取當前對象的構造函數app

納尼?這是什麼狀況?豈不是咱們有好多種獲取構造函數的方法?Nice!

以下:函數

p1.constructor === p1.__proto__.constructor
p1.constructor === Person.prototype.constructor

用數學思惟左右置換,互求,是能夠的

複製代碼

p1這個對象是Person的一個實例,由於它是Person new 出來,Person是它的構造器,因此它的constructor指向了Person;ui

另外,Person.prototype是一個對象,它的constructor也指向了Person,是否是能夠理解爲:Person.prototype 也是Person的一個實例?this

其實是真的能夠這樣理解的,我查閱了不少資料,雖然是僞相同,但網上大多數人都以爲這樣更容易理解原型和原型鏈

中場小結:

  1. 經過__proto__能夠獲取原型,一直往一層層獲取,一直__proto__直到結果爲null就是盡頭了。走完這個流程就是走完了你當前對象的原型鏈。(這一塊後面細講)編碼

  2. 經過constructor能夠獲取當前對象的構造函數spa

  3. 任何對象都有原型,任何對象均可能是別的對象的原型,那什麼叫原型?什麼叫原型對象?

通俗的理解----prototype

老虎和貓都同屬貓科,也就是說,老虎和貓都是「貓科」new出來的實例,「貓科」就是老虎和貓的原型。

原型上有個prototype屬性,這個屬性是個對象,裏邊有老虎和貓都繼承到的屬性和方法,好比毛色特徵,善攀緣及跳躍等等,這個對象就是原型對象。咱們平時說:在當前找不到的方法,去它的原型上找,其實就是去這個原型對象找。

綜上所述----

原型其實就是當前對象的構造器(構造函數),它上面有個prototype屬性,這個屬性是個對象,裏邊包含全部後代均可以繼承到的方法和屬性,這個prototype屬性叫作原型對象,咱們通常就是跟它打交道。

爲了避免混淆,文後核心會一直圍繞原型對象開展,而原型,我習慣用構造函數的稱呼。(到此,原型和原型對象的關係已經說明白了,若是看不懂請留言)

  1. 在實際編碼中會用到這三個屬性作繼承,這裏有個坑,以下:
function Person(name) {
    this.name = name
}
// 修改原型
Person.prototype.getName = function() {}
var p = new Person('林蛋大')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true


// 重寫原型
Person.prototype = {
    getName: function() {}
}
var p = new Person('楚中天')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false

why false????

複製代碼

這是由於給Person.prototype賦值的是一個對象直接量{getName: function(){}},使用對象直接量方式定義的對象,它的構造器(constructor)指向的是根構造器Object

因此這個時候p.constructor === Object爲true,它如今的構造器是個叫作Object的構造器(構造函數),而已經不是叫作Person的構造器了,你讓他們怎麼相等?這種繼承註定會失敗

那麼怎麼辦?這個時候要改很簡單,只要把constructor給指回來就行了,這是寫原型繼承的時候最最最重要的坑!

Person.prototype = {
    getName: function() {}
}
var p = new Person('jack')
p.constructor = Person; //就是這一行,核心重點
!
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
複製代碼

二. 原型鏈

上節說到__proto__能夠獲取原型對象,一直往上獲取就能拿到整條鏈子,怎麼個獲取法?口說不清咱們直接看代碼:

var function Person(name){
        this.name = name;
    }
var p1 = new Person('林蛋大');

//不打印的話,你知道如下會輸出什麼嗎?
1. p1.__proto__  ?
2. Person.__proto__  ?

複製代碼
  1. 確定能秒答:Person.prototype
  2. 也就是構造函數Person的原型,既然是函數,那麼確定是Function.prototype 此處你的腦子須要有一條鏈子的形象:

你先把這圖放腦子裏等着,咱們先來分析第2題,:

p1.__proto__ ? Person.prototype
p1.__proto__.__proto__?
複製代碼
  1. 此題要一步一步從左往右推導:從第一題得出p1__proto__ 就是Person.prototype
  2. 故此題所求的是Person.prototype的原型對象,而求原型對象的方法是__proto__
  3. Person.prototype是個對象,因此腦子裏應該能秒閃出來:Object.prototype

綜上所述,第2題要求的是p1的構造器的原型對象。 答案以下:

Person.prototype.__proto__  ?Object.prototype

騷問法:
p1.__proto__.__proto__ ?Object.prototype

變態問法:
p1.constructor.__proto__ ? Object.prototype
p1.__proto__.constructor.prototype.__proto__ ? Object.prototype
Person.prototype.constructor.prototype.__proto__ ? Object.prototype

暈了?請翻到上面兩個constructor的等式,左右置換一下:

p1.__proto__.constructor 就是Person
Person.prototype.constructor  就是Person

懂了吧?嘿嘿~

複製代碼

第2題分析完了,咱們來看看鏈子怎麼求。

咱們往上追溯原型鏈的目的,無非就是找些方法或者屬性來知足當前的coding須要,因此---

  1. 先找p1的上級,也就是它的構造器Person上有木有,而全部的屬性和方法都是固定放在prototype裏的,咱們一鍵到達原型對象裏翻找:
由 p1.__proto__  獲得 Person.prototype,發現構造器Person的集合裏沒有咱們想要的

複製代碼
  1. Person的集合裏沒有,咱們只好再往上找了,仍然使用一鍵到達原型集合的惟一方法__proto__
由 Person.__proto__  獲得 Function.prototype,發現構造器Function的集合裏仍是沒有

複製代碼
  1. 再往上找:
由 Function.__proto__  獲得 Object.prototype,發現構造器Object的集合裏仍是沒有

複製代碼
  1. 不要放棄,繼續追溯,堅持就是勝利:
由 Object.__proto__  獲得 null,納尼????

複製代碼

祖宗十八代找遍,都找到類人猿了,仍是沒有找到咱們要用的方法或者屬性,只有一個解釋:你要找的傢伙還沒有出世(不存在)

這個時候若是你強行調用這個方法或者使用這個屬性,運行後你就會獲得一串紅色大字:‘ XXX is undefined .....’

以上就是咱們本着解決實際須要的初衷,追溯原型鏈的過程。

打個比方就是p1的家族世代修煉,每一代都修煉出本身的獨門絕學,而後傳給後代,最後面的p1就繼承到了以上全部祖宗的絕學(屬性和方法)

在JS的世界裏你們都是分散的對象,總有個機制把他們鏈接起來才能去運轉去工做去產出,因此js做者設計了這一套繼承機制,這也就是所謂的原型鏈。

你能看到這裏,說明你是一位好學勤奮的同窗,給你一個萌萌噠鼓勵~

不過咱們仍是要繼續這個又臭又長的裹腳布啊

console.log(p1.arguments) // arguments 從哪裏來的?
console.log(Person.call(window)) // call 方法從哪裏來的?
複製代碼

相信你們也看過不少代碼,常常忽然冒出arguments,找遍全世界也沒有找到arguments是在哪裏聲明的。畢竟p1中並無arguments屬性,找到它的原型Person,上面也沒有,哪兒冒出來的?

按照上面的分析,只好往上追溯打印一下翻找,果真---

console.log(Function.prototype) // function() {} (一個空的函數)
console.log(Object.getOwnPropertyNames(Function.prototype)); 
/* 輸出
["length", "name", "arguments", "caller", "constructor", "bind", "toString", "call", "apply"]
*/
複製代碼

順利被咱們找出arguments和call,getOwnPropertyNames是頂級公民Object.prototype裏的屬性和方法,全部的對象都能繼承到它的屬性和方法,它的角色至關於老祖宗(初代)你們自行打印一下,它的方法和屬性很是多。

可是!Function.prototype竟然是個空函數,爲何?請看下一節

三. 特殊公民 Function.prototype

全部函數對象(注意區別普通對象)的__proto__都指向Function.prototype,也就是說它是全部函數對象的原型對象,並且它是一個空函數(Empty function)

先無論它是空函數什麼的,來看看下列JS世界中各種構造器的原型,你會很驚訝他們都是同一個:

Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true
 
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
 
String.__proto__ === Function.prototype  // true
String.constructor == Function //true
 
// 全部的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身
Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true
 
// 全部的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
 
Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true
 
RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true
 
Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true
 
Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

複製代碼

JavaScript中有內置(build-in)構造器/對象共計12個(ES5中新加了JSON),這裏列舉了可訪問的8個構造器。剩下的如Global是不能直接訪問的,Arguments僅在函數調用時由JS引擎建立;另外還有Math,JSON是以對象形式存在的,無需new,對象形式的__proto__是Object.prototype。

以下

Math.__proto__ === Object.prototype  // true
Math.construrctor == Object // true
 
JSON.__proto__ === Object.prototype  // true
JSON.construrctor == Object //true
複製代碼

上面說的函數對象固然包括自定義的。以下

// 函數聲明
function Person() {}
// 函數表達式
var Perosn = function() {}
console.log(Person.__proto__ === Function.prototype) // true
console.log(Man.__proto__ === Function.prototype)    // true
複製代碼

這些打印結果說明:全部的構造器(構造函數)都來自於 Function.prototype,甚至包括根構造器Object及Function自身。因此全部構造器都繼承了Function.prototype的屬性及方法。 故,函數是惟一一個typeof 結果是function 的類型。

那麼上一節的問題,說Function.prototype怎麼是個空函數啊?不止是它,若是你求Array構造器的原型,也是一個空數組:

console.log(Function.prototype) // function() {} (一個空的函數)
console.log(Array.prototype) // [ ] (一個空的數組)

複製代碼

怎麼辦?故技重施啊!它本身沒有,它的原型確定有鴨!

//首先 Function.prototype/Array.prototype是個對象

console.log(Function.prototype.__proto__  === Object.prototype ) //true
console.log(Array.prototype.__proto__  === Object.prototype ) //true

//Object.prototype的原型呢?null,到世界的盡頭了
console.log(Object.prototype.__proto__  === null )//true

複製代碼

到世界盡頭了?剛纔明明說object也是function 建立的,那麼Object的原型對象應該是Function.prototype 纔對啊!由於全部對象的原型,都指向本身的構造器的prototype屬性。

若是能這樣的話,會是什麼樣子?請看:

若是 Object.prototype.__proto__ === Function.prototype 爲true,
那麼繼續往上找 Function.prototype.__proto__ === Object.prototype 
再繼續往上 Object.prototype.__proto__ === Function.prototype
再再繼續往上  Function.prototype.__proto__ === Object.prototype  
.........

複製代碼

死循環,這條鏈子沒完沒了了????並且還沒多大意義!

因此設計者讓Object.prototype 指向null, 整條鏈子快樂結束。

四. 靈魂面試題

function Person(name) {
    this.name = name
}
var p2 = new Person('king');

核心點:__proto__是求原型對象的,也就是求構造器的prototype屬性 ===>原型對象是構造器的一個屬性,自己是個對象
    
constructor 是求構造器的 ====> 構造器的prototype屬性的對象集合裏也有constructor,這個prototype裏的constructor指向構造器本身

console.log(p2.__proto__)//Person.prototype
console.log(p2.__proto__.__proto__)//結合上題,也就是Person.prototype的__proto__,Person.prototype自己是個對象,因此這裏輸出:Object.prototype
console.log(p2.__proto__.__proto__.__proto__)//同理,這裏是求Object.prototype的__proto__,這裏輸出:null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null後面沒有了,報錯
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null後面沒有了,報錯

console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2是實例對象,不是函數對象,是沒有prototype屬性滴

console.log(Person.constructor)//Function 一個空函數
console.log(Person.prototype)//打印出Person.prototype這個對象裏全部的方法和屬性

console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)//Person.prototype是對象,因此輸出:Object.prototype
console.log(Person.__proto__)//Function.prototype

console.log(Function.prototype.__proto__)//Object.prototype
console.log(Function.__proto__)//Function.prototype

console.log(Object.__proto__)//Function.prototype
console.log(Object.prototype.__proto__)//null

複製代碼
相關文章
相關標籤/搜索