ECMAScript深入精晓JavaScript体系(2):揭秘命名函数表明式

前言

网上还没用发现有人对命名函数表明式进去重复深远的研究,正因为这么,网上出现了各式种种的误解,本文将从常理和履行四个方面来探究JavaScript关于命名函数表明式的优缺点。

一言以蔽之的说,命名函数表明式只有一个用户,那就是在Debug或者Profiler分析的时候来描述函数的名号,也足以利用函数名完毕递归,但很快你
就会发现实际是不切实际的。当然,要是您不爱戴调试,那就没怎么可担心的了,否则,若是你想打听包容性方面的东西来说,你要么应该继续往下看看。

大家先开始看看,什么叫函数表明式,然后再说一下现代调试器怎样处理那几个表达式,假诺你早已对那地方很了解的话,请直接跳过此小节。

函数表明式和函数注脚

在ECMAScript中,创造函数的最常用的多个艺术是函数表明式和函数声明,两者之间的界别是有点晕,因为ECMA规范只眼看了少数:函数注脚必须带有标示符(Identifier)(就是我们常说的函数名称),而函数表明式则足以简单这些标记符:

  函数宣称:

  function 函数名称 (参数:可选){ 函数体 }

  函数表达式:

  function
函数名称(可选)(参数:可选){ 函数体 }

由此,可以看看,借使不注脚函数名称,它必将是表明式,可如若注脚了函数名称的话,怎么着判定是函数表明如故函数表明式呢?ECMAScript是通
过左右文来区其余,借使function
foo(){}是作为赋值表明式的一有的的话,那它就是一个函数表明式,如若function
foo(){}被含有在一个函数体内,或者放在程序的最顶部的话,那它就是一个函数申明。

  function foo(){} // 声明,因为它是程序的一部分
  var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分

  new function bar(){}; // 表达式,因为它是new表达式

  (function(){
    function bar(){} // 声明,因为它是函数体的一部分
  })();

再有一种函数表明式不太普遍,就是被括号括住的(function
foo(){}),他是表明式的因由是因为括号
()是一个分组操作符,它的内部只能分包表明式,大家来看多少个例子:

  function foo(){} // 函数声明
  (function foo(){}); // 函数表达式:包含在分组操作符内

  try {
    (var x = 5); // 分组操作符,只能包含表达式而不能包含语句:这里的var就是语句
  } catch(err) {
    // SyntaxError
  }

你可以会想到,在行使eval对JSON举办实践的时候,JSON字符串经常被含有在一个圆括号里:eval(‘(‘ + json + ‘)’),那样做的原由就是因为分组操作符,也就是那对括号,会让解析器强制将JSON的花括号解析成表明式而不是代码块。

  try {
    { "x": 5 }; // "{" 和 "}" 做解析成代码块
  } catch(err) {
    // SyntaxError
  }

  ({ "x": 5 }); // 分组操作符强制将"{" 和 "}"作为对象字面量来解析

表达式和申明存在着那一个神秘的差别,首先,函数申明会在其余表明式被解析和求值往日先被解析和求值,尽管你的表明在代码的尾声一行,它也会
在同功用域内先是个表明式以前被分析/求值,参考如下例子,函数fn是在alert之后申明的,不过在alert执行的时候,fn已经有定义了:

  alert(fn());

  function fn() {
    return 'Hello world!';
  }

除此以外,还有少数须要提示一下,函数注解在规范语句内虽说可以用,然则尚未被规范,也就是说分裂的条件可能有差距的施行结果,所以那样情状下,最好使用函数表达式:

  // 千万别这样做!
  // 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个

  if (true) {
    function foo() {
      return 'first';
    }
  }
  else {
    function foo() {
      return 'second';
    }
  }
  foo();

  // 相反,这样情况,我们要用函数表达式
  var foo;
  if (true) {
    foo = function() {
      return 'first';
    };
  }
  else {
    foo = function() {
      return 'second';
    };
  }
  foo();

函数申明的实际规则如下:

