一个“大”题目有趣的答案

2010-10-03 06:36

一个“大”题目有趣的答案

by Belleve Invis

at 2010-10-02 22:36:56

original http://typeof.net/2010/10/the-interesting-answer-for-functions-in-js/

lifesinger 出了个题目:

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

f(); // What's the result?

本人测试结果:

  • IE 6,7,8: true
  • Firefox 3.6.8: true
  • Chrome 6, IE9 beta, Safari 5: false

现在审视下代码:(请原谅我关掉了高亮)

f = function() { return true; };            // 1
g = function() { return false; };           // 2
(function() {                               // F
                                            // A
    if (g() && [] == ![]) {
        f = function f() { return false; }; // 3
        function g() { return true; };      // 4
    }
})();

f(); // What's the result?

IE 6,7,8 会让函数声明体 3(错误地) 和 4(正确地) 在 A 处生效,于是 F 中,fg 都是局部的了。这样,外界的 f 根本没有被赋值,结果是 true。等价的代码是:

f = function() { return true; };            // 1
g = function() { return false; };           // 2
(function() {                               // F
    var f, g;                               // A
    f = function(){ return false };
    g = function(){ return true  };
    if (g() && [] == ![]) {
        f = function () { return false; };  // 3
        // function g() { return true; };   // 4
    }
})();

f(); // What's the result?

Firefox 则是因为支持条件函数定义(1),在 A34 都没有生效,导致 if 分支不成立,同样没有给外界 f 赋值,得到 true。精确描述它的行为很难,下面是一个等效的代码:

f = function() { return true; };            // 1
g = function() { return false; };           // 2
(function() {                               // F
    var g1;                                 // A
    g1 = g;
    if (g() && [] == ![]) {
        g1 = function () { return true; };
        f = function () { return false; };  // 3
        // function g() { return true; };   // 4
    }
})();

f(); // What's the result?

Chrome、Safari、IE9 beta 则依照了 ECMA 之规范,只有声明 4A 处生效,等效代码是:

f = function() { return true; };            // 1
g = function() { return false; };           // 2
(function() {                               // F
    var g = function () { return true; };   // A
    if (g() && [] == ![]) {
        f = function () { return false; };  // 3
        // function g() { return true; };   // 4
    }
})();

f(); // What's the result?

结果得到 false


lifesinger说我分析的不深入,好,我将代码加了钩子后捕获各种浏览器的输出,加钩子的代码是:

var trace = function(m, x){console.log(m + ': ' + x); return x}; 

f = function() { return true; };          // 1
g = function() { return false; };         // 2
trace('[1f] now f', f); 
trace('[1g] now g', g); 
(function() {                             // F
    trace('[2f] now f', f); 
    trace('[2g] now g', g); 
    if (trace('g() && [] == ![]', trace('g()',g()) && trace('[] == ![]', [] == trace('![]', ![])))) { 
        trace('[3f] now f', f); 
        trace('[3g] now g', g); 
        f = function f() { return 0; };   // 3
        function g() { return 1; }        // 4
        trace('[4f] now f', f); 
        trace('[4g] now g', g); 
    }
    trace('[5f] now f', f); 
    trace('[5g] now g', g); 
})(); 
trace('[6f] now f', f); 
trace('[6g] now g', g); 
f()

chorme 6、Safari 5 的输出结果是:

[1f] now f: function () { return true; }
[1g] now g: function () { return false; }
[2f] now f: function () { return true; }
[2g] now g: function g() { return 1; }
g(): true
![]: false
[] == ![]: true
g() && [] == ![]: true
[3f] now f: function () { return true; }
[3g] now g: function g() { return 1; }
[4f] now f: function f() { return 0; }
[4g] now g: function g() { return 1; }
[5f] now f: function f() { return 0; }
[5g] now g: function g() { return 1; }
[6f] now f: function f() { return 0; }
[6g] now g: function () { return false; }
false

可以注意 [2f][2g] 的输出,chrome 6 遵守了 ECMA 的规定,忽略 3 处的函数声明,启用了 4 处的声明。所以你可以看到 [2g] 的里面是一个 1。而 f 则是直到 [4f] 才赋值为 function f(){return 0}

Firefox 3.6.8 的输出结果是:

[1f] now f: function () { return true; }
[1g] now g: function () { return false; }
[2f] now f: function () { return true; }
[2g] now g: function () { return false; }
g(): false
g() && [] == ![]: false
[5f] now f: function () { return true; }
[5g] now g: function () { return false; }
[6f] now f: function () { return true; }
[6g] now g: function () { return false; }
true

可以看出少了好几行,这是因为 if 的短路所致。因为我们把 function g() 给弄进了 if 里,所以 [2g] 处的结果仍然是外界的 g。

IE 6,7,8 的输出是:

日志: [1f] now f: function() { return true; }
日志: [1g] now g: function() { return false; }
日志: [2f] now f: function f() { return 0; }
日志: [2g] now g: function g() { return 1; }
日志: g(): true
日志: ![]: false
日志: [] == ![]: true
日志: g() && [] == ![]: true
日志: [3f] now f: function f() { return 0; }
日志: [3g] now g: function g() { return 1; }
日志: [4f] now f: function f() { return 0; }
日志: [4g] now g: function g() { return 1; }
日志: [5f] now f: function f() { return 0; }
日志: [5g] now g: function g() { return 1; }
日志: [6f] now f: function() { return true; }
日志: [6g] now g: function() { return false; }
true

IE 6,7,8 错误地启用了 3 处的声明,把 F 里定义了一个局部变量 f,所以 [2f] 处得到了不同的结果。因为 f 已经成为 F 的局部变量,因此外界的 f 没有收到影响。

IE9 的输出是:

日志: [1f] now f: function() { return true; }
日志: [1g] now g: function() { return false; }
日志: [2f] now f: function() { return true; }
日志: [2g] now g: function g() { return 1; }
日志: g(): true
日志: ![]: false
日志: [] == ![]: true
日志: g() && [] == ![]: true
日志: [3f] now f: function() { return true; }
日志: [3g] now g: function g() { return 1; }
日志: [4f] now f: function f() { return 0; }
日志: [4g] now g: function g() { return 1; }
日志: [5f] now f: function f() { return 0; }
日志: [5g] now g: function g() { return 1; }
日志: [6f] now f: function f() { return 0; }
日志: [6g] now g: function() { return false; }
false

和 chrome 6 的结果只有空格位置的区别。


至于 [] == ![] 的问题,因为 ![] 等于 false,是个简单值,所以需要把 [] 转换为简单值(toString)。而 [].toString() 的结果是空字符串,所以在 == 的宽松比较下它们相等。