moztw.org

Meta-programming in JavaScript

如大家所熟知,JavaScript 是一種具有許多特性的動態語言,能應用於 meta-programming ,換句話說,其可以操作程式及改變程式。Meta-programming 的能力在於動態 (runtime) 改變和定義程式的行為,具有高效率的表達程式結構和邏輯的優點。然而,meta-programming 是一把兩面刃,善者如倚天劍、屠龍刀;劣者則是一套七傷拳,使程式如無字天書。然而,無字天書也需看人解讀,得奧義者,駕輕就熟,否則就如身陷泥沼般不得其門而入。

然而,刀就是刀,端看人如何使用。以下介紹 JavaScript 提供的武器,看耍劍者如何應用。

getter 和 setter 可定義物件 property 的存取,在程式存取特定 property 時,觸發另一段程式,改變程式的行為。例如:

var data = {
get foo() { // Getter
var v = QueryDataBase(...);
return v;
},
set foo(value) { // Setter
SaveDataBase(value);
}
}
var valueFromDB = data.foo;
data.foo = valueSaveToDB;

1234567891011

var data = { get foo() { // Getter var v = QueryDataBase(...); return v; }, set foo(value) { // Setter SaveDataBase(value); } } var valueFromDB = data.foo; data.foo = valueSaveToDB;

把原本 property 的存取,變成 database 的存取。

更進一步的,我們可以動態的定義 getter 和 setter。

function initDBField(obj, name) {
// define getter
obj.__defineGetter__(name, function () {
return GetDataBase(name);
});
// define setter
obj.__defineSetter__(name, function (value) {
return SetDataBase(name, value);
});
}

var data = {};
var dataFields = "fieldA fieldB fieldC fieldD".split();
for (var i = 0; i < dataFields.length; i++) {
initDBField(data, dataFields[i]);
}

12345678910111213141516

function initDBField(obj, name) { // define getter obj.__defineGetter__(name, function () { return GetDataBase(name); }); // define setter obj.__defineSetter__(name, function (value) { return SetDataBase(name, value); }); } var data = {}; var dataFields = "fieldA fieldB fieldC fieldD".split(); for (var i = 0; i < dataFields.length; i++) { initDBField(data, dataFields[i]); }

透過動態生成 getter/setter 的方式,更有效率的表達。

在 JavaScript 1.8.5 之後,更引入 Proxy ,能進一步攔截物件的使用。例如:

var obj = Proxy.create({
get: function(name) { return "value=" + name; },
has: function(name) { return true; }
});

1234

var obj = Proxy.create({ get: function(name) { return "value=" + name; }, has: function(name) { return true; } });

讀取任何 property,例如 obj.XXX 就會傳回 “value=XXX” 的字串。因此可以控制所有未知的存取。

在 JavaScript ,只要把物件加入到 prototype 裡,就能變成 instance 的 property。所以我們可以動態產生 method。例如:

engine = {......};
radio = {......};

function car() {
}
car.prototype = {};

for (let [propname, value] in Iterator(engine)) {
car.prototype[propname] = value;
}
for (let [propname, value] in Iterator(radio)) {
car.prototype[propname] = value;
}

12345678910111213

engine = {......}; radio = {......}; function car() { } car.prototype = {}; for (let [propname, value] in Iterator(engine)) { car.prototype[propname] = value; } for (let [propname, value] in Iterator(radio)) { car.prototype[propname] = value; }

將 engine 和 radio 兩個 object 的內容,混合成為 car 的 prototype,也可以透過前面提到的 Proxy,達到類似的功能。

Mozilla 近來針對 JavaScript 做了許多改進,功能更趨完整。透過這些新功能,更易於進行 meta-programming 與更高階的 coding 技巧。然而,誠如前面所說,必需考量可讀性和維護者的能力,以期在高效表達和可讀性之間取得平衡。