函数申明只可以冒出在程序函数体内。从句法上讲,它们
无法冒出在Block(块)({ … })中,例如不可能出现在 if、while 或 for
语句中。因为 Block(块) 中只好分包Statement语句,
而不可以包涵函数申明诸如此类的源元素。另一方面,仔细看一看规则也会发觉,唯一可能让表达式出现在Block(块)中状态,就是让它看成表明式语句的一有的。然而,规范明确规定了表明式语句不可以以主要字function开端。而那实则就是,函数表明式如出一辙也无法冒出在Statement语句或Block(块)中(因为Block(块)就是由Statement语句构成的)。

函数语句

在ECMAScript的语法增加中,有一个是函数语句,近期唯有根据Gecko的浏览器完结了该扩充,所以对于上面的事例,大家仅是抱着读书的目的来看,一般的话不推荐使用(除非你针对Gecko浏览器举行支付)。

1.貌似语句能用的地点,函数语句也能用,当然也席卷Block块中:

  if (true) {
    function f(){ }
  }
  else {
    function f(){ }
  }

2.函数语句可以像其余语句一样被解析,包涵基于条件执行的景观

  if (true) {
    function foo(){ return 1; }
  }
  else {
    function foo(){ return 2; }
  }
  foo(); // 1
  // 注:其它客户端会将foo解析成函数声明 
  // 因此,第二个foo会覆盖第一个,结果返回2,而不是1

3.函数语句不是在变量伊始化时期声明的,而是在运作时宣称的——与函数表明式一样。然而,函数语句的标识符一旦讲明能在函数的方方面面成效域生效了。标识符有效性正是导致函数语句与函数表明式区其余关键所在(下一小节大家将会显得命名函数表明式的具体表现)。

  // 此刻,foo还没用声明
  typeof foo; // "undefined"
  if (true) {
    // 进入这里以后,foo就被声明在整个作用域内了
    function foo(){ return 1; }
  }
  else {
    // 从来不会走到这里,所以这里的foo也不会被声明
    function foo(){ return 2; }
  }
  typeof foo; // "function"

而是,我们可以使用上边这样的符合标准的代码来情势方面例子中的函数语句:

  var foo;
  if (true) {
    foo = function foo(){ return 1; };
  }
  else {
    foo = function foo() { return 2; };
  }

4.函数语句和函数注明(或命名函数表明式)的字符串表示类似,也包含标识符:

  if (true) {
    function foo(){ return 1; }
  }
  String(foo); // function foo() { return 1; }

5.此外一个,早期基于Gecko的兑现(Firefox
3及从前版本)中设有一个bug,即函数语句覆盖函数声明的方法不得法。在那么些中期的贯彻中,函数语句不知为什么不能遮住函数申明:

  // 函数声明
  function foo(){ return 1; }
  if (true) {
    // 用函数语句重写
    function foo(){ return 2; }
  }
  foo(); // FF3以下返回1,FF3.5以上返回2

  // 不过,如果前面是函数表达式,则没用问题
  var foo = function(){ return 1; };
  if (true) {
    function foo(){ return 2; }
  }
  foo(); // 所有版本都返回2

重复强调一点,上边这么些事例只是在某些浏览器匡助,所以推举咱们不要采纳那些,除非你就在特点的浏览器上做开发。

取名函数表达式

函数表明式在其实应用中仍旧很普遍的,在web开发中友个常用的情势是根据对某种特性的测试来伪装函数定义,从而达到质量优化的目的,但出于那种艺术都是在相同功能域内,所以基本上一定要用函数表明式:

  // 该代码来自Garrett Smith的APE Javascript library库(http://dhtmlkitchen.com/ape/) 
  var contains = (function() {
    var docEl = document.documentElement;

    if (typeof docEl.compareDocumentPosition != 'undefined') {
      return function(el, b) {
        return (el.compareDocumentPosition(b) & 16) !== 0;
      };
    }
    else if (typeof docEl.contains != 'undefined') {
      return function(el, b) {
        return el !== b && el.contains(b);
      };
    }
    return function(el, b) {
      if (el === b) return false;
      while (el != b && (b = b.parentNode) != null);
      return el === b;
    };
  })();

波及命名函数表明式,理所当然,就是它得闻明字,后边的例证var bar =
function
foo(){};就是一个卓有功用的命名函数表明式,但有一点亟需牢记:那么些名字只在新定义的函数效率域内立见作用,因为专业规定了标记符不可能在外侧的功效域内一蹴而就:

  var f = function foo(){
    return typeof foo; // foo是在内部作用域内有效
  };
  // foo在外部用于是不可见的
  typeof foo; // "undefined"
  f(); // "function"

