【问题标题】:Why are anonymous function expressions and named function expressions initialized so differently?为什么匿名函数表达式和命名函数表达式的初始化如此不同?
【发布时间】:2013-02-14 06:54:38
【问题描述】:

我正在查看 section 13 或 ECMAScript 规范 (v. 5)。一个匿名函数表达式初始化如下:

返回创建一个新的函数对象的结果,如 13.2 中指定的,参数由 FormalParameterListopt 指定,主体由 FunctionBody 指定。传入正在运行的执行上下文的 LexicalEnvironment 作为 Scope。如果 FunctionExpression 包含在严格代码中或其 FunctionBody 是严格代码,则将 true 作为 Strict 标志传递。

这个逻辑非常类似于函数声明的初始化方式。但是,请注意命名函数表达式的初始化有多么不同。

  1. 让 funcEnv 成为调用 NewDeclarativeEnvironment 的结果,并将运行执行上下文的词法环境作为 论据
  2. 设 envRec 为 funcEnv 的环境记录。
  3. 调用 envRec 的 CreateImmutableBinding 具体方法,传入 Identifier 的 String 值作为参数。
  4. 让闭包是创建一个新的函数对象的结果,如 13.2 中指定的那样,其参数由 FormalParameterListopt 指定 和由 FunctionBody 指定的主体。传入 funcEnv 作为 Scope。经过 如果 FunctionExpression 包含在 true 中,则作为 Strict 标志 严格代码,或者如果它的 FunctionBody 是严格代码。
  5. 调用 envRec 的 InitializeImmutableBinding 具体方法,传递 Identifier 的 String 值和闭包作为参数。
  6. 返回关闭。

我知道命名/匿名函数表达式之间的一大区别是命名函数表达式可以在函数内递归调用,但我能想到的就是这些。为什么设置如此不同,为什么需要执行这些额外步骤?

【问题讨论】:

    标签: javascript function anonymous-function ecmascript-5 ecma262


    【解决方案1】:

    两者之间的核心区别在于范围界定(虽然,如果不出意外,很想知道这样做实际上涉及了多少;) - 你已经正确指出了命名/匿名之间的主要区别函数表达式是递归调用命名函数的轻松

    我为什么说轻松?好吧,实际上没有什么能阻止您使用calling an anonymous function recursively,但这只是简单的不漂亮:

    //silly factorial, 5!
    (function(n) {
      if (n<=1) return 1;
      return (n*arguments.callee(n-1)); //arguments.callee is so 1990s!
    })(5);
    

    其实就是exactly what MDN says in describing named function expressions

    如果要在函数体内引用当前函数, 您需要创建一个命名函数表达式。那么这个名字 仅限于函数体(范围)。这也避免了使用 非标准 arguments.callee 属性。

    【讨论】:

    • arguments.callee 在严格模式下是不允许的。
    • @jfriend00: ...我并不是说它是 :) 即使 not 在严格模式下它也已被弃用/不赞成。 Afaic 这正是命名函数表达式成为事物的原因 - 允许递归 - 我的意思是命名的函数标识符仅在函数范围内!
    • 我只是添加了一条额外的信息作为不使用arguments.callee 的另一个原因。无需听起来防守。
    • 我不是听起来防守,我是正在防守! #fairenough #pointtaken
    【解决方案2】:

    所有“跳舞”的原因很简单。

    命名函数表达式的标识符需要在函数范围内而不是外部可用。

    typeof f; // undefined
    
    (function f() {
      typeof f; // function
    })();
    

    如何使f 在函数中可用?

    您不能在外部词法环境中创建绑定,因为 f 不应该在外部可用。而且你不能在内部变量环境中创建绑定,因为......它还没有创建;该函数在实例化时尚未执行,因此从未发生过带有 NewDeclarativeEnvironment 的 10.4.3(输入函数代码)步骤。

    因此,这样做的方法是创建一个中间词法环境,直接从当前词法环境“继承”,然后将其作为 [[Scope]] 传递给新创建的函数。

    如果我们将13中的步骤分解成伪代码,你可以清楚地看到这一点:

    // create new binding layer
    funcEnv = NewDeclarativeEnvironment(current Lexical Environment)
    
    envRec = funcEnv
    // give it function's identifier
    envRec.CreateImmutableBinding(Identifier)
    
    // create function with this intermediate binding layer
    closure = CreateNewFunction(funcEnv)
    
    // assign newly created function to an identifier within this intermediate binding layer
    envRec.InitializeImmutableBinding(Identifier, closure)
    

    所以f 中的词法环境(例如在解析标识符时)现在看起来像这样:

    (function f(){
    
      [global environment] <- [f: function(){}] <- [Current Variable Environment]
    
    })();
    

    使用匿名函数,它看起来像这样:

    (function() {
    
      [global environment] <- [Current Variable Environment]
    
    })();
    

    【讨论】:

    • 还有其他细微之处。函数表达式名称绑定是只读的,但您仍然可以在函数表达式的主体中使用相同的名称声明 var 或函数。描述这种语义(记住这只是一个规范)需要使用额外的环境记录。
    • 有趣。但是为什么这需要额外的环境记录呢?例如,如果要在步骤 5 之前的函数声明 (10.5) 期间创建 NFE 的标识符绑定,则源中的任何 var/function 声明都将覆盖 NFE 的绑定 (5f) 而不是隐藏它。效果几乎一样,不是吗?
    猜你喜欢
    • 2021-04-26
    • 2020-11-22
    • 1970-01-01
    • 1970-01-01
    • 2014-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多