JavaScript 中的原型一直是我很害怕的一個主題,理由很簡單,由於真的很差理解,但它確實是 JavaScript 中很重要的一部分,並且是面試的必考題,就算如今不懂,之後早晚有一天要把它弄懂,否則的話永遠都沒辦法把本身的技術能力往上提升一個層次,因此今天就來說講 JavaScript 中的原型。javascript
本文是這系列的第四篇,往期文章:java
首先要說一下爲何會有原型這個東西,那是由於在 JavaScript 中並無 「類」 的概念,它是靠原型和原型鏈實現對象屬性的繼承,即使在 ES6 中新出了class
的語法,但那也只是一個語法糖,它的底層依然是原型。面試
要理解原型(原型鏈),最重要的是理解兩個屬性以及它們之間的關係:瀏覽器
__proto__
prototype
__proto__
JavaScript中,萬物皆對象,全部的對象都有__proto__
屬性(null
和undefined
除外),並且指向創造這個對象的函數對象的prototype
屬性。閉包
var obj = {};
console.log( obj.__proto__ === Object.prototype ); // true
var arr = [];
console.log( arr.__proto__ === Array.prototype ); // true
var fn = function(){};
console.log( fn.__proto__ === Function.prototype ); // true
var str = "";
console.log( str.__proto__ === String.prototype ); // true
var num = 1;
console.log( num.__proto__ === Number.prototype ); // true
複製代碼
前面說了,在 JavaScript 中,一切皆對象(能夠理解爲它們都是從對象那裏繼承過來的),因此:函數
console.log( Function.prototype.__proto__ === Object.prototype ); // true
console.log( Array.prototype.__proto__ === Object.prototype ); // true
console.log( String.prototype.__proto__ === Object.prototype ); // true
複製代碼
而由於Object.prototype
的__proto__
已是終點了,因此它的指向是:post
console.log( Object.prototype.__proto__ === null ); // true
複製代碼
注意,雖然大多數瀏覽器都支持經過__proto__
來訪問,但它並非ECMAScript
的標準,在 ES5 中能夠經過Object.getPrototypeOf()
來獲取這個屬性。ui
var obj = {};
console.log( Object.getPrototypeOf(obj) === Object.prototype ); // true
複製代碼
prototype
prototype
是每一個函數對象都具備的屬性(它也有__proto__
,由於函數也是對象),實例化建立出來的對象會共享此prototype
裏的屬性和方法(經過__proto__
)。this
在上面的例子中已經看到過prototype
的身影,下面經過一個例子來說述它的做用。spa
如今咱們有一個構造函數Person
,而且對它進行實例化:
function Person(name){
this.name = name;
this.sayName = function(){
console.log("個人名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小紅");
a.sayName(); // 個人名字是:小明
b.sayName(); // 個人名字是:小紅
複製代碼
可是,用構造函數生成實例對象,有一個缺點,那就是沒法共享屬性和方法。
例如上面例子中的a
和b
,它們都有sayName
方法,雖然作的事相同,但它們倒是獨立的,這就會形成極大的資源浪費,由於每個實例對象,都有本身的屬性和方法的副本。
考慮到這一點,Brendan Eich 決定爲構造函數設置一個prototype
屬性。
這個屬性包含一個對象,全部實例對象須要共享的屬性和方法,都放在這個對象裏面,而不須要共享屬性和方法,就放在構造函數裏面,這個對象就是prototype
對象。
實例對象一旦建立,將自動引用prototype
對象的屬性和方法。也就是說,實例對象的屬性和方法,分紅兩種,一種是本地的,另外一種是引用的。
如今對上面的例子進行改寫:
function Person(name){
this.name = name;
}
Person.prototype = {
sayName : function(){
console.log("個人名字是:" + this.name);
}
}
var a = new Person("小明");
var b = new Person("小紅");
a.sayName() // 個人名字是:小明
b.sayName() // 個人名字是:小紅
複製代碼
如今不管Person
被實例化多少次,它的實例對象都共享同一個sayName
方法,這就是prototype
最大的用處。
講原型一個不可避免的概念就是原型鏈,原型鏈是經過__proto__
來實現的。
如今咱們以Person
的例子來說整個原型鏈。
var a = new Person("小明");
// 實例化對象的 __proto__ 指針指向構造函數的原型
console.log( a.__proto__ === Person.prototype )
// 構造函數的原型是一個對象,它的 __proto__ 指向對象的原型
console.log( Person.prototype.__proto__ === Object.prototype )
// 函數也是一個對象,它的 __proto__ 指向 函數的原型
console.log( Person.__proto__ === Function.prototype )
// 函數的原型是一個對象,它的 __proto__ 指向對象的原型
console.log( Function.prototype.__proto__ === Object.prototype )
// 對象的原型的__proto__ 指向 null
console.log( Object.prototype.__proto__ === null )
複製代碼
以上就是a
對象的整個原型鏈。
當訪問一個對象的屬性時,Javascript 會從對象自己開始往上遍歷整個原型鏈,直到找到對應屬性爲止。若是此時到達了原型鏈的頂部,也就是上例中的 Object.prototype
,仍然未發現須要查找的屬性,那麼 Javascript 就會返回 undefined
值。
注:此文爲原創文章,如需轉載,請註明出處。