既然如此,这么须要,那命名函数表明式到底有何用啊?为什么要取名?

正如我们开端所说:给它一个名字就是足以让调节进程更便于,因为在调节的时候,倘诺在调用栈中的种种项都有自己的名字来讲述,那么调试进程就太爽了,感受差距嘛。

调试器中的函数名

假诺一个函数盛名字,那调试器在调试的时候会将它的名字呈现在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和
这么些使用该函数的福利具有同样的角色,不过日常情状下,那么些调试器只设置简便的条条框框来定名,所以说并未太大价钱,大家来看一个例子:

  function foo(){
    return bar();
  }
  function bar(){
    return baz();
  }
  function baz(){
    debugger;
  }
  foo();

  // 这里我们使用了3个带名字的函数声明
  // 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了 
  // 因为很明白地显示了名称
  baz
  bar
  foo
  expr_test.html()

因而翻看调用栈的音信,大家得以很明了地知道foo调用了bar,
bar又调用了baz(而foo本身有在expr_test.html文档的大局功效域内被调用),不过,还有一个比较爽地点,就是刚刚说的Firebug为匿名表明式取名的职能:

  function foo(){
    return bar();
  }
  var bar = function(){
    return baz();
  }
  function baz(){
    debugger;
  }
  foo();

  // Call stack
  baz
  bar() //看到了么? 
  foo
  expr_test.html()

下一场,当函数表明式稍微复杂一些的时候,调试器就不那么聪明了,大家不得不在调用栈中看到问号:

  function foo(){
    return bar();
  }
  var bar = (function(){
    if (window.addEventListener) {
      return function(){
        return baz();
      };
    }
    else if (window.attachEvent) {
      return function() {
        return baz();
      };
    }
  })();
  function baz(){
    debugger;
  }
  foo();

  // Call stack
  baz
  (?)() // 这里可是问号哦
  foo
  expr_test.html()

除此以外,当把函数赋值给五个变量的时候,也会产出让人郁闷的标题:

  function foo(){
    return baz();
  }
  var bar = function(){
    debugger;
  };
  var baz = bar;
  bar = function() { 
    alert('spoofed');
  };
  foo();

  // Call stack:
  bar()
  foo
  expr_test.html()

那儿,调用栈突显的是foo调用了bar,但实则并非如此,之所以有那种难题,是因为baz和别的一个包罗alert(‘spoofed’)的函数做了引用调换所导致的。

总归,唯有给函数表达式取个名字,才是最委托的艺术,也就是应用取名函数表明式。大家来使用带名字的表明式来重写上边的例证(注意及时调用的发布式块里重回的2个函数的名字都是bar):

  function foo(){
    return bar();
  }
  var bar = (function(){
    if (window.addEventListener) {
      return function bar(){
        return baz();
      };
    }
    else if (window.attachEvent) {
      return function bar() {
        return baz();
      };
    }
  })();
  function baz(){
    debugger;
  }
  foo();

  // 又再次看到了清晰的调用栈信息了耶!
  baz
  bar
  foo
  expr_test.html()

OK,又学了一招吧?可是在欢悦此前,大家再看看不一致平日的JScript吧。

JScript的Bug

正如恶的是,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来引用函数。若是您采纳了命名函数表明式,那么应该只在调节的时候利用这些名字。最后,还要记住一点,一定要把取名函数表达式表明时期错误创制的函数清理彻底

对于,上面最终一点,我们还得再解释一下。

JScript的内存管理

了解了那个不符合规范的代码解析bug将来,大家如若用它的话,就会发觉内存方面实际上是有难题的,来看一个例子:

  var f = (function(){
    if (true) {
      return function g(){};
    }
    return function g(){};
  })();

俺们精通,这些匿名函数调用重回的函数(带有标识符g的函数),然后赋值给了外部的f。咱们也知道,命名函数表明式会促成暴发多余的函数对象,而该
对象与再次来到的函数对象不是一次事。所以那一个多余的g函数就死在了回来函数的闭包中了,由此内存难题就应运而生了。那是因为if语句内部的函数与g是在同一个功效域中被声称的。那种状态下
,除非大家显式断开对g函数的引用,否则它平素占着内存不放。

  var f = (function(){
    var f, g;
    if (true) {
      f = function g(){};
    }
    else {
      f = function g(){};
    }
    // 设置g为null以后它就不会再占内存了
    g = null;
    return f;
  })();

