歡迎光臨
每天分享高質量文章

圖解Javascript原型鏈

作者:Yusheng 的部落格

網址:http://blog.rainy.im/2015/07/20/prototype-chain-in-js/

本文嘗試闡述Js中原型(prototype)、原型鏈(prototype chain)等概念及其作用機制。上一篇文章(圖解Javascript背景關係與作用域)介紹了Js中變數作用域的相關概念,實際上關註的一個核心問題是:“在執行當前這行程式碼時Js直譯器可以獲取哪些變數”,而原型與原型鏈實際上還是關於這一問題。

我們知道,在Js中一切皆為物件(Object),但是Js中並沒有類(class);Js是基於原型(prototype-based)來實現的面向物件(OOP)的程式設計正規化的,但並不是所有的物件都擁有 prototype 這一屬性:

var a = {};

console.log(a.prototype); //=> undefined

var b = function(){};

console.log(b.prototype); //=> {}

var c = ‘Hello’;

console.log(c.prototype); //=> undefined

prototype 是每個 function 定義時自帶的屬性,但是Js中 function 本身也是物件,我們先來看一下下麵幾個概念的差別:

1. function 、 Function 、 Object 和 {}

function 是Js的一個關鍵詞,用於定義函式型別的變數,有兩種語法形式:

function f1(){

console.log(‘This is function f1!’);

}

typeof(f1); //=> ‘function’

var f2 = function(){

console.log(‘This is function f2!’);

}

typeof(f2); //=> ‘function’

如果用更加面向物件的方法來定義函式,可以用 Function :

var f3 = new Function(“console.log(‘This is function f3!’);”);

f3(); //=> ‘This is function f3!’

typeof(f3); //=> ‘function’

typeof(Function); //=> ‘function’

實際上 Function 就是一個用於建構式型別變數的類,或者說是函式型別實體的建構式(constructor);與之相似有的 Object 或 String 、 Number 等,都是Js內建型別實體的建構式。比較特殊的是 Object ,它用於生成物件型別,其簡寫形式為 {} :

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 是每一個函式型別自帶的兩個屬性,而其它非函式型別並沒有(開頭的例子已經說明),這一點之所以比較容易被忽略或誤解,是因為所有型別的建構式本身也是函式,所以它們自帶了 prototype 屬性:

// Node

console.log(Object.prototype); //=> {}

console.log(Function.prototype);//=> [Function: Empty]

console.log(String.prototype); //=> [String: ”]

除了 prototype 之外,Js中的所有物件( undefined 、 null 等特殊情況除外)都有一個內建的 [[Prototype]] 屬性,指向它“父類”的 prototype ,這個內建屬性在ECMA標準中並沒有給出明確的獲取方式,但是許多Js的實現(如Node、大部分瀏覽器等)都提供了一個 __proto__ 屬性來指代這一 [[Prototype]] ,我們透過下麵的例子來說明實體中的 __proto__ 是如何指向建構式的 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 是一個函式型別的變數,因此自帶了 prototype 屬性, prototype 屬性中的 constructor 又指向 Person 本身;透過 new 關鍵字生成的 Person 類的實體 p1 ,透過 __proto__ 屬性指向了 Person 的原型。這裡的 __proto__ 只是為了說明實體 p1 在內部實現的時候與父類之間存在的關聯(指向父類的原型),在實際操作過程中實體可以直接透過.獲取父類原型中的屬性,從而實現了繼承的功能。

3. 原型鏈

清楚了 prototype 與 __proto__ 的概念與關係之後我們會對“Js中一切皆為物件”這句話有更加深刻的理解。進而我們會想到,既然 __proto__ 是(幾乎)所有物件都內建的屬性,而且指向父類的原型,那是不是意味著我們可以“逆流而上”一直找到源頭呢?我們來看下麵的例子:

// 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 物件也有 __proto__ 屬性,向上追溯一直到 null 。

new 關鍵詞的作用就是完成上圖所示實體與父類原型之間關係的串接,並建立一個新的物件; instanceof 關鍵詞的作用也可以從上圖中看出,實際上就是判斷 __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

參考

  1. JavaScript constructors, prototypes, and the new keyword
  2. Javascript 面向物件程式設計
  3. Professional JavaScript for Web Developers

贊(0)

分享創造快樂