JavaScript探秘:JScript的Bug

IE的ECMAScript严重混淆了命名函数表达式
服务器君一共花费了339.716 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

比较恶的是,IE的ECMAScript实现JScript严重混淆了命名函数表达式,搞得现很多人都出来反对命名函数表达式,而且即便是最新的一版(IE8中使用的5.8版)仍然存在下列问题。

下面我们就来看看IE在实现中究竟犯了那些错误,俗话说知已知彼,才能百战不殆。我们来看看如下几个例子:

例1:函数表达式的标示符泄露到外部作用域

var f = function g(){};
typeof g; // "function"

上面我们说过,命名函数表达式的标示符在外部作用域是无效的,但JScript明显是违反了这一规范,上面例子中的标示符g被解析成函数对象,这就乱了套了,很多难以发现的bug都是因为这个原因导致的。

注:IE9貌似已经修复了这个问题

例2:将命名函数表达式同时当作函数声明和函数表达式

typeof g; // "function"
var f = function g(){};

特性环境下,函数声明会优先于任何表达式被解析,上面的例子展示的是JScript实际上是把命名函数表达式当成函数声明了,因为它在实际声明之前就解析了g。

这个例子引出了下一个例子。

例3:命名函数表达式会创建两个截然不同的函数对象!

	var f = function g(){};
    f === g; // false

    f.expando = 'foo';
    g.expando; // undefined

看到这里,大家会觉得问题严重了,因为修改任何一个对象,另外一个没有什么改变,这太恶了。通过这个例子可以发现,创建2个不同的对象,也就是说如果你想修改f的属性中保存某个信息,然后想当然地通过引用相同对象的g的同名属性来使用,那问题就大了,因为根本就不可能。

再来看一个稍微复杂的例子:

例4:仅仅顺序解析函数声明而忽略条件语句块

	var f = function g() {
      return 1;
    };
    if (false) {
      f = function g(){
        return 2;
      };
    }
    g(); // 2

这个bug查找就难多了,但导致bug的原因却非常简单。首先,g被当作函数声明解析,由于JScript中的函数声明不受条件代码块约束,所以在这个很恶的if分支中,g被当作另一个函数function g(){ return 2 },也就是又被声明了一次。然后,所有“常规的”表达式被求值,而此时f被赋予了另一个新创建的对象的引用。由于在对表达式求值的时候,永远不会进入“这个可恶if分支,因此f就会继续引用第一个函数function g(){ return 1 }。分析到这里,问题就很清楚了:假如你不够细心,在f中调用了g,那么将会调用一个毫不相干的g函数对象。

你可能会问,将不同的对象和arguments.callee相比较时,有什么样的区别呢?我们来看看:

  var f = function g(){
    return [
      arguments.callee == f,
      arguments.callee == g
    ];
  };
  f(); // [true, false]
  g(); // [false, true]

可以看到,arguments.callee的引用一直是被调用的函数,实际上这也是好事,稍后会解释。

还有一个有趣的例子,那就是在不包含声明的赋值语句中使用命名函数表达式:

  (function(){
    f = function f(){};
  })();

按照代码的分析,我们原本是想创建一个全局属性f(注意不要和一般的匿名函数混淆了,里面用的是带名字的生命),JScript在这里捣乱了一把,首先他把表达式当成函数声明解析了,所以左边的f被声明为局部变量了(和一般的匿名函数里的声明一样),然后在函数执行的时候,f已经是定义过的了,右边的function f(){}则直接就赋值给局部变量f了,所以f根本就不是全局属性。

了解了JScript这么变态以后,我们就要及时预防这些问题了,首先防范标识符泄漏带外部作用域,其次,应该永远不引用被用作函数名称的标识符;还记得前面例子中那个讨人厌的标识符g吗?——如果我们能够当g不存在,可以避免多少不必要的麻烦哪。因此,关键就在于始终要通过f或者arguments.callee来引用函数。如果你使用了命名函数表达式,那么应该只在调试的时候利用那个名字。最后,还要记住一点,一定要把命名函数表达式声明期间错误创建的函数清理干净。

延伸阅读

此文章所在专题列表如下:

  1. 我们应该如何去了解JavaScript引擎的工作原理
  2. JavaScript探秘:编写可维护的代码的重要性
  3. JavaScript探秘:谨慎使用全局变量
  4. JavaScript探秘:var预解析与副作用
  5. JavaScript探秘:for循环(for Loops)
  6. JavaScript探秘:for-in循环(for-in Loops)
  7. JavaScript探秘:Prototypes强大过头了
  8. JavaScript探秘:eval()是“魔鬼”
  9. JavaScript探秘:用parseInt()进行数值转换
  10. JavaScript探秘:基本编码规范
  11. JavaScript探秘:函数声明与函数表达式
  12. JavaScript探秘:命名函数表达式
  13. JavaScript探秘:调试器中的函数名
  14. JavaScript探秘:JScript的Bug
  15. JavaScript探秘:JScript的内存管理
  16. JavaScript探秘:SpiderMonkey的怪癖
  17. JavaScript探秘:命名函数表达式替代方案
  18. JavaScript探秘:对象Object
  19. JavaScript探秘:原型链 Prototype chain
  20. JavaScript探秘:构造函数 Constructor
  21. JavaScript探秘:可执行的上下文堆栈
  22. 执行上下文其一:变量对象与活动对象
  23. 执行上下文其二:作用域链 Scope Chains
  24. 执行上下文其三:闭包 Closures
  25. 执行上下文其四:This指针
  26. JavaScript探秘:强大的原型和原型链
  27. JavaScript函数其一:函数声明
  28. JavaScript函数其二:函数表达式
  29. JavaScript函数其三:分组中的函数表达式
  30. JavaScript函数其四:函数构造器
  31. JavaScript变量对象其一:VO的声明
  32. JavaScript变量对象其二:VO在不同的执行上下文中
  33. JavaScript变量对象其三:执行上下文的两个阶段
  34. JavaScript变量对象其四:关于变量
  35. JavaScript变量对象其五:__parent__ 属性
  36. JavaScript作用域链其一:作用域链定义
  37. JavaScript作用域链其二:函数的生命周期
  38. JavaScript作用域链其三:作用域链特征
  39. JavaScript闭包其一:闭包概论
  40. JavaScript闭包其二:闭包的实现
  41. JavaScript闭包其三:闭包的用法

本文地址:http://www.nowamagic.net/librarys/veda/detail/1636,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1636

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《高性能网站建设指南》 桑德斯 (Steve Sounders) (作者), 刘彦博 (译者)

《高性能网站建设指南》结合Web2.0以来Web开发领域的最新形势和特点,介绍了网站性能问题的现状、产生的原因,以及改善或解决性能问题的原则、技术技巧和最佳实践。重点关注网页的行为特征,阐释优化Ajax、CSS、JavaScript、Flash和图片处理等要素的技术,全面涵盖浏览器端性能问题的方方面面。在《高性能网站建设指南》中,作者给出了14条具体的优化原则,每一条原则都配以范例佐证,并提供了在线支持。全书内容丰富,主要包括减少HTTP请求、ExpiresHeader技术、Gzip组件、CSS和JavaScript最佳实践、关闭ETags的技巧、Ajax缓存技术和最小化技术等。

更多计算机宝库...