圖解Javascript原型(prototype)鏈
本文嘗試闡述Js中原型(prototype)、原型鏈(prototype chain)等概念及其作用機(jī)制。
我們知道,在Js中一切皆為對(duì)象(Object),但是Js中并沒有類(class);Js是基于原型(prototype-based)來實(shí)現(xiàn)的面向?qū)ο?OOP)的編程范式的,但并不是所有的對(duì)象都擁有 prototype 這一屬性:
var a = {};
console.log(a.prototype); //=> undefined
var b = function(){};
console.log(b.prototype); //=> {}
var c = 'Hello';
console.log(c.prototype); //=> undefined
prototype 是每個(gè) function 定義時(shí)自帶的屬性,但是Js中 function 本身也是對(duì)象,我們先來看一下下面幾個(gè)概念的差別:
1. function 、 Function 、 Object 和 {}
function 是Js的一個(gè)關(guān)鍵詞,用于定義函數(shù)類型的變量,有兩種語法形式:
function f1(){
console.log('This is function f1!');
}
typeof(f1); //=> 'function'
var f2 = function(){
console.log('This is function f2!');
}
typeof(f2); //=> 'function'
如果用更加面向?qū)ο蟮姆椒▉矶x函數(shù),可以用 Function :
var f3 = new Function("console.log('This is function f3!');");
f3(); //=> 'This is function f3!'
typeof(f3); //=> 'function'
typeof(Function); //=> 'function'
實(shí)際上 Function 就是一個(gè)用于構(gòu)造函數(shù)類型變量的類,或者說是函數(shù)類型實(shí)例的構(gòu)造函數(shù)(constructor);與之相似有的 Object 或 String 、 Number 等,都是Js內(nèi)置類型實(shí)例的構(gòu)造函數(shù)。比較特殊的是 Object ,它用于生成對(duì)象類型,其簡寫形式為 {} :
var o1 = new Object();
typeof(o1); //=> 'object'
var o2 = {};
typeof(o2); //=> 'object'
typeof(Object); //=> 'function'
2. prototype VS __proto__
清楚了上面的概念之后再來看 prototype :
Each function has two properties: length and prototype
prototype 和 length 是每一個(gè)函數(shù)類型自帶的兩個(gè)屬性,而其它非函數(shù)類型并沒有(開頭的例子已經(jīng)說明),這一點(diǎn)之所以比較容易被忽略或誤解,是因?yàn)樗蓄愋偷臉?gòu)造函數(shù)本身也是函數(shù),所以它們自帶了 prototype 屬性:
// Node
console.log(Object.prototype); //=> {}
console.log(Function.prototype);//=> [Function: Empty]
console.log(String.prototype); //=> [String: '']
除了 prototype 之外,Js中的所有對(duì)象( undefined 、 null 等特殊情況除外)都有一個(gè)內(nèi)置的 [[Prototype]] 屬性,指向它“父類”的 prototype ,這個(gè)內(nèi)置屬性在ECMA標(biāo)準(zhǔn)中并沒有給出明確的獲取方式,但是許多Js的實(shí)現(xiàn)(如Node、大部分瀏覽器等)都提供了一個(gè) __proto__ 屬性來指代這一 [[Prototype]] ,我們通過下面的例子來說明實(shí)例中的 __proto__ 是如何指向構(gòu)造函數(shù)的 prototype的:
var Person = function(){};
Person.prototype.type = 'Person';
Person.prototype.maxAge = 100;
var p = new Person();
console.log(p.maxAge);
p.name = 'rainy';
Person.prototype.constructor === Person; //=> true
p.__proto__ === Person.prototype; //=> true
console.log(p.prototype); //=> undefined
上面的代碼示例可以用下圖解釋:
Person 是一個(gè)函數(shù)類型的變量,因此自帶了 prototype 屬性, prototype 屬性中的 constructor 又指向 Person 本身;通過 new 關(guān)鍵字生成的 Person 類的實(shí)例 p1 ,通過 __proto__ 屬性指向了 Person 的原型。這里的 __proto__ 只是為了說明實(shí)例 p1 在內(nèi)部實(shí)現(xiàn)的時(shí)候與父類之間存在的關(guān)聯(lián)(指向父類的原型),在實(shí)際操作過程中實(shí)例可以直接通過 . 獲取父類原型中的屬性,從而實(shí)現(xiàn)了繼承的功能。
3. 原型鏈
清楚了 prototype 與 __proto__ 的概念與關(guān)系之后我們會(huì)對(duì)“Js中一切皆為對(duì)象”這句話有更加深刻的理解。進(jìn)而我們會(huì)想到,既然 __proto__ 是(幾乎)所有對(duì)象都內(nèi)置的屬性,而且指向父類的原型,那是不是意味著我們可以“逆流而上”一直找到源頭呢?我們來看下面的例子:
// Node
var Obj = function(){};
var o = new Obj();
o.__proto__ === Obj.prototype; //=> true
o.__proto__.constructor === Obj; //=> true
Obj.__proto__ === Function.prototype; //=> true
Obj.__proto__.constructor === Function; //=> true
Function.__proto__ === Function.prototype; //=> true
Object.__proto__ === Object.prototype; //=> false
Object.__proto__ === Function.prototype; //=> true
Function.__proto__.constructor === Function;//=> true
Function.__proto__.__proto__; //=> {}
Function.__proto__.__proto__ === o.__proto__.__proto__; //=> true
o.__proto__.__proto__.__proto__ === null; //=> true
從上面的例子和圖解可以看出, prototype 對(duì)象也有 __proto__ 屬性,向上追溯一直到 null 。
new 關(guān)鍵詞的作用就是完成上圖所示實(shí)例與父類原型之間關(guān)系的串接,并創(chuàng)建一個(gè)新的對(duì)象; instanceof 關(guān)鍵詞的作用也可以從上圖中看出,實(shí)際上就是判斷__proto__ (以及 __proto__.__proto__ …)所指向是否父類的原型:
var Obj = function(){};
var o = new Obj();
o instanceof Obj; //=> true
o instanceof Object; //=> true
o instanceof Function; //=> false
o.__proto__ === Obj.prototype; //=> true
o.__proto__.__proto__ === Object.prototype; //=> true
o.__proto__.__proto__ === Function; //=> false