Как `this` работает в параметрах по умолчанию?

Итак... 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, но я не понимаю. Почему bar не принимает x из foo при вызове внутри foo?

<суб > 1 - Да, я знаю, что это ES2015.
2 - Пример A
3 - Пример B

суб >

Ответ 1

Мой вопрос: как this играет в это? Я не понимаю. Действительно ли они оцениваются во время разговора?

Да, инициализаторы параметров оцениваются во время разговора. Он сложный, но шаги в основном следующие:

  • В стек устанавливается новый контекст выполнения с новой средой в области "закрытия" вызываемой функции
  • При необходимости он thisBinding инициализируется
  • Созданы декларации:
    • Создаются взаимозаменяемые привязки для имен параметров
    • При необходимости объект arguments создается связанный
    • Связи итеративно инициализируются из списка аргументов (включая все деструктуризации и т.д.)
      В ходе этого, инициализаторы оцениваются
    • Если были задействованы какие-либо блокировки, добавлена ​​новая среда.
    • Создаются взаимозаменяемые привязки для переменных, объявленных в теле функции (если они уже не выполняются именами параметров) и инициализируются с помощью undefined
    • Созданы привязки для let и const переменных в теле функции
    • Связи для функций (из объявлений функций в теле) инициализируются с помощью инстанцируемых функций
  • Наконец, объект оценивается.

Таким образом, инициализаторы параметров имеют доступ к this и arguments вызова, к ранее инициализированным другим параметрам и всему, что находится в их "верхней" лексической области. На них не влияют переменные, объявленные в теле функции (хотя на них влияют все остальные параметры, даже если они находятся в их временной мертвой зоне).

что об этом:

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

Я не понимаю. Почему bar не принимает x из foo при вызове внутри foo?

Потому что x - это локальная переменная, к которой bar не имеет доступа. Нам так повезло, что они не динамически охвачены! Инициализаторы параметров не оцениваются на сайте вызова, а внутри области вызываемой функции. В этом случае идентификатор x разрешен для глобальной переменной x.

Ответ 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 при его назначении. Затем он должен работать, как ожидалось.

Извините, если это неясно; дайте мне знать в комментариях, и я буду рад прояснить!:)