读书频道 > web开发 > Javascript > Effective JavaScript:编写高质量JavaScript代码的68个有效方法
第14条:当心命名函数表达式笨拙的作用域
2013-12-07 15:03:36     我来说两句 
收藏    我要投稿   
本书共分为7章,分别涵盖JavaScript的不同主题。第1章主要讲述最基本的主题,如版本、类型转换要点、运算符注意事项和分号局限等。第2章主要讲解变量作用域,介绍此方面的一些基本概念,以及一些最佳实践经验。第  立即去当当网订购

JavaScript函数无论放在何处看起来似乎都是一样的,但是根据上下文其含义会发生变化。请看以下代码片段。

 

这段代码可以是一个函数声明,也可以是一个命名函数表达式(named function expression),这取决于它出现的地方。这个声明是如此熟悉,它定义一个函数并绑定到当前作用域的一个变量。例如,在程序的最顶层,以上的声明将创建一个名为double的全局函数。但是同一段函数代码也可以作为一个表达式,它可以有截然不同的含义。例如:

 

根据ECMAScript规范,此语句将该函数绑定到变量f,而不是变量double。当然,给函数表达式命名并不是必要的。我们可以使用匿名的函数表达式形式:

 

匿名和命名函数表达式的官方区别在于后者会绑定到与其函数名相同的变量上,该变量将作为该函数内的一个局部变量。这可以用来写递归函数表达式。

 

注意,变量find的作用域只在其自身函数中。不像函数声明,命名函数表达式不能通过其内部的函数名在外部被引用。

 

使用命名函数表达式进行递归似乎没有必要,因为使用外部作用域的函数名也可达到同样的效果:


 

命名函数表达式真正的用处是进行调试。大多数现代的JavaScript环境都提供对Error对象的栈跟踪功能。在栈跟踪中,函数表达式的名称通常作为其入口使用。用于检查栈的设备调试器对命名函数表达式有类似的使用。

遗憾的是,命名函数表达式是作用域和兼容性问题臭名昭著的来源。这要归结于在ECMAScript规范的历史中很不幸的错误以及流行的JavaScript引擎中的Bug。规范的错误在ES3中已经存在,JavaScript引擎被要求将命名函数表达式的作用域表示为一个对象,这有点像有问题的with结构。该作用域对象只含有单个属性,该属性将函数名和函数自身绑定起来。该作用域对象也继承了Object.prototype的属性。这意味着仅仅是给函数表达式命名也会将Object.prototype中的所有属性引入到作用域中。结果可能会出人意料:

 

该程序看起来会产生null,但其实会产生一个新的对象。因为命名函数表达式在其作用域内继承了Object.prototype.constructor(即Object的构造函数)。就像with语句一样,这个作用域会因Object.prototype的动态改变而受到影响。程序的一部分可能添加或删除Object.prototype属性,命名函数表达式中的所有变量都会受到影响。

幸运的是,ES5修正了这个错误。但是一些JavaScript环境仍然使用过时的对象作用域。更糟的是,有些环境甚至更不符合标准,而且甚至对匿名函数表达式使用对象作为作用域。即使删除上述例子中的函数表达式名也会产生一个对象,而不是预期结果null.

 

系统中避免对象污染函数表达式作用域的最好方式是避免任何时候在Object.prototype中添加属性,以及避免使用任何与标准Object.protoype属性同名的局部变量。

在流行的JavaScript引擎中的另一个缺陷是对命名函数表达式的声明进行提升。例如:

 

需要明确的是,这是不符合标准的行为。更糟的是,一些JavaScript环境甚至把f和g这两个函数作为不同的对象,从而导致不必要的内存分配。这种行为的一个合理的解决办法是创建一个与函数表达式同名的局部变量并赋值为null。

 

即使在没有错误地提升函数表达式声明的环境中,使用var重声明变量能确保仍然会绑定变量g。设置变量g为null能确保重复的函数可以被垃圾回收。

当然可以得出合理的结论:命名函数表达式由于会导致很多问题,所以并不值得使用。一个不太严肃的回应是在开发阶段使用命名函数表达式用作调试,在发布前通过预处理程序将所有的函数表达式转为匿名的。但有一条是肯定的,你应当总是明确发布的平台(请参阅第1条)。你可能做的最糟的事情是为了支持那些甚至没有必要支持的平台将代码弄得一团糟。

 提示

在Error对象和调试器中使用命名函数表达式改进栈跟踪。

在ES3和有问题的JavaScript环境中谨记函数表达式作用域会被Object.prototype污染。

谨记在错误百出的JavaScript环境中会提升命名函数表达式声明,并导致命名函数表达式的重复存储。

考虑避免使用命名函数表达式或在发布前删除函数名。

如果你将代码发布到正确实现的ES5环境中,那么你没有什么好担心的。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:第13条:使用立即调用的函数表达式创建局部作用域
下一篇:第15条:当心局部块函数声明笨拙的作用域
相关文章
图文推荐
3.12 本章小结
3.10 添加新函数
3.9 递归
3.8 闭包
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站