Mozilla的JavaScript引擎__noSuchMethod__()方法
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__()将是一个非常强有力的工具。