【前端芝士樹】Javascript的原型與原型鏈

【前端芝士樹】Javascript的原型、原型鏈以及繼承機制

前端的面試中經常會遇到這個問題,自己也是一直似懂非懂,趁這個機會整理一下

0. 爲什麼會出現原型和原型鏈的概念

1994年,網景公司(Netscape)發佈了Navigator瀏覽器0.9版,但是剛開始的Js沒有繼承機制,更別提像同時期興盛的C++和Java這樣擁有面向對象的概念。在實際的開發過程中,工程師們發現沒有繼承機制很難解決一些問題,必須有一種機制能將所有的對象關聯起來。

Brendan Eich鑑於以上情況,但不想把Js設計得過爲複雜,於是引入了new關鍵詞constructor構造函數來簡化對象的設計,引入了prototype函數對象來包含所有實例對象的構造函數的屬性和方法,引入了proto原型鏈的概念解決繼承的問題。

1. Javscript 函數和函數對象

var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
凡是直接或者間接通過 new Function() 創建的對象都是函數對象,其他的都是普通對象。

在上面的程序中,o1、o2、o3都是普通對象,f1、f2、f3都是函數對象。

2. 構造函數

function Person(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');
person1person2 都是 構造函數 Person 的實例。
實例的構造函數屬性(constructor)指向構造函數。

3. 原型對象

Person.prototype = {
   name:  'Zaxlct',
   age: 28,
   job: 'Software Engineer',
   sayName: function() {
     alert(this.name);
   }
}

每個函數對象都有一個 prototype 屬性,這個屬性就是函數的原型對象

每個對象都有 proto 屬性,但只有函數對象纔有 prototype 屬性

clipboard.png

在默認情況下,所有的原型對象都會自動獲得一個 constructor(構造函數)屬性,這個屬性(是一個指針)指向 prototype 屬性所在的函數(Person)

Person.prototype.constructor == Person

而在Person這個對象進行實例化的時候,實際上是創建了一個它的實例對象並賦值給它的 prototype,所以得出以下結論:

原型對象(Person.prototype)是 構造函數(Person)的一個實例。

4. _proto_

JS 在創建對象(不論是普通對象還是函數對象)的時候,都有一個叫做__proto__ 的內置屬性,用於指向創建它的構造函數的原型對象,也就是prototype。

clipboard.png

Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

看下面一段代碼

Person.prototype.__proto__ === Object.prototype;

Person.prototype 是一個普通對象,我們無需關注它有哪些屬性,只要記住它是一個普通對象。
因爲一個 普通對象的構造函數 === Object
所以 Person.prototype.__proto__ === Object.prototype

5. 原型鏈

原型鏈的形成是真正是靠__proto__ 而非prototype。
person1.__proto__ === Person.prototype;
Person.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
//Object.prototype.__proto__ === null,保證原型鏈能夠正常結束。

Person.__proto__ === Function.prototype;
Object.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;

前面三項已經形成了一個原型鏈,那麼後面代碼中Person和Object的__proto__都是Function.prototype呢?

所有函數對象的proto都指向Function.prototype,它是一個空函數(Empty function)
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

Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

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
所有的構造器都來自於 Function.prototype,甚至包括根構造器Object及Function自身。所有構造器都繼承了·Function.prototype·的屬性及方法。如length、call、apply、bind

所以我們再舉一個原型鏈的例子

let num = new Number();
num.__proto__ === Number.prototype;
Number.prototype.__proto__ === Function.prototype;
Funtion.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;

也可看下圖的實現,更直觀。
clipboard.png

ok, 所以明白爲什麼Number、String、Array這樣的對象shi'li能繼承到Object的屬性以及原型鏈是怎麼一回事了吧。

參考文章
最詳盡的 JS 原型與原型鏈終極詳解,沒有「可能是」。
三張圖搞懂JavaScript的原型對象與原型鏈 - 水乙 - 博客園
Javascript繼承機制的設計思想 - 阮一峯的網絡日誌
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章