深刻淺出JS原型鏈

前言

原型鏈做爲js中的一座大山,算是面試過程當中的必問之一;理解好原型鏈,對於學習js也有很大的幫助。面試

定義

咱們先來看看它的定義瀏覽器

當js在一個對象上查找屬性的時候,首先查找對象自己的屬性(即聲明時候定義的屬性),若是在這些屬性中沒有找到目標屬性,那麼js會找它的 __proto__對象,若是這個對象沒有,那麼繼續找 __proto__對象的 __proto__對象,直到 __proto__null或者找到目標屬性爲止...... 這個__proto__的關係鏈,就是咱們說的原型鏈

特性

其實原型鏈真的很簡單,你只要記住下面這三個點就完了。函數

  • 每個對象都有__proto__屬性
  • 每個函數都有prototype屬性,這個屬性值也是一個對象
  • 每個對象的__protp__都會指向它的構造函數的prototype

實踐

俗話說實踐出真知,咱們一步步來驗證這幾個特性.學習

驗證第一點

你們能夠分段複製個人代碼粘貼到瀏覽器控制檯中this

let obj = {name:"leelei"};
console.log(obj.__proto__);

let arr = [1,2,3]
console.log(arr.__proto__);

let str = "http://www.leelei.info";
console.log(str.__proto__);

let num = 100;
console.log(num.__proto__);

當你所有打印完之後,你會發現一個問題,誒,個人str和num不是基礎類型嗎?不是對象也有proto屬性?prototype

這裏咱們要提一下js的隱式轉化規則了,當你訪問基礎類型的一些屬性或者調用一些方法的時候,你的基礎類型會被js給包裝起來,就像下面這樣code

str.__proto__ => new String(str).__proto__;
num.__proto__ => new Number(num).__proto__;

那麼這個new Stringnew Number哪裏來的噶?對象

它們都是js原生的構造函數,總共有如下幾種原型鏈

  • Object
  • Number
  • String
  • Boolean
  • RegExp
  • Error
  • Function
  • Array
  • Date
咱們如今已經驗證了第一點了,每一個對象(基礎類型是經過原生構造函數轉成了對象)都有__proto__屬性

驗證第二點

請一行一行復制如下代碼到控制檯中get

console.log(Object.prototype);
console.log(Number.prototype);
console.log(String.prototype);
console.log(Boolean.prototype);
console.log(RegExp.prototype);
console.log(Error.prototype);
console.log(Function.prototype);
console.log(Array.prototype);
console.log(Date.prototype);

若是咱們不是構造函數呢

function fn(){}
console.log(fn.prototype);

能夠看到,不管是構造函數,仍是咱們聲明的函數都有prototype的屬性。
區別只是原生的構造函數他們的prototype對象上已經自帶了一些方法和屬性。

那麼綜上咱們能夠驗證第二個特性,每個函數都有 prototype屬性

驗證第三點

咱們只要判斷下第一步的__proto__是否指向第二步對應構造函數的prototype便可。

let obj = {name:"leelei"};
obj.__proto__ === Object.prototype; //true

let arr = [1,2,3]
arr.__proto__ === Array.prototype; //true

let str = "http://www.leelei.info";
str.__proto__ === String.prototype; //true

let num = 100;
num.__proto__ === Number.prototype; //true
結果已經顯而易見了,第三個論點也可獲得論證

從定義出發

咱們根據定義來感覺一下這個鏈條

let obj = {name:"leelei"};

console.log(obj.__proto.valueOf); //有這個方法嗷

obj.valueOf(); //{name:"leelei"};

咱們能夠看到,在咱們聲明obj的時候並無這個屬性方法,可是咱們卻能夠調用,咱們打印了obj的__proto__對象,發現這裏存在這個方法,因此能夠調用。

這裏就是定義所講的當對象自己沒有這個屬性的時候,咱們會去它的__proto__對象上找

這裏才只有一層噶?若是隔多幾層會不會調用不了了阿?
咱們能夠編寫一個構造函數,而後給它的原型對象prototype添加自定義方法來試驗。

function Foo(age){this.age = age};
Foo.prototype.say = function(){console.log('hello')};

let foo = new Foo(18);

根據特性2,對象的__proto__對象指向構造函數的prototype,因此這樣是沒什麼問題的

foo.say()
=> foo.__proto__.say()  //hello
至關於=> Foo.prototype.say()  //hello

那咱們還能不能使用Object上面的方法呢?固然能夠

foo.valueOf()
=> foo.__proto__.valueOf 
至關於=> Foo.prototype.valueOf 怎麼沒有嗷?

//第一層找不到喔,那就往上一層
//不要忘了prototype也是對象!
//因此它也有__proto__屬性

==> Foo.prototype.__proto__.valueOf 
至關於==> Object.prototype.valueOf 找到啦

你可能會有點好奇爲何這兩個會等於?
Foo.prototype.__proto__ === Object.prototype

由於Foo.prototype是對象,Foo是函數

Foo.__proto__ === Function.prototype;

Foo.prototype.__proto__ === Object.prototype;

一點須要注意的

typeof (Function.prototype); //function

這個竟然不是對象?!驚了
根據特性2,每一個函數都有一個prototype屬性。
既然Function.prototype不是對象而是方法,那麼它也有prototype屬性纔對

Function.prototype.prototype //undefined

驚了!它竟然沒有prototype屬性!

莫慌,除這個以外,都是適用的。記住這個特例便可。

總結

其實原型鏈並不複雜,可是單純簡單記憶的話確實比較難記,咱們能夠本身在控制檯多搗鼓一下,按照它的定義來走,更方便咱們理解和記憶。
謝謝你們,若是有錯誤,請在評論區指出。
打個廣告,李雷的博客

相關文章
相關標籤/搜索