Dmitry Baranovskiy是何许人也?他是目前世界最优秀的JS开源图形库Raphaël的作者,还做了许多JS游戏自娱,是JS界顶尖高手之一。
以下五道题是放于他的博客上,直到另一个它们被另一个JS高手Nicholas C. Zakas(Yahoo!主页首席前端工程师)提到,才迅速在网络蔓延开去。大家先试着自己做一下,想不明白才看解析吧。
if (!("a" in window)) { var a = 1; } alert(a);
var a = 1, b = function a(x) { x && a(--x); }; alert(a);
function a(x) { return x * 2; } var a; alert(a);
function b(x, y, a) { arguments[2] = 10; alert(a); } b(1, 2, 3);
function a() { alert(this); } a.call(null);
第一题(请经过思考后再按运行框)
第二题(请经过思考后再按运行框)
第三题(请经过思考后再按运行框)
第四题(请经过思考后再按运行框)
第五题(请经过思考后再按运行框)
解释
//第一题 //undefined //★★考察javascript在预编译期干了什么 javascript分两个阶段,预编译期与运行期 预编译期,var 变量提前,术语为提前声明。这时javascript引擎是从上到下,从外到内整块地分析我们的源码, 构建语法树。 if (!("a" in window)) { var a = 1; } alert(a); 由于javascript不存在块作用域,因此if里面的东西与if外面属于同一个作用域。在预编译阶段,var a被抽取出来, 放到作用域的顶部。更专业的解释可见ecma262r3,var 变量被放置到一个叫调用对象的东东中。 那么这时,我们的代码就变成这样 var a if(!("a" in window)){ a = 1 } alert(a) 到运行期时,便开始从上到下,从里到外执行那些修改过的脚本。很明显,"a" in window 为true ,再加个否定 就为false,进不到if语句里面了,当然alert undefined(凡是只声明没赋值的都会被自动赋上window的一个 属性undefined) //第二题 // 1 //★★考察函数声明,函数表达式与命名函数表达式 这是函数声明: function aa(){ alert("司徒正美!") } 这是函数表达式: var bb = function(){ alert("司徒正美!!") } 那这个算什么? var cc = function dd(){ alert("司徒正美!!!") } 简单,也是函数表达式,不过是叫命名函数表达式。但这东西并不统一,先说标准浏览器下,dd这个函数名只对 其函数体内可见,因此外围作用域一调用它就会出错,因为它并不存在此引用(或叫变量吧)。这特性好像比较 新的标准浏览器支持,如FF3+,safari3+(safari2有bug)。再回头看IE,IE是支持这东西,还支持更多奇怪 的写法,如 http://bbs.51js.com/viewthread.php?tid=86272&highlight=%2Binfinte 但IE的命名函数表达式很显然与标准的出入太大了。首先dd这函数名对函数的内外作用域是可见的,这很要命, 很容易污染全局作用域,造成的命名冲突,其次,它会创建两个对象,cc是一个,dd是一个,修改其中一个会不 会同步更新另一个,是内存泄漏的根源之一。命名函数表达式在IE中也作为函数声明,换言之它会在预编译阶阶 就会干掉我们的同名函数声明, 你怎样调试也打不出原因,因为调试是在运行期运行的…… 嘛,由于这个alert是位于外围作用域,因此在标准浏览器,我们的代码可以看作是这样: var a = 1; var b = function (x) { x && a(--x); }; alert(a); 因此是1 在IE中,预编译阶段,其实有两个a,不过没有关系,反正前面的会覆盖后面的,然后到运行期,a会赋予1,这时它 不会赋给另一个函数对象,因为另一个a已在预编译阶段就被干掉了。换言之,这次很幸运避免了创建两个函数对象, 于是alert(1)。 但命名函数表达式在IE下的糟糕表现,我们还是避免使用它吧,要想在内部调用自身,arguments.callee完成能胜任。 //第三题 //弹出函数的toString(),即 //function a(x) { // return x * 2; //} 思路基本同第一题,var变量声明在预编译阶段被提前了,然后被重写。 var a; function a(x) { return x * 2; } alert(a); //第四题 //10 //★★考察arguments对象 在执行一个函数前,它会把它自身(callee),它的运行环境(callee.caller),传入参数的个数,构建成一个 argument对象,接着就像数组一样,把参数一个个塞进argument中。运行时,如果某个参数被修改了, arguments对应的值也同步更新。 理解这些就简单了。原函数的意思是传入三个参数,内部会把第三个参数修改成10,然后弹出第三个参数10, 因此它根本不在乎你传什么,只要传够三个或三个以上参数,它就会alert 10。 //第五题 //弹出window对象的toString() //基本上不是[object Window] 就是[object],[object DOMWindow] //★★考察动态绑定的用法,详见我的博文《javascript的动态this与动态绑定》 function a() { alert(this);//这里将返回其调用者 } 如果现在直接运行,肯定返回全局对象window。 但如果使用apply或call来改变调用对象呢,原则上其第一个参数就是调用对象,通常也被称之为thisObject。但 有几个特殊情况,当第一参数为null,undefined,或干脆一个参数也没有,就会默认为是传入window对象。
补充实验:
嘛,解释就完全按我的理解去写啦,应该大体上和Nicholas C. Zakas差不多。可能他的更详细,毕竟从原文篇幅来看也确实如此,英文好的就看英文吧。本时我搜到一些俄罗斯文,法文什么的,也基本上贴在google在线翻译上,连蒙带猜地学习。外国人的技术比我们强多了,大家应该多上外国网站。