Menu

繼承與原型鏈

對於那些熟悉基於類的面向對象語言(java或者c++)的開發者來說,JavaScript的語法是比較怪異的, 這是由於javascript是一門動態語言,而且它沒有類的概念 (雖然class是個保留字,不能作為變量名來使用).

繼承方面,javascript中的每個對象都有一個內部私有的鏈接指向另一個對象 (或者為 null),這個對象就是原對象的原型. 這個原型也有自己的原型, 直到對象的原型為null為止. 這種一級一級的鏈結構就稱為原型鏈.

繼承屬性

javascript對象有兩種不同的屬性,一種是對象自身的屬性,另外一種是繼承於原型鏈上的屬性.下面的代碼演示了當訪問一個對象的屬性時,到底發生了什麼.

// 假定我們有個對象o,並且o所在的原型鏈如下:
// {a:1, b:2} ---> {b:3, c:4} ---> null
// 'a'和'b'是o自身的屬性.
// 該例中,用"對象.[[Prototype]]"來表示這個對象的原型.
// 這只是一個純粹的符號表示(ECMAScript標準中也這樣使用),不能在實際代碼中使用.
console.log(o.a); // 1
// a是o的自身屬性嗎?是的,該屬性的值為1
console.log(o.b); // 2
// b是o的自身屬性嗎?是的,該屬性的值為2
// o.[[Prototype]]上還有一個'b'屬性,但是它不會被訪問到.這種情況稱為"屬性遮蔽".
console.log(o.c); // 4
// c是o的自身屬性嗎?不是,那看看o.[[Prototype]]上有沒有.
// c是o.[[Prototype]]的自身屬性嗎?是的,該屬性的值為4
console.log(o.d); // undefined
// d是o的自身屬性嗎?不是,那看看o.[[Prototype]]上有沒有.
// d是o.[[Prototype]]的自身屬性嗎?不是,那看看o.[[Prototype]].[[Prototype]]上有沒有.
// o.[[Prototype]].[[Prototype]]為null,原型鏈已到頂端,沒有d屬性,返回undefined
 

繼承方法

JavaScript並沒有真正的"方法". JavaScript只有函數,而且一個對象的屬性值可以為一個函數. 屬性為函數的情況和屬性為其他值的情況基本沒有差別, 包括"屬性遮蔽" (這種情況相當於其他語言的方法重寫).

一個函數作為對象的屬性和獨立使用的主要區別是,當函數被調用時, this的值不同.

var o = {
  a: 2,
  m: function(b){
    return this.a + 1;
  }
};
console.log(o.m()); // 3
// 當調用 o.m 時,'this'指向了o.
var p = Object.create(o);
// p是一個對象, p.[[Prototype]]是o.
p.a = 12; // 創建p的自身屬性a.
console.log(p.m()); // 13
// 調用p.m時, 'this'指向 p. 'this.a'則是12.
 

使用不同的方法來創建對象和生成原型鏈

使用普通語法創建對象

var o = {a: 1};
// o這個對象繼承了Object.prototype上面的所有屬性
// 所以可以這樣使用 o.hasOwnProperty('a').
// hasOwnProperty 是Object.prototype的自身屬性.
// Object.prototype的原型為null,如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 數組都繼承於Array.prototype (indexOf, forEach,等方法都是從它繼承而來).
// 原型鏈如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
  return 2;
}
// 函數都繼承於Function.prototype(call, bind,等方法都是從它繼承而來):
// f ---> Function.prototype ---> Object.prototype ---> null
 

使用構造方法創建對象

在javascript中,構造方法其實就是一個普通的函數.當使用new 操作符來作用這個函數時,它就可以被稱為構造方法(構造函數).

function Graph() {
  this.vertexes = [];
  this.edges = [];
}
Graph.prototype = {
  addVertex: function(v){
    this.vertexes.push(v);
  }
};
var g = new Graph();
// g是生成的對象,他的自身屬性有'vertexes'和'edges'.
// 在g被實例化時,g.[[Prototype]]指向了Graph.prototype.
 

使用Object.create創建對象

ECMAScript 5中引入了一個新方法: Object.create. 可以調用這個方法來創建一個新對象. 新對象的原型就是調用create方法時傳入的第一個參數:

var a = {a: 1}; 
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因為d沒有繼承Object.prototype