搞懂__proto__與prototype

一,前言

對一個知識點是否徹底把握,最好的校驗方法就是可否用本身的語言將其表述出來。

原型與原型鏈一直是學習 JS 繞不過的知識點,其中protoprototype 最爲讓人頭疼,這裏簡單的寫下我本身的理解,從原型與原型鏈中拆解 protoprototype ,但願能對你們有所幫助。javascript

一,原型

1,定義java

在javascript中,函數能夠有屬性。 每一個函數都有一個特殊的屬性叫做原型(prototype)
注:prototype屬性是函數特有,對象沒有該屬性

原型(prototype) 是什麼東西呢,裏面又有哪些屬性呢?來,讓咱們拿個具體例子看下:面試

function fn() {};

console.dir(fn)

clipboard.png

這裏咱們能夠看出 原型(prototype) 是一個對象,對於當前例子來講,裏面有 constructorproto兩個屬性。那這個原型對象有什麼做用呢?來,讓咱們繼續往下看~ 函數

2,使用學習

如今咱們知道了原型是一個對象,那原型對象有什麼做用呢?實際上,原型是 ECMAScript 實現繼承的過程當中產生的一個概念。這裏咱們簡單拿 ES5 的對象來舉例子:this

function Person(name) {
    this.name = name;
}

Person.prototype.say = function() {
    console.log(`My name is ${this.name}`);
}

let person = new Person('小明');
person.say();  // My name is 小明

讓咱們來逐步解釋下這個例子:spa

  1. 聲明瞭一個構造函數 Person,其有一個屬性 name
  2. 在其原型上聲明瞭一個函數 say
  3. 實例一個 Person 類——person
  4. 調用 person 的 say 方法

咱們發現,person能夠調用其構造函數原型裏的say方法,why?讓咱們看下 person 裏都有什麼:prototype

clipboard.png

實例 person 雖然自身沒有 say 方法,可是經過 proto 屬性訪問到了其原型中的 say 方法。code

爲何 proto 屬性會指向其構造函數的原型 prototype 呢,他們之間是什麼關係呢?讓咱們繼續往下看~對象

二,原型鏈

1,__proto__

在介紹原型鏈前,讓咱們先看一個屬性:proto,這是一個與原型 prototype 很類似的屬性,此次讓咱們來完全搞懂他們之間的關係。

讓咱們先來看MDN上的定義:

Object.prototype 的 proto 屬性是一個訪問器屬性(一個getter函數和一個setter函數), 暴露了經過它訪問的對象的內部[[Prototype]] (一個對象或 null)。
注:函數也是一種對象,因此其同時具備 prototype 與 __proto__ 兩個屬性

看不懂定義不要緊,讓咱們舉例說明:

let obj = {a: 1};

console.log(obj);

clipboard.png

咱們能夠看出 proto 指向了一個對象,這個對象是什麼呢?來,讓咱們繼續看

class Parent {
    constructor(name) {
        this.name = name;
    }
    
    print() {
        console.log(this.name);
    }
}

let parent = new Parent('小明');

console.dir(parent);
console.dir(Parent);

clipboard.png

clipboard.png

咦,有沒有發現 parent.__proto__ 與 Parent.prototype所指向的對象很類似,它們知否是同一個對象呢?

parent.__proto__ == Parent.prototype   // true

結果是同一個引用,這個時候咱們能夠得出一個結論:實例的__proto__屬性指向其構造函數的原型。那他們之間的這種關聯關係有什麼做用呢?這便涉及到了原型鏈。

2,原型鏈

JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不單單在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。
注:由於對象沒有prototyp屬性,因此經過__proto__屬性與原型prototype進行關聯

文字描述很抽象,讓咱們經過ES5的繼承來具體分析:

function Parent(name) {
    this.name = name;
}

Parent.prototype.print = function() {
    console.log(this.name);
}

function Child(name) {
    Parent.call(this, name);
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

let child = new Child('小明');
console.log(child);

讓咱們來看下 child 裏都有什麼

clipboard.png

這裏可能有的同窗對Object.create()這個函數不太熟悉,不明白其作了什麼,不要急,讓咱們來看下其源碼實現:

function create(proto) {
    function F();
    F.prototype = proto;
    return new F();
}

聲明瞭一個構造函數F,而後將其原型指向參數proto,返回構造函數的實例。好,讓咱們將整個過程串起來看一下:

1,聲明 Parent 構造函數
2,聲明 Child 構造函數,並手動綁定 this 指向
3,執行 Object.create(Parent.prototype):聲明一個構造函數 F,更改其原型prototype指向(F.prototype = Parent.prototype),而後返回 F 的實例 f,注意這一步,其實是 f.__proto__ == F.prototype
4,將 Child 的 prototype 指向 f.prototype
5,綁定 Child 的構造函數

讓咱們再來看下 child.print() 的調用過程

1,child對象裏沒有 print 函數,因而便在其原型上尋找:child.__proto__  ——>  f.prototype
2,進入 f.prototype 中尋找 print 函數,發現沒有,因而去其原型上尋找:f.__proto__   ——>  F.prototype
3,F.prototype == Parent.prototype,因而便進入 Parent.prototype 中尋找 print 函數,有 print 函數,調用成功

怎麼樣,是否是豁然開朗!原型鏈其實就是經過 proto 與 prototype 的關聯關係鏈接起來的,這樣對象即可以尋找其原型上的方法與屬性。詳細的關係描述以下圖:

圖片描述

實戰

讓咱們來看一道面試題:

var F = function() {};

Object.prototype.a = function() {
  console.log('a');
};

Function.prototype.b = function() {
  console.log('b');
}

var f = new F();

f.a();
f.b();

F.a();
F.b();

解題思路以下:

1. f.a() ——> 實例 f 調用 a 方法,自身沒有,從其原型中查找:f.__proto__ == F.prototype
 2. F.prototype中沒有 a 方法,因而繼續在其原型中查找,F.prototype.__proto__ == Object.prototype
 3. Object.prototype中有a方法,無b方法,因此f.a()結果爲a,f.b()調用會報錯:f.b is not a function
 4. F.a():構造函數調用 a 方法,自身沒有,從其原型中查找:F.__proto__ == Funtion.prototype
 5. Funtion.prototype中有 b 方法,因此F.b()的輸出爲b。沒有 a 方法,繼續在其原型中查找,Function.prototype.__proto__ == Object.prototype
 6. Object.prototype中有 a 方法,因此F.a()的輸出爲a

三,總結

本文對__proto__與prototype之間的關係進行了簡單的梳理,寫下了筆者本身的理解,給你們理解提供一個思路,固然可能存在描述不許確或錯誤的地方,歡迎你們留言交流,以上~

相關文章
相關標籤/搜索