经过安装g为null,垃圾回收器就把g引用的很是隐式函数给回收掉了,为了印证大家的代码,我们来做一些测试,以有限扶助大家的内存被回收了。

测试

测试很简单,就是命名函数表达式制造10000个函数,然后把它们保存在一个数组中。等说话过后再看那几个函数到底占用了稍稍内存。然后,再断开那几个引用并再一次这一进度。上边是测试代码:

  function createFn(){
    return (function(){
      var f;
      if (true) {
        f = function F(){
          return 'standard';
        };
      }
      else if (false) {
        f = function F(){
          return 'alternative';
        };
      }
      else {
        f = function F(){
          return 'fallback';
        };
      }
      // var F = null;
      return f;
    })();
  }

  var arr = [ ];
  for (var i=0; i<10000; i++) {
    arr[i] = createFn();
  }

因此运行在Windows XP SP2中的职责管理器可以观察如下结果:

  IE6:

    without `null`:   7.6K -> 20.3K
    with `null`:      7.6K -> 18K

  IE7:

    without `null`:   14K -> 29.7K
    with `null`:      14K -> 27K

如我辈所料,呈现断开引用可以自由内存,不过自由的内存不是成百上千,10000个函数对象才获释大致3M的内存,这对有些小型脚本不算什么,但对于大型程序,或者长日子运作在低内存的设施里的时候,这是相当有要求的。

 

关于在Safari
2.x中JS的剖析也有一些bug,但介于版本比较低,所以大家在那里就不介绍了,大家若是想看的话,请密切查阅英文材料。

SpiderMonkey的怪癖

我们都通晓,命名函数表明式的标识符只在函数的一对成效域中有效。但含有那几个标识符的一些效用域又是如何体统的吗?其实万分简单。在命名函数表达式被求值时,会始建一个特种的目的,该对象的绝无仅有目标就是保存一个质量,而这一个特性的名字对应着函数标识符,属性的值对应着卓殊函数。这些目的会被注入到当前出力域链的前端。然后,被“伸张”的效益域链又被用于伊始化函数。

在此间,有某些可怜妙趣横生,那就是ECMA-262定义这些(保存函数标识符的)“特殊”对象的法门。标准说“像调用new
Object()表明式那样”
成立这么些目的。借使从字面上来精通那句话,那么这几个目的就相应是全局Object的一个实例。然则,唯有一个落实是比照正规字面上的须求这么做的,这些完成就是SpiderMonkey。因而,在SpiderMonkey中,增加Object.prototype有可能会困扰函数的部分效用域:

  Object.prototype.x = 'outer';

  (function(){

    var x = 'inner';

    /*
      函数foo的作用域链中有一个特殊的对象——用于保存函数的标识符。这个特殊的对象实际上就是{ foo: <function object> }。
      当通过作用域链解析x时,首先解析的是foo的局部环境。如果没有找到x,则继续搜索作用域链中的下一个对象。下一个对象
      就是保存函数标识符的那个对象——{ foo: <function object> },由于该对象继承自Object.prototype,所以在此可以找到x。
      而这个x的值也就是Object.prototype.x的值(outer)。结果,外部函数的作用域(包含x = 'inner'的作用域)就不会被解析了。
    */

    (function foo(){

      alert(x); // 提示框中显示:outer

    })();
  })();

不过,更高版本的SpiderMonkey改变了上述行为,原因想必是觉得那是一个安全漏洞。也就是说,“特殊”对象不再继承Object.prototype了。然则,如若你利用Firefox
3依然更低版本,还足以“重温”这种表现。

另一个把内部对象落成为大局Object对象的是小米(Blackberry)浏览器。目前,它的运动对象(Activation
Object)依然继承Object.prototype。可是,ECMA-262并不曾说移步目的也要“像调用new
Object()表明式那样”来创制(或者说像创制保存NFE标识符的目的一样成立)。
人家规范只说了移动对象是标准中的一种体制。

