你的 mixins 兼容 ECMAScript 5 嗎?
最近,我正在與一位客戶合作,在一個可以充分利用 ECMAScript 5 的項目上遇到一個有趣的問題。問題源於使用 mixins,這是 JavaScript 中一種非常常見的模式,其中一個對像從另一個對象分配屬性(包括方法)。大多數mixin函數看起來像這樣:
function mixin(receiver, supplier) {
for (var property in supplier) {
if (supplier.hasOwnProperty(property)) {
receiver[property] = supplier[property];
}
}
}
mixin()
內部 函數,一個 for
循環遍歷供應商自己的所有屬性,並將值分配給接收器上的同名屬性。幾乎每個 JavaScript 庫都有某種形式的這個函數,讓你可以編寫如下代碼:
mixin(object, {
name: "Nicholas",
sayName: function() {
console.log(this.name);
}
});
object.sayName(); // outputs "Nicholas"
在本例中,object
接收屬性 name
和方法 sayName()
.這在 ECMAScript 3 中很好,但並未涵蓋 ECMAScript 5 中的所有基礎。
我遇到的問題是這種模式:
(function() {
// to be filled in later
var name;
mixin(object, {
get name() {
return name;
}
});
// let's just say this is later
name = "Nicholas";
}());
console.log(object.name); // undefined
這個例子看起來有點做作,但卻是對問題的準確描述。要混入的屬性包括只有一個 getter 的 ECMAScript 5 訪問器屬性。該 getter 引用了一個名為 name
的局部變量 未初始化為變量,因此接收 undefined
的值 .稍後,name
被分配一個值,以便訪問者可以返回一個有效值。不幸的是,object.name
(混合屬性)總是返回 undefined
.這是怎麼回事?
仔細查看 mixin()
功能。實際上,循環並不是將屬性從一個對象重新分配給另一個對象。它實際上是創建一個具有給定名稱的數據屬性,並通過訪問供應商上的該屬性為其分配返回值。對於此示例,mixin()
有效地做到了這一點:
receiver.name = supplier.name;
數據屬性 receiver.name
被創建並賦值為 supplier.name
.當然,supplier.name
有一個返回本地 name
值的 getter 多變的。在那個時間點,name
值為 undefined
, 所以這是存儲在 receiver.name
中的值 .沒有為 receiver.name
創建每個 getter 所以值永遠不會改變。
要解決此問題,您需要使用屬性描述符將屬性從一個對象正確混合到另一個對象。 mixin()
的純 ECMAScript 5 版本 應該是:
function mixin(receiver, supplier) {
Object.keys(supplier).forEach(function(property) {
Object.defineProperty(receiver, property, Object.getOwnPropertyDescriptor(supplier, property));
});
}
在這個新版本的函數中,Object.keys()
用於檢索 supplier
上所有可枚舉屬性的數組 .然後,forEach()
方法用於迭代這些屬性。對 Object.getOwnPropertyDescriptor()
的調用 檢索 supplier
的每個屬性的描述符 .由於描述符包含有關屬性的所有相關信息,包括 getter 和 setter,因此該描述符可以直接傳遞到 Object.defineProperty()
在 receiver
上創建相同的屬性 .使用這個新版本的 mixin()
,這篇文章前面的有問題的模式正如你所期望的那樣工作。吸氣劑被正確轉移到 receiver
來自 supplier
.
當然,如果您仍然需要支持舊版瀏覽器,那麼您將需要一個回退到 ECMAScript 3 方式的函數:
function mixin(receiver, supplier) {
if (Object.keys) {
Object.keys(supplier).forEach(function(property) {
Object.defineProperty(receiver, property, Object.getOwnPropertyDescriptor(supplier, property));
});
} else {
for (var property in supplier) {
if (supplier.hasOwnProperty(property)) {
receiver[property] = supplier[property];
}
}
}
}
如果您使用的是 mixin()
函數,請務必仔細檢查它是否適用於 ECMAScript 5,特別是適用於 getter 和 setter。否則,你可能會發現自己像我一樣遇到錯誤。
更新(2012 年 12 月 12 日) :修正編碼錯誤。