對一個知識點是否徹底把握,最好的校驗方法就是可否用本身的語言將其表述出來。
原型與原型鏈一直是學習 JS 繞不過的知識點,其中proto 與 prototype 最爲讓人頭疼,這裏簡單的寫下我本身的理解,從原型與原型鏈中拆解 proto 與 prototype ,但願能對你們有所幫助。javascript
1,定義java
在javascript中,函數能夠有屬性。 每一個函數都有一個特殊的屬性叫做原型(prototype)
注:prototype屬性是函數特有,對象沒有該屬性
原型(prototype) 是什麼東西呢,裏面又有哪些屬性呢?來,讓咱們拿個具體例子看下:面試
function fn() {}; console.dir(fn)
這裏咱們能夠看出 原型(prototype) 是一個對象,對於當前例子來講,裏面有 constructor 與 proto兩個屬性。那這個原型對象有什麼做用呢?來,讓咱們繼續往下看~ 函數
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
咱們發現,person能夠調用其構造函數原型裏的say方法,why?讓咱們看下 person 裏都有什麼:prototype
實例 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);
咱們能夠看出 proto 指向了一個對象,這個對象是什麼呢?來,讓咱們繼續看
class Parent { constructor(name) { this.name = name; } print() { console.log(this.name); } } let parent = new Parent('小明'); console.dir(parent); console.dir(Parent);
咦,有沒有發現 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 裏都有什麼
這裏可能有的同窗對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之間的關係進行了簡單的梳理,寫下了筆者本身的理解,給你們理解提供一個思路,固然可能存在描述不許確或錯誤的地方,歡迎你們留言交流,以上~