一个“大”题目有趣的答案
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 中,f
和 g
都是局部的了。这样,外界的 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),在 A 处 3 和 4 都没有生效,导致 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 之规范,只有声明 4 在 A 处生效,等效代码是:
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()
的结果是空字符串,所以在 ==
的宽松比较下它们相等。