Как `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
при его назначении. Затем он должен работать, как ожидалось.
Извините, если это неясно; дайте мне знать в комментариях, и я буду рад прояснить!:)