`this` 在默认参数中如何工作?

How does `this` work in default parameters?

所以... ES6¹(刚好在几个小时前被标准化)为类似于 PHP、[=44= 中的函数带来了 默认参数 ] 等。我可以做类似的事情:

function foo (bar = 'dum') {
    return bar;
}

foo(1); // 1
foo(); // 'dum'
foo(undefined); // 'dum'

MDN 说参数的默认值是在调用时计算的。这意味着每次我调用函数时,表达式 'dum' 都会被再次求值(除非实现做了一些我们不关心的奇怪优化)。

我的问题是,this 如何发挥作用?

let x = {
  foo (bar = this.foo) {
    return bar;
  }
}

let y = {
  z: x.foo
}

x.foo() === y.z(); // what?

babel 转译器目前将其计算为 false,但我不明白。如果他们真的在调用时被评估,那么这个呢:

let x = 'x from global';

function bar (thing = x) {
  return thing;
}

function foo () {
  let x = 'x from foo';
  return bar();
}

bar() === foo(); // what?

babel 转译器当前评估³它为 true,但我不明白。为什么 barfoo 内部调用时不从 foo 获取 x

1 - 是的,我知道它是 ES2015。
2 - Example A
3 - Example B

当他们说 "evaluated at call time" 时,我认为他们指的是 call-by-name 表达式。下面是 babel 如何输出你的第三个例子:

'use strict';

var x = 'x from global';

function bar() {
  var thing = arguments[0] === undefined ? x : arguments[0];

  return thing;
}

function foo() {
  var x = 'x from foo';
  return bar();
}

bar() === foo(); // what?

由于var x是在bar的词法范围内从全局范围继承的,也就是它的使用范围。

现在,考虑以下问题:

let i = 0;

function id() {
  return i++;
}

function bar (thing = id()) {
  return thing;
}

console.info(bar() === bar()); // false

转译为

"use strict";

var i = 0;

function id() {
  return i++;
}

function bar() {
  var thing = arguments[0] === undefined ? id() : arguments[0];

  return thing;
}

console.info(bar() === bar()); // false

注意这里,id 是如何在 内部 函数中被调用的,而不是在 定义函数时被缓存和记忆的 ,因此是按名称调用而不是按值调用。

所以在您的第二个示例中 的行为 实际上是正确的。没有 y.foo 并且由于 this 在 Javascript 中是 动态范围 (即它根据给定函数调用的接收者而变化),当y.z() 查找 this.foo,它将在 y 中查找它,因此 y.z() 将 return undefined,而 x.foo() 将只是 return foo 函数本身。

如果您想要绑定到接收器,您可以在分配时将foo绑定到x。然后它应该按预期工作。

抱歉,如果有任何不清楚的地方;在评论中让我知道,我很乐意澄清! :)

My question is, how does this play into this? I don't get it. Are they are really evaluated at call time?

是的,参数初始值设定项在调用时计算。 It's complicated,不过步骤基本如下:

  1. 栈上建立了一个new execution context
    在被调用函数的 "closure scope" 中带有 new environment
  2. 如果需要的话,就是thisBinding is initialised
  3. Declarations are instantiated:
    1. 已创建参数名称的可变绑定
    2. 如有必要,将创建一个 arguments 对象并绑定
    3. 参数列表中的 bindings are iteratively initialised(包括所有解构等)
      在此过程中,initialisers are evaluated
    4. 如果涉及任何闭包,则会插入一个新环境
    5. 创建函数体中声明的变量的可变绑定(如果尚未通过参数名称完成)并使用 undefined
    6. 初始化
    7. 函数体中 letconst 变量的绑定已创建
    8. 函数的绑定(来自主体中的函数声明)是用实例化函数初始化的
  4. 终于body of the function is evaluated.

因此参数初始化器确实可以访问调用的 thisarguments,可以访问先前初始化的其他参数,以及它们 "upper" 词法范围内的所有内容。它们不受函数体中声明的变量的影响(尽管它们受所有其他参数的影响,即使在它们的暂时死区中)。

what about this:

function bar (thing = x) {}
{
  let x = 'x from foo';
  return bar();
}

I don't get it. Why does bar not take the x from foo when called inside foo?

因为xbar无权访问的局部变量。我们很幸运,他们 not dynamically scoped!参数初始值设定项不在调用站点评估,而是在被调用函数的范围内评估。在这种情况下,x 标识符被解析为全局 x 变量。