Ext JS 的 Ext.extend 是其組件體系的基石,實現了 JavaScript 中類似 Java 的類繼承。本文以 Java 開發者視角,層層遞進:從原型鏈基礎、手寫 extend 模擬 Java 繼承,到解讀 Ext.extend 源碼核心邏輯,最後結合 Observable 實戰演示組件化開發模式。
extends 關鍵字建立類關係super(...) 初始化父類字段var Plane = function(x, y) { this.x = x; this.y = y; };
Plane.prototype.XY = function() { alert(this.x * this.y); };
var Space = function(x, y, z) {
Plane.call(this, x, y); // 手動調用父構造器
this.z = z;
};
Space.prototype.XYZ = function() { alert(this.x * this.y * this.z); };
問題:Space.prototype.__proto__ === Object.prototype,無法訪問 Plane.prototype.XY。
var extend = function(Sub, Super, protoMethods) {
var F = function() {}; // 空中介構造器
F.prototype = Super.prototype; // 中介原型指向父原型
Sub.prototype = new F(); // 子原型為中介實例,__proto__ 指向父原型
Sub.prototype.constructor = Sub; // 修正 constructor 指向
// 複製子類原型方法
for (var p in protoMethods) Sub.prototype[p] = protoMethods[p];
Sub.superclass = Super.prototype; // 靜態屬性引用父原型,便於 super 調用
};
優勢:
✅ 乾淨原型(無父類構造器產生的 undefined 字段)
✅ 方法不丟失(合併 protoMethods)
✅ 支持 Sub.superclass.constructor.call(this, ...) 調用父構造器
extend2(Super, { constructor, ...methods })var extend2 = function(Super, proto) {
var Sub = proto.constructor !== Object ? proto.constructor
: function() { Super.apply(this, arguments); };
extend(Sub, Super, proto);
return Sub;
};
用法更接近 Java 類定義風格:
var Space = extend2(Plane, {
constructor: function(x, y, z) { Plane.call(this, x, y); this.z = z; },
XYZ: function() { alert(this.x * this.y * this.z); }
});
Ext.extend = function() {
var io = function(o) { for (var m in o) this[m] = o[m]; }; // 內聯覆寫工具
var oc = Object.prototype.constructor;
return function(sb, sp, overrides) {
// 兼容兩種調用:extend(sub, sup, overrides) 或 extend(sup, overrides)
if (typeof sp == 'object') {
overrides = sp; sp = sb;
sb = (overrides.constructor !== oc) ? overrides.constructor
: function() { sp.apply(this, arguments); };
}
var F = function() {}, sbp, spp = sp.prototype;
F.prototype = spp;
sbp = sb.prototype = new F(); // 乾淨原型鏈接
sbp.constructor = sb; // 修正 constructor
sb.superclass = spp; // 靜態 superclass
// 防禦性修正:若父原型 constructor 指向 Object,重置為父類
if (spp.constructor == oc) spp.constructor = sp;
sb.override = function(o) { Ext.override(sb, o); }; // 類級覆寫
sbp.superclass = sbp.supr = (function() { return spp; }); // 原型級 superclass/supr
sbp.override = io; // 實例級覆寫
Ext.override(sb, overrides); // 應用傳入的方法/屬性
sb.extend = function(o) { return Ext.extend(sb, o); }; // 鏈式繼承
return sb;
};
}();
| 特性 | 實現細節 | 對應 Java 概念 |
|---|---|---|
| 乾淨原型 | 中介函數 F 隔離父構造器副作用 | 避免父構造器邏輯污染子原型 |
| 雙參數兼容 | 自動識別第二參數類型,支持傳統/現代語法 | 靈活適配不同編碼習慣 |
| 防禦性 constructor 修正 | 檢查父原型 constructor,必要時重置 | 確保 instanceof/反射正常工作 |
| 多層 superclass 引用 | 靜態 sb.superclass、原型 sbp.superclass/supr | 方便構造器鏈與方法覆寫調用 super |
| 內建 override 機制 | 類級 sb.override、實例級 sbp.override | 運行時動態擴展/修補行為 |
var ForumThread = Ext.extend(Ext.util.Observable, {
constructor: function(config) {
this.name = config.name;
this.listeners = config.listeners; // 註入監聽器
this.events = { "change": true }; // 聲明事件
ForumThread.superclass.constructor.call(this, config); // 必須調用父構造
},
changeName: function(newName) {
alert("原名:" + this.name);
this.name = newName;
alert("新名:" + this.name);
this.fireEvent("change", this); // 觸發事件
}
});
// 使用
Ext.onReady(function() {
var thread = new ForumThread({
name: "Ext 繼承機制深度解析",
listeners: {
change: function(t) { alert("持久化新標題:" + t.name); }
}
});
thread.changeName("新標題:Ext extend 核心原理");
});
Observable 構造器中直接讀取 this.listeners 和 this.events(子類屬性),違反「父類不應依賴子類實現細節」原則。但 Ext 團隊統一維護規範,且 Ext.extend 的防禦性代碼(if (spp.constructor == oc) spp.constructor = sp;)確保了原型鏈完整性,使此模式在受控框架內安全運行。
Ext.extend 以中介函數模式實現乾淨、可靠的鏈接。superclass 引用、內建 override,體現成熟框架的工程智慧。superclass ≈ super,override ≈ 方法覆寫,constructor 約定 ≈ 構造器規範。Ext.extend 不只是語法糖,更是一套經過實戰驗證的「類系統模擬器」。掌握其原理,便掌握了 Ext JS 組件體系的靈魂。