Dmitry Baranovskiy的javascript谜题

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在线翻译上,连蒙带猜地学习。外国人的技术比我们强多了,大家应该多上外国网站。

原文地址:https://www.cnblogs.com/rubylouvre/p/1658434.html