關於原型和原型鏈的介紹,網上數不勝數,但能講清楚這兩個概念的不多,大多數都是介紹各類對象、屬性之間如何指來指去,最後的結果就是箭頭滿天飛,大腦一團糟。本文將從這兩個概念的命名入手,用通俗易懂的語言,幫助你理解這兩個東西究竟是何方神聖。前端
JavaScript和Java、C++等傳統面向對象的編程語言不一樣,它是沒有類(class)的概念的(ES6 中的class也只不過是語法糖,並不是真正意義上的類),而在JavaScript中,一切皆是對象(object)。在基於類的傳統面向對象的編程語言中,對象由類實例化而來,實例化的過程當中,類的屬性和方法會拷貝到這個對象中;對象的繼承其實是類的繼承,在定義子類繼承於父類時,子類會將父類的屬性和方法拷貝到自身當中。所以,這類語言中,對象建立和繼承行爲都是經過拷貝完成的。但在JavaScript中,對象的建立、對象的繼承(更好的叫法是對象的代理,由於它並非傳統意義上的繼承)是不存在拷貝行爲的。如今讓咱們忘掉類、忘掉繼承,這一切都不屬於JavaScript。編程
其實,原型這個名字自己就很容易產生誤解,原型在百度詞條中的釋義是:指原來的類型或模型。按照這個定義解釋的話,對象的原型是對象建立自身的模子,模子具有的特色對象都要具備,這儼然就是拷貝的概念。咱們已經說過, JavaScript的對象建立不存在拷貝,對象的原型實際上也是一個對象,它和對象自己是徹底獨立的兩個對象。既然如此,原型存在的意義又是什麼呢?原型是爲了共享多個對象之間的一些共有特性(屬性或方法),這個功能也是任何一門面向對象的編程語言必須具有的。A、B兩個對象的原型相同,那麼它們必然有一些相同的特徵。bash
JavaScript中的對象,都有一個內置屬性[[Prototype]]
,指向這個對象的原型對象。當查找一個屬性或方法時,若是在當前對象中找不到定義,會繼續在當前對象的原型對象中查找;若是原型對象中依然沒有找到,會繼續在原型對象的原型中查找(原型也是對象,也有它本身的原型);如此繼續,直到找到爲止,或者查找到最頂層的原型對象中也沒有找到,就結束查找,返回undefined。能夠看出,這個查找過程是一個鏈式的查找,每一個對象都有一個到它自身原型對象的連接,這些連接組件的整個鏈條就是原型鏈。擁有相同原型的多個對象,他們的共同特徵正是經過這種查找模式體現出來的。編程語言
在上面的查找過程,咱們提到了最頂層的原型對象,這個對象就是Object.prototype
,這個對象中保存了最經常使用的方法,如toString
、valueOf
、hasOwnProperty
等,所以咱們才能在任何對象中使用這些方法。函數
當經過字面量方式建立對象時,它的原型就是Object.prototype
。雖然咱們沒法直接訪問內置屬性[[Prototype]]
,但咱們能夠經過Object.getPrototypeOf()
或對象的__proto__
獲取對象的原型。ui
var obj = {};
Object.getPrototypeOf(obj) === Object.prototype; // true
obj.__proto__ === Object.prototype; // true
複製代碼
經過函數的構造調用(注意,咱們不把它叫作構造函數,由於JavaScript中一樣沒有構造函數的概念,全部的函數都是平等的,只不過用來建立對象時,函數的調用方式不一樣而已)也是一種經常使用的建立對象的方式。基於同一個函數建立出來的對象,理應能夠共享一些相同的屬性或方法,但這些屬性或方法若是放在Object.prototype裏,那麼全部的對象均可以使用它們了,做用域太大,顯然不合適。因而,JavaScript在定義一個函數時,同時爲這個函數定義了一個 默認的prototype屬性,全部共享的屬性或方法,都放到這個屬性所指向的對象中。由此看出,經過一個函數的構造調用建立的對象,它的原型就是這個函數的prototype指向的對象。this
var f = function(name) { this.name = name };
f.prototype.getName = function() { return this.name; } //在prototype下存放全部對象的共享方法
var obj = new f('JavaScript');
obj.getName(); // JavaScript
obj.__proto__ === f.prototype; // true
複製代碼
第三種經常使用的建立對象的方式是使用Object.create()
。這個方法會以你傳入的對象做爲建立出來的對象的原型。spa
var obj = {};
var obj2 = Object.create(obj);
obj2.__proto__ === obj; // true
複製代碼
這種方式還能夠模擬對象的「繼承」行爲。prototype
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name,label) {
Foo.call( this, name ); //
this.label = label;
}
// temp對象的原型是Foo.prototype
var temp = Object.create( Foo.prototype );
// 經過new Bar() 建立的對象,其原型是temp, 而temp的原型是Foo.prototype,
// 從而兩個原型對象Bar.prototype和Foo.prototype 有了"繼承"關係
Bar.prototype = temp;
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
a.__proto__.__proto__ === Foo.prototype; //true
複製代碼
__proto__
和prototype這是容易混淆的兩個屬性。__proto__
指向當前對象的原型,prototype是函數才具備的屬性,默認狀況下,new 一個函數建立出的對象,其原型都指向這個函數的prototype屬性。代理
1.對於JavaScript中的內置對象,如String、Number、Array、Object、Function等,由於他們是native代碼實現的,他們的原型打印出來都是ƒ () { [native code] }
。
2.內置對象本質上也是函數,因此能夠經過他們建立對象,建立出的對象的原型指向對應內置對象的prototype屬性,最頂層的原型對象依然指向Object.prototype。
'abc'.__proto__ === String.prototype; // true
new String('abc').__proto__ === String.prototype; //true
new Number(1).__proto__ ==== Number.prototype; // true
[1,2,3].__proto__ === Array.prototype; // true
new Array(1,2,3).__proto__ === Array.prototype; // true
({}).__proto__ === Object.prototype; // true
new Object({}).__proto__ === Object.prototype; // true
var f = function() {};
f.__proto__ === Function.prototype; // true
var f = new Function('{}');
f.__proto__ === Function.prototype; // true
複製代碼
3.Object.create(null)
建立出的對象,不存在原型。
var a = Object.create(null);
a.__proto__; // undefined
複製代碼
此外,函數的prototype中還有一個constructor方法,建議你們就當它不存在,它的存在讓JavaScript原型的概念變得更加混亂,並且這個方法也幾乎沒有做用。
歡迎關注個人公衆號:老幹部的大前端,領取21本大前端精選書籍!