JavaScript中有很多內部屬性和方法,在大多數情況下,只有JavaScript引擎才可以訪問,但不論什麼都是有特例的,在這裡就是指Mozilla的JavaScript引擎,包括SpiderMonkey和Rhino,都提供了若干接口來訪問這些內部屬性,如果加以合理利用的話,不僅可以讓JavaScript更加健壯,還可以開發出一些有意思的功能,比如本文介紹的__noSuchMethod__()方法就是其中之一。
著作權聲明
本文譯自 Nicholas C. Zakas 於2009年2月17日在個人網站上發表的《Mozilla JavaScript extension: __noSuchMethod__》。原文是唯一的正式版,本文是經過原作者(Nicholas C. Zakas)授權的簡體中文翻譯版(Simplified Chinese Translation)。譯者(明達)在翻譯的准確性上做了大量的努力,並承諾譯文的內容完全忠於原文,但可能還是包含疏漏和不妥之處,歡迎大家指正。譯注的內容是非正式的,僅代表譯者個人觀點。
以下是對原文的翻譯:
和其它浏覽器相比,Mozilla的JavaScript引擎總會有一些與眾不同的亮點。SpiderMonkey和他的好搭檔Rhino(用 Java實現的JavaScript引擎)都提供了很多擴展特性,可以幫助我們開發更加健壯的JavaScript應用。其中之一就是本地對象的__noSuchMethod__()方法。在大多數JavaScript引擎中,訪問一個不存在的方法時都只會簡單的拋出錯誤,而在Mozilla的引擎中,這只是默認行為,我們可以通過覆蓋某個對象的__noSuchMethod__()方來來重新定義這個行為。當這個對象試圖調用一個不存在的方法時,就會觸發該對象的__noSuchMethod__()方法。
當__noSuchMethod__()方法被調用時,JavaScript引擎會傳入兩個參數,一個是調用方法的名稱,一個是參數數組,對應於要傳遞給調用方法的所有參數,注意這個參數數組應該是一個Array對象,而不是arguments對象,而且就算沒有參數,也要傳遞一個空數組,下面舉一個簡單的例子加以說明:
// 注意,下面的代碼只有在使用SpiderMonkey或者Rhino的浏覽器中才會被正確解析
var person = {
name: "Nicholas",
__noSuchMethod__: function(name, args){
alert("Method called '" + name +
"' executed with arguments [" + args + "]");
}
}
//"Method called 'sayName' executed with arguments []"
person.sayName();
//"Method called 'phone' executed with arguments [Mike]"
person.phone("Mike");
這段代碼定義了一個person對象,並重寫了該對象的__noSuchMethod__()方法。當調用person對象的sayName()方法和phone()方法時,由於這兩個方法都不存在,所以__noSuchMethod__()方法會被自動調用,這樣我們就避免了一個錯誤的顯示,並且可以做出相應的處理。在上面這個例子中,我們所做的處理就是直接將方法的名稱和傳遞的參數直接顯示出來。
但話說回來,在運行時才發現未定義方法的情況,是不該出現在正常的開發實踐中的,那簡直就是自取煩惱。對於通常的情況來說,這個方法似乎沒有什麼作用,但他確實為我們提供了一個可能性,來創建一些有趣的動態工具,比如說建立一個可以輸出有效XHTML文檔的對象:
function HTMLWriter(){
this._work = [];
}
HTMLWriter.prototype = {
escape: function (text){
return text.replace(/[><"&]/g, function(c){
switch(c){
case ">": return ">";
case "<": return "<";
case "\"": return """;
case "&": return "&";
}
});
},
startTag: function(tagName, attributes){
this._work.push("<" + tagName);
if (attributes){
var name, value;
for (name in attributes){
if (attributes.hasOwnProperty(name)){
value = this.escape(attributes[name]);
this._work.push(" " + name + "=\"" + value + "\"");
}
}
}
this._work.push(">");
},
text: function(text){
this._work.push(this.escape(text));
},
endTag: function(tagName){
this._work.push("");
},
toString: function(){
return this._work.join("");
}
};
var writer = new HTMLWriter();
writer.startTag("html");
writer.startTag("head");
writer.startTag("title");
writer.text("Example & Test");
writer.endTag("title");
writer.endTag("head");
writer.startTag("body", { style: "background-color: red" });
writer.text("Hello world!");
writer.endTag("body");
writer.endTag("html");
alert(writer);
這段代碼通過三個方法來完成任務,分別是:startTag()、endTag()和text()。而上面給出的調用例子,卻顯得非常冗長。想象一下,如果不使用startTag()、endTag()和text()這三個方法,而是為每一個有效的XHTML標記都建立一個方法,那麼這個例子的調用方法可能就會變成下面這個樣子了:
var writer = new HTMLWriter();
var result = writer.html()
.head().title().text("Example & Test").xtitle().xhead()
.body().text("Hell world!").xbody()
.xhtml().toString();
由於每個標簽的實現大致相同,所以我們可能需要為HTMLWriter對象復制一系列非常相似的方法,這無疑是一種嚴重的浪費行為。而這正是__noSuchMethod__()發揮真正作用的時候。讓我們來看看用__noSuchMethod__()來實現這種效果到底有多麼簡單:
function HTMLWriter(){
this._work = [];
}
HTMLWriter.prototype = {
escape: function (text){
return text.replace(/[><"&]/g, function(c){
switch(c){
case ">": return ">";
case "<": return "<";
case "\"": return """;
case "&": return "&";
}
});
},
text: function(text){
this._work.push(this.escape(text));
return this;
},
toString: function(){
return this._work.join("");
},
__noSuchMethod__: function(name, args){
var tags = [
"a", "abbr", "acronym", "address", "applet", "area",
"b", "base", "basefont", "bdo", "big", "blockquote",
"body", "br", "button",
"caption", "center", "cite", "code", "col", "colgroup",
"dd", "del", "dir", "div", "dfn", "dl", "dt",
"em",
"fieldset", "font", "form", "frame", "frameset",
"h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "html",
"i", "iframe", "img", "input", "ins", "isindex",
"kbd",
"label", "legend", "li", "link",
"map", "menu", "meta",
"noframes", "noscript",
"object", "ol", "optgroup", "option",
"p", "param", "pre",
"q",
"s", "samp", "script", "select", "small", "span", "strike",
"strong", "style", "sub", "sup",
"table", "tbody", "td", "textarea", "tfoot", "th", "thead",
"title", "tr", "tt",
"u", "ul",
"var"
];
var closeTag = (name.charAt(0) == "x"),
tagName = closeTag ? name.substring(1) : name;
if (tags.indexOf(tagName) > -1){
if (!closeTag){
this._work.push("<" + tagName);
if (args.length){
var attributes = args[0],
name, value;
for (name in attributes){
if (attributes.hasOwnProperty(name)){
value = this.escape(attributes[name]);
this._work.push(" " + name + "=\"" +
value + "\"");
}
}
}
this._work.push(">");
} else {
this._work.push("");
}
return this;
} else {
throw new Error("Method '" + name + "' is undefined.");
}
}
};
這段代碼的主要功能都是在__noSuchMethod__()中實現的。它包含一個數組,對應於全部有效的XHTML標簽,用於查找可以調用的方法。如果需要關閉標簽,只需要在方法名前面加一個“x”就可以,在__noSuchMethod__()中,會對方法名的首字母進行判斷,如果首字母是“x”,就會標記為結束標簽,並將“x”從方法名稱中去掉。接下來,會通過Mozilla的數組擴展 indexOf()來判斷方法名稱是否在可用標簽的數組裡面,如果方法名是無效的,就會拋出一個錯誤;如果方法名是有效的,就會返回生成的標簽字符串。代碼所支持的標簽數量是可以動態設置的,我們只需要對標簽列表數組進行修改,就可以達到添加或者刪除“方法”的目的。
很顯然,由於這個特性不能跨浏覽器,所以肯定不能用於通常的情況下。但對於那些專門針對Mozilla引擎(比如Firefox等)的JavaScript應用來說,這無疑為我們敞開了一道神奇的大門,尤其在開發動態接口時,__noSuchMethod__()將是一個非常強有力的工具。