對於原型咱們經過[[prototype]]
、proto 以及 prototype 這三個概念理解其實現繼承的思路。數組
在 ECMAScript 標準中規定每一個對象都有一個內置的屬性[[prototype]]
,它指向一個對象的原型對象。當查找一個對象的屬性或方法時,若是在當前對象找不到,則在其原型對象上尋找,若是原型對象上也沒有,則在原型對象的原型對象上尋找,如此繼續直到一個對象的原型對象爲 null(null 沒有原型)。能夠看到,這樣一種層層向上的查找是一種鏈式查找,在每一層上的對象都有一個指向其原型對象的連接([[prototype]]
),由這些連接組成的整個鏈條就叫作原型鏈。瀏覽器
如圖 1 所示,原型鏈查找的思路大體爲:bash
__proto__
([[prototype]]
)中查找,此時__proto__
指向 object2。__proto__
中查找,若是沒有則繼續向上查找。__proto__
爲 null,則再也不查找。說明: 圖中 builts-in 爲構建內置函數好比 toString()、valueOf 等。函數
前述中的[[Prototype]]
是一個內置屬性,咱們並不能直接獲取,爲了操做屬性的便利性不少瀏覽器都實現了 Object.prototype.__proto__
,所以能夠經過 obj.__proto__
來訪問對象的原型對象[[Prototype]]
,因此__proto__
和[[Prototype]]
本質上是一個東西,都指向一個對象的原型對象。 另外一方面,設置[[Prototype]]
是一個緩慢的操做,影響性能,所以使用 __proto__
是有爭議的,更推薦使用 Object.getPrototypeOf 和 Object.setPrototypeOf 來訪問原型對象(儘管如此,若是性能是個問題,也應儘可能避免使用)。性能
prototype 是構造函數(一個擁有 [[Construct]]
內部方法的對象)纔有的屬性,好比函數(非箭頭函數),實例對象是沒有這個屬性的。這個所謂的 prototype,其實能夠認爲是構造函數內部一個普通的對象(或者說指向一個普通對象),只是很不幸地也叫作 prototype(原型對象)而已,當構造函數執行時,會自動將構造函數的 prototype 賦值給 __proto__
(構造函數內部沒有顯示返回對象的狀況下),這樣在新的實例上經過原型鏈就能夠共享構造函數 prototype 及其原型鏈上的屬性了。prototype 和前述的__proto__
、[[Prototype]]
是徹底不一樣的概念,咱們一般的混淆,主要就來自於用原型對象一詞來指代了不一樣的它們。ui
來看下面的例子: 函數 Animal 經過 new 實例化的對象可以訪問到函數 prototype 屬性的 food 和 eat,這是如何作到的呢?this
var Animal = function(name) {
this.name = name;
};
Animal.prototype.food = 'meat';
Animal.prototype.eat = function() {
console.log(this.name + ' eat ' + this.food);
};
var panda = new Animal('panda');
var dog = new Animal('dog');
console.log(panda.eat()); // panda eat meat
console.log(dog.eat()); // dog eat meat
console.log(panda.__proto__=== Animal.prototype); // true
複製代碼
以下圖所示,實例對象 panda 和 dog 之因此可以訪問 Animal 原型上的 food 和 eat 屬性是由於在調用構造函數時 Animal 的 prototype 對象賦值給了實例對象的 __proto__
屬性,實例對象在訪問本身的方法(panda.eat)時,若是沒有找到,則在__proto__
對象中尋找,而這個對象正好是 Animal 的 prototype 對象,它擁有 eat 方法,因此能夠成功訪問 eat 方法。spa
來看另外一個例子: 以下將函數 Fish 的 prototype 賦值爲 Animal,以此,經過 fish 的實例來訪問 Animal 原型 prototype 上的方法,可結果是 Uncaught TypeError: nimo.eat is not a function,爲何會這樣呢?之因此會出現這樣的錯誤,是由於咱們錯誤的把原型對象(__proto__
)當原型對象(prototype)。前述咱們已經知道繼承是經過原型鏈來實現的,而原型鏈又是經過 __proto__
來串聯的。當函數 Fish 的 prototype 賦值爲 Animal 後,生成的實例對象 nimo 的 __proto__
爲 Animal,因此訪問 nimo.eat 會先在 Animal 上尋找 eat 方法,如圖 3,Animal 函數並無 eat 方法,從而經過 Animal 的__proto__
繼續向上尋找,直到頂層對象 Object,結果仍是沒有,所以報錯。prototype
var Animal = function(name) {
this.name = name;
};
Animal.prototype.food = 'meat';
Animal.prototype.eat = function() {
console.log('I can eat' + this.food);
};
var Fish = function(name) {
this.name = name;
};
Fish.prototype = Animal;
var nimo = new Fish('nimo');
console.log(nimo.eat()); // Uncaught TypeError: nimo.eat is not a function
複製代碼
語法結構建立對象code
對象字面量
經過對象字面量建立的對象其原型鏈爲 obj --> Object.prototype --> null
var obj = { a: 1 };
複製代碼
數組字面量
經過數組字面量建立的對象其原型鏈爲 arr --> Array.prototype --> Object.prototype --> null
var arr = [1, 2];
複製代碼
函數字面量
經過函數字面量建立的對象其原型鏈爲 f --> Function.prototype --> Object.prototype --> null
function f(){ console.log('func');}
複製代碼
構造器建立對象
經過構造函數建立的對象其原型鏈爲 instance --> func.prototype --> Object.prototype --> null
var Animal = function(name) {
this.name = name;
};
Animal.prototype.food = 'meat';
Animal.prototype.eat = function() {
console.log('I can eat' + this.food);
};
//實例對象panda的__proto__指向Animal.prototype
var panda = new Animal('panda');
複製代碼
Object.create 建立對象
在 ES5 中引入了一個新的方法來建立對象,就是 Object.create,新對象的原型就是該方法傳入的第一個參數。
var a = { x: 1 };
// a --> Object.prototype --> null
var b = Object.create(a);
// b --> a --> Object.prototype --> null
console.log(b.__proto__ === a); // true
console.log(b.x); // 1
var c = Object.create(b);
// c --> b --> a --> Object.prototype --> null
console.log(c.__proto__ === b); // true
複製代碼
__proto__
指向的對象)。[[Prototype]]
爲一個對象的指向原型對象的內置屬性,不能直接訪問。__proto__
爲一個非標準的,只是爲了方便訪問原型對象而實現的一個屬性,它和[[Prototype]]
本質上同樣都 指向原型對象,是全部對象都有的屬性。[[construct]]
內部方法的對象纔有的屬性,它自己只是一個普通對象,只是正好叫作原型對象,它的做用是在構造函數生成新的實例時將這個所謂的原型對象賦值給實例的 __proto__
屬性,這樣新的實例就能夠經過 __proto__
來繼承構造函數原型裏的方法。能夠看到,prototype 和 __proto__
所指的原型對象是徹底不一樣的概念。