原型與閉包是 JS 兩大難點。本文聚焦原型機制與基於原型的繼承,閉包將於下篇講解。若有疑問或糾錯,歡迎討論。
所有函數(含構造函數、普通函數)均有 prototype 屬性,指向原型對象。
function F() {}
alert(F.prototype instanceof Object); // true
function A(x) { this.x = x; }
var obj = new A(1);
obj = new Object()obj.__proto__ = A.prototype,同時 obj.constructor === A.prototype.constructor(永恆指向構造函數的 prototype.constructor)A.call(obj, 1) 初始化實例屬性三步完成後,obj 與 A 斷開聯繫。此時 obj 擁有實例屬性 x,並通過 __proto__ 訪問 A.prototype 成員。
var num = 0;
for (o in A.prototype) { alert(o); num++; }
alert("member: " + num);
A.prototype.say = function() { alert("Hi"); };
所有 A 實例共享同一個 say 函數引用,而非各自複製副本。
function B(x, y) {
this.tmpObj = A;
this.tmpObj(x); // 等價 A.call(this, x)
delete this.tmpObj;
this.y = y;
}
缺點:無法繼承 A.prototype 成員。
function A(x) { this.x = x; }
A.prototype.a = "a";
function B(x, y) {
this.y = y;
A.call(this, x); // 類式繼承實例屬性
}
B.prototype.b1 = function() { alert("b1"); };
B.prototype = new A(); // 原型鏈繼承:B.prototype → A 實例
B.prototype.b2 = function() { alert("b2"); };
B.prototype.constructor = B; // 修正 constructor 指向
var obj = new B(1, 3);
B.prototype = new A():B 原型指向 A 實例 → 擁有 x(undefined)、a("a"),建立原型鏈obj 擁有兩個 x:實例屬性 x=1 遮蔽原型屬性 x=undefinedb1 定義在 new A() 之前,被拋棄的原型對象帶走 → obj.b1 = undefinedb2 定義在 之後,正常掛載於新原型B.prototype.constructor = B:修正構造器指向,否則 obj.constructor === Aobj → B.prototype (A 實例) → A.prototype → Object.prototype → null
(x:1, y:3) (x:undefined, a:"a", b2) (a:"a")
核心心法: 先 A.call(this) 拿實例屬性,再 B.prototype = new A() 掛原型鏈,最後 在新原型上續增方法並修正 constructor。這就是 JS 原型繼承的標準範式。