【问题标题】:How does `this` work in default parameters?`this` 在默认参数中如何工作?
【发布时间】:2015-06-17 20:20:47
【问题描述】:

所以... ES6¹(恰好在几个小时前被标准化)为类似于 PHP、Python 等中的函数带来了 默认参数。我可以执行以下操作:

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

【问题讨论】:

  • 是否有关于董事会接受ES6最终草案的公告?
  • 你的比较结果基本上是x.foo === y.foo,这显然是false,因为在这两种情况下你都在调用foo函数,但在第一种情况下,this === x和第二种情况下,this === y。问题似乎真的是,为什么本质上是let x = {foo(){ return this; }}; let y = {z: x.foo}; y.foo() === y。答案是因为y.foo()y.foo.call(y) 是一样的。这就是this 的定义方式。

标签: javascript ecmascript-6 default-parameters


【解决方案1】:

我的问题是,this 如何参与其中?我不明白。是否真的在通话时进行了评估?

是的,参数初始化器在调用时进行评估。 It's complicated,不过步骤基本如下:

  1. 在堆栈上建立了一个new execution context
    在被调用函数的“闭包范围”中带有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. 在函数体中创建letconst 变量的绑定
    7. 函数的绑定(来自函数体中的函数声明)使用实例化函数进行初始化
  4. 最后是body of the function is evaluated

所以参数初始化器确实可以访问调用的thisarguments,访问之前初始化的其他参数,以及它们“上层”词法范围内的所有内容。它们不受函数体中声明的变量的影响(尽管它们受到所有其他参数的影响,即使在它们的临时死区中也是如此)。

这个呢:

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

我不明白。为什么bar 在调用时不从foo 中获取xfoo里面?

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

【讨论】:

  • 如果有人关心,我们在chat 对此进行了进一步讨论,如果您想阅读的话:)
【解决方案2】:

当他们说“在调用时评估”时,我认为他们指的是 按名称调用 表达式。以下是 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() 将返回undefined,而x.foo() 将只返回foo 函数本身。

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

抱歉,如果有任何不清楚的地方;在 cmets 中告诉我,我很乐意澄清! :)

【讨论】:

  • "并且由于这是在 Javascript 中动态限定的",这就是我的问题。使用this.something 不应该特别注意,因为this.something 并不神奇,只有this 是。我将在问题中添加一个清晰的示例来澄清这一点,感谢您的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-06
  • 2012-08-01
  • 2016-07-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多