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;
  })();

末尾咱们深受起一个使用上述技术的使用实例,这是一个跳浏览器的addEvent函数代码:

  // 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;
  })();

虽是方案特别优雅,但也非是没缺陷。第一,由于采用不同之标识符,导致丧失了命名的一致性。且不说这样好或颇,最起码它不够清晰。有人欢喜而
用相同的名,但为有人向不以乎字眼上的差距。可究竟,不同之名会让丁联想到所用底异实现。例如,在调试器中看看attachEvent,我们就算亮
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性(本质上是一个字符串),如果开发人员为函数的是特性赋值,则该属性的价将以调试器或性质分析器中叫显示在函数“名称”的职务及。Francisco
Tolmasky详细地说了是策略的原理与实现。

 

ECMAScript未来考虑

明天的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。理查德说了本人在当时篇稿子被提及的大多数bug,所以自己强烈建议大家去看望他的说。我还要感谢Yann-Erwan
Perio
道格拉斯·克劳克佛德(Douglas
Crockford)
,他们早以2003年就算于comp.lang.javascript论坛中提及并讨论NFE问题了。

约翰-戴维·道尔顿(John-David
Dalton)
针对“最终解决方案”提出了挺好之提议。

托比·兰吉的症结被自己之所以在了“替代方案”中。

盖瑞特·史密斯(Garrett Smith)德米特里·苏斯尼科(Dmitry
Soshnikov)
针对本文的大半点作出了补偿及修正。

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

参考译文:连年访问
(SpiderMonkey的特别之后的章节参考该文)