准备面试中,在一篇博文中看到这样一道面试题


f = function() {return true;};  
g = function() {return false;};  
(function() {  
   if (g() && [] == ![]) {  
      f = function f() {return false;};  
      function g() {return true;}  
   }  
})();  
alert(f()); // true or false ?


那篇博文中对这道的解答完全不对,我觉得是因为这道题实在绕得太厉害,出得太变态,如果谁能一眼就看出此题的关键并且做出正确解答,那一定是JS达人。我花了好长时间才完全领会这道题,下面就来详解下此题的知识点。


此题的关键是第4行的if条件

g() && [] == ![]

这里有三个关键的点,

第一,g()在这里执行的到底是第2行的函数还是第6行的函数?

,Javascript里面的falsy值

,== 运算符的运算逻辑


先看第一点,我不喜欢卖关子,直接揭晓答案,第4行的g(),执行的是第6行的函数,返回值的是true。

这里的考点是javascript的Execution Context, Variable Object, Scope Chain等概念,简单来说,Javascript的作用域是函数级别的,并且在函数刚开始执行时,就会对函数体内所有的变量及内部函数进行声明,不会等到写的那一行,也就是说,即使你一个内部函数写在包含它的函数的最后面,你在第一行也照样用。回到这道题,我们本来有一个全局的函数g,写在第二行,但是进入第4行,我们进入了一个匿名函数的内部,这个匿名函数里面又在第6行声明一个局部函数g,于是这个局部函数覆盖了全局函数,因此,第4行调用的是写在第6行的局部函数g。


既然g()返回的true,那么接下来就要看 [] == ![] 这句极诡异的比较式的值,这里是拿一个空数组去跟空数组的取反的值比较,初看起来肯定是false啊。可惜,它的值是true,请不要崩溃,让我来仔细分析javascript的操蛋的逻辑。

首先我们看右边的 ![],对一个空数组取反,那么意味着先要把空数组转化为一个boolean值,再取反,在javascript中,所有对象在作为boolean值使用时,都是表示true,空数组也是一个对象,所以空数组也是true,那么对空数组取反,等价于 !true,所以,![]的值为false。

好了,既然右边说完了是false,现在我们就等于在看 [] == false,刚才不是说了空数组是对象是true么,那很明显这个比较就是true == false, 就是返回false对不对?可惜不是,这个比较会返回true,看到这你抓狂了没有,想不通了有没有?先冷静,我来告诉你发生了什么,根据 ECMAScript的对于==的规范,这样一个表达式最终是这么做的

1.把右边的false转成数值0

2.把左边的对象转成字符串,空数组转成字符串就是空字符串

3.把空字符串转成数值,结果是0

4.比较0 == 0,返回true

这里的关键是第2步,数组会先转成空字符串,空字符其实本身就是一个falsy值。


好了,到底谜底全部解开,第4行的if中的判断为实际上是true && true,结果还是true,于是全局函数f在第5行被重新赋值,所以在第9行执行的是第5行被重新赋值的f,返回false。


关于第5行其实还有点小问题,为什么这里的function f() {return false;}没有像第6行的函数g一样被当做一个局部函数,如果这样的话,那么f也是一个局部函数,对f的赋值应该不会影响全局函数f。这里的原因在于,整个第5行不是一个函数声明,第5行作为一个整体其实是一条赋值语句,所以这里的f不会被当做局部变量。


这道题出得虽然太变态,但是涵盖了好多javascript的知识,特别是一些javascript语言本身不合理的地方,仔细研究并搞懂相关的所有概念,必能有大收获。