那大家就来探望华为里都暴发了怎么:

  Object.prototype.x = 'outer';

  (function(){

    var x = 'inner';

    (function(){

      /*
      在沿着作用域链解析x的过程中,首先会搜索局部函数的活动对象。当然,在该对象中找不到x。
      可是,由于活动对象继承自Object.prototype,因此搜索x的下一个目标就是Object.prototype;而
      Object.prototype中又确实有x的定义。结果,x的值就被解析为——outer。跟前面的例子差不多,
      包含x = 'inner'的外部函数的作用域(活动对象)就不会被解析了。
      */

      alert(x); // 显示:outer

    })();
  })();

不过神奇的依旧,函数中的变量甚至会与已有些Object.prototype的成员暴发顶牛,来看望下边的代码:

  (function(){

    var constructor = function(){ return 1; };

    (function(){

      constructor(); // 求值结果是{}(即相当于调用了Object.prototype.constructor())而不是1

      constructor === Object.prototype.constructor; // true
      toString === Object.prototype.toString; // true

      // ……

    })();
  })();

要幸免这些题材,要幸免选取Object.prototype里的特性名称,如toString,
valueOf, hasOwnProperty等等。

 

JScript解决方案

  var fn = (function(){

    // 声明要引用函数的变量
    var f;

    // 有条件地创建命名函数
    // 并将其引用赋值给f
    if (true) {
      f = function F(){ }
    }
    else if (false) {
      f = function F(){ }
    }
    else {
      f = function F(){ }
    }

    // 声明一个与函数名(标识符)对应的变量,并赋值为null
    // 这实际上是给相应标识符引用的函数对象作了一个标记,
    // 以便垃圾回收器知道可以回收它了
    var F = null;

    // 返回根据条件定义的函数
    return f;
  })();

末段大家付出一个选取上述技术的利用实例,那是一个跨浏览器的add伊夫nt函数代码:

  // 1) 使用独立的作用域包含声明
  var addEvent = (function(){

    var docEl = document.documentElement;

    // 2) 声明要引用函数的变量
    var fn;

    if (docEl.addEventListener) {

      // 3) 有意给函数一个描述性的标识符
      fn = function addEvent(element, eventName, callback) {
        element.addEventListener(eventName, callback, false);
      }
    }
    else if (docEl.attachEvent) {
      fn = function addEvent(element, eventName, callback) {
        element.attachEvent('on' + eventName, callback);
      }
    }
    else {
      fn = function addEvent(element, eventName, callback) {
        element['on' + eventName] = callback;
      }
    }

    // 4) 清除由JScript创建的addEvent函数
    //    一定要保证在赋值前使用var关键字
    //    除非函数顶部已经声明了addEvent
    var addEvent = null;

    // 5) 最后返回由fn引用的函数
    return fn;
  })();

代替方案

事实上,要是我们不想要那些描述性名字的话,大家就足以用最简便的款型来做,也就是在函数内部宣称一个函数(而不是函数表明式),然后再次回到该函数:

  var hasClassName = (function(){

    // 定义私有变量
    var cache = { };

    // 使用函数声明
    function hasClassName(element, className) {
      var _className = '(?:^|\\s+)' + className + '(?:\\s+|$)';
      var re = cache[_className] || (cache[_className] = new RegExp(_className));
      return re.test(element.className);
    }

    // 返回函数
    return hasClassName;
  })();

众目睽睽,当存在多少个支行函数定义时,那么些方案就老大了。不过有种情势相似可以兑现:那就是提前选拔函数申明来定义所有函数,并分别为那么些函数指定差别的标识符:

  var addEvent = (function(){

    var docEl = document.documentElement;

    function addEventListener(){
      /* ... */
    }
    function attachEvent(){
      /* ... */
    }
    function addEventAsProperty(){
      /* ... */
    }

    if (typeof docEl.addEventListener != 'undefined') {
      return addEventListener;
    }
    elseif (typeof docEl.attachEvent != 'undefined') {
      return attachEvent;
    }
    return addEventAsProperty;
  })();

即便这几个方案很优雅,但也不是未曾缺陷。第一,由于应用分化的标识符,导致丧失了命名的一致性。且不说那样好仍旧坏,最起码它不够显明。有人喜欢使
用相同的名字,但也有人根本不在乎字眼上的差距。可到底,不相同的名字会让人联想到所用的两样完成。例如,在调试器中看看attach伊芙nt,我们就知
addEvent是基于attachEvent的已毕。当
然,基于完结来定名的不二法门也不肯定都行得通。如果大家要提供一个API,并遵守那种情势把函数命名为inner。那么API用户的很简单就会被相应落成的
细节搞得晕头转向。

要解决那几个题材,当然就得想一套更合理的命名方案了。但重假如绝不再额外创设麻烦。我前几日能想起来的方案大致有如下多少个:

  'addEvent', 'altAddEvent', 'fallbackAddEvent'
  // 或者
  'addEvent', 'addEvent2', 'addEvent3'
  // 或者
  'addEvent_addEventListener', 'addEvent_attachEvent', 'addEvent_asProperty'

其余,那种方式还设有一个小标题,即扩大内存占用。提前创造N个分歧名字的函数,等于有N-1的函数是用不到的。具体来讲,假设document.documentElement
中包含attachEvent,那么addEventListeneraddEventAsProperty则根本就富余了。可是,他们都占着内存哪;而且,那几个内存将永久都得不到释放,原因跟JScript臭哄哄的命名表达式相同——这八个函数都被“截留”在再次回到的分外函数的闭包中了。

唯独,增添内存占用这么些标题确实没什么大不断的。如果某个库——例如Prototype.js——接纳了那种形式,无非也就是多创制一两百个函数而已。只要不是(在运行时)重复地创设那么些函数,而是只(在加载时)创设一回,那么就从未什么好担心的。

WebKit的displayName

WebKit团队在那一个难点选取了一部分另类的政策。介于匿名和命名函数如此之差的表现力,WebKit引入了一个“特殊的”displayName属性(本质上是一个字符串),假使开发人士为函数的那一个特性赋值,则该属性的值将在调试器或性质分析器中被突显在函数“名称”的地点上。FranciscoTolmasky详细地诠释了这些策略的法则和完毕

 

前景考虑

他日的ECMAScript-262第5版(目前仍旧草案)会引入所谓的严苛情势(strict
mode)
。开启严俊形式的兑现会禁用语言中的那多少个不安定、不可靠和不安全的风味。据说是因为安全方面的考虑,arguments.callee特性将在严谨情势下被“封杀”。因而,在地处严酷情势时,访问``arguments.callee会导致TypeError(参见ECMA-262第5版的10.6节)。而我因而在此提到严厉形式,是因为一旦在依照第5版正式的落到实处中无法选拔arguments.callee来推行递归操作,那么使用命名函数表明式的可能就会大大增加。从那个意义上的话,了解命名函数表明式的语义及其bug也就显得尤其重点了。

  // 此前,你可能会使用arguments.callee
  (function(x) {
    if (x <= 1) return 1;
    return x * arguments.callee(x - 1);
  })(10);

  // 但在严格模式下,有可能就要使用命名函数表达式
  (function factorial(x) {
    if (x <= 1) return 1;
    return x * factorial(x - 1);
  })(10);

  // 要么就退一步,使用没有那么灵活的函数声明
  function factorial(x) {
    if (x <= 1) return 1;
    return x * factorial(x - 1);
  }
  factorial(10);

致谢

理查德· 康福德(Richard
Cornford)
,是她首先释疑了JScript中命名函数表明式所存在的bug。Richard解释了自我在那篇小说中提及的一大半bug,所以我强烈提议大家去探访她的表明。我还要感谢Yann-Erwan
Perio
道格拉斯·克劳克佛德(DouglasCrockford),他们早在二〇〇三年就在comp.lang.javascript论坛中提及并研商NFE难点了

约翰-戴维·道尔顿(John-David
Dalton)
对“最后解决方案”指出了很好的提议。

托比·兰吉的主旨被我用在了“替代方案”中。

盖瑞特·史密斯(Garrett Smith)德米Terry·苏斯尼科(Dmitry
Soshnikov)
对本文的多地点作出了补偿和更正。

文字转自:http://www.cnblogs.com/TomXu/archive/2011/12/29/2290308.html
英文原稿:http://kangax.github.com/nfe/

参照译文:老是访问
(SpiderMonkey的更加之后的章节参考该文)