JavaScript: ПОРЯДОК выполнения функции

Я изучаю JavaScript, но многого не понимаю. На одном онлайн-викторине по JavaScript появился следующий вопрос:
Что следующий код JavaScript выведет на консоль:

const a = {};
const b = () => a.a = () => {};
const c = c => '' + c + a.a(b());
const d = console.log.bind(console);
const e = (e => () => d(c(e++)))(0);

try{
  e();
}catch(a){
  e();
}

Мне потребовалось некоторое время, чтобы понять, что означает каждая переменная (здесь константа). Я начал анализировать код с блока e() внутри try. Итак, e представляет замыкание, а это значит, что функция d будет вызываться с аргументом c(0), а e станет 1. Как я понял, здесь d в основном представляет собой функцию console.log (но я не могу понять, почему они использовали bind?).

На данный момент я знаю, что сначала будет выполнено c(0), а затем результат будет записан в консоль, верно? Давайте посмотрим на функцию c. Он возвращает первый аргумент, преобразованный в строку, и конкатенированный результат a.a(b()). Итак, a.a(b()) будет выполнено первым, я прав? Но проблема в том, что a.a не является функцией, она не определена, поэтому будет выдана ошибка, и мы перейдем к catch.

Теперь в блоке catch все должно быть так же, поэтому a.a все еще не является функцией и должна выдаваться ошибка ссылки. Но меня удивило, когда я увидел, что никакой ошибки не выдается, но консоль на самом деле регистрирует 1undefined. Почему? Как?

Хорошо, немного подумав, я понял, что, возможно, при вызове a.a(b()) сначала выполняется b(). Следуя моему предположению, тогда функция b присваивает ссылку на функцию, которая ничего не делает со свойством a объекта a, верно? Но тогда a.a ЯВЛЯЕТСЯ функцией, и она будет выполняться в блоке try, а 0undefined будет логироваться.

Однако ни одно из этих двух предположений не верно. Главный вопрос здесь заключается в том, что выполняется первым? Если мы позвоним someObject.propertyWhichIsNotAFunction(somethingWhichMakesItAFunction), что произойдет? Что выполняется в первую очередь? Кажется, что в блоке try сначала выполняется одно, а в блоке catch другое. Это действительно не имеет смысла для меня. Любые объяснения?


person Community    schedule 06.12.2016    source источник


Ответы (1)


Это очень похоже на причину Почему значение foo.x не определено в foo.x = foo = {n: 2}? , просто немного сложнее, поскольку оно зависит от того, когда во время выполнения возникает ошибка TypeError.

В основном происходит следующее:

  1. a.a оценивается, чтобы выяснить, какая функция будет вызываться позже на шаге 3.
  2. b() оценивается, так как это аргумент функции, и его возвращаемое значение становится фактическим аргументом того, что оценивается a.a.
  3. Независимо от того, что a.a оценивается на шаге 1, выполняется с возвращаемым значением b() в качестве аргумента.

Соответствующая часть спецификации находится здесь: https://www.ecma-international.org/ecma-262/7.0/index.html#sec-function-calls

Обратите внимание, что первое, что происходит при вызове функции:

  1. Пусть ref будет результатом вычисления MemberExpression.
  2. Пусть func будет ? ПолучитьЗначение(ссылка).

Это означает, что вычисляется a.a, а функция, на которую он ссылается, называется func. В первый раз a.a оценивается как undefined, поэтому func равно undefined, и на шаге 2 https://www.ecma-international.org/ecma-262/7.0/index.html#будетвыданTypeError.sec-evaluatedirectcall , который после ArgumentListEvaluation(arguments), который вызывает b() и присваивает новое значение a.a, но не после значения func.

person Paul    schedule 06.12.2016
comment
Это означает, что если a.a не является функцией, то независимо от того, что происходит при оценке ее аргументов, должна быть выдана ошибка ссылки (кроме случаев, когда сама оценка выдает ошибку)? Ну, это довольно странно. Я думал, что JavaScript предназначен для того, чтобы избежать ненужных вычислений, таких как f1() && f2(), если f1() оценивается как false, то f2() не будет выполняться. Какова логика оценки аргументов ссылки на функцию, которую мы точно знаем, что не можем вызвать? - person ; 07.12.2016
comment
@ manga171 У аргументов могут быть побочные эффекты, как у b(). Спецификация фокусируется на правильности больше, чем на оптимизации, и оценка короткого замыкания f1() && f2() также может иметь последствия для правильности, например, isReady() && go() гарантированно не вызовет go(), если isReady() возвращает false, нет такой выгоды от того, чтобы не оценивать аргументы функции перед вызовом функции ; Более интересен противоположный вопрос: какова логика оценки ссылки на функцию перед оценкой аргументов. - person Paul; 07.12.2016
comment
@ manga171 Ответ на этот вопрос, вероятно, заключается в том, что спецификация не идеальна, и внесение изменений, влияющих на существующий код, имеет большое значение. Легко показать, что Let argList be ? ArgumentListEvaluation(Arguments). происходит всегда, поэтому можно было бы вытащить его из любых условий и сделать его первым, что происходит при оценке функции, но это изменило бы поведение кода в вашем вопросе, поскольку оно никогда не вызовет исключение; Я думаю, что это выведет 0undefined. - person Paul; 07.12.2016
comment
@Paulpro Ссылка на функцию оценивается перед аргументами, чтобы получить поведение оценки слева направо. Почему ошибка типа не возникает до того, как аргументы будут оценены, это хороший вопрос. - person Bergi; 07.12.2016
comment
@Bergi Я думаю, что ошибка TypeError, выдаваемая после оценки аргументов, имеет смысл только потому, что она согласуется с тем, что произойдет, если сама функция выдаст ошибку сразу после вызова; применяются любые побочные эффекты, вызванные оценкой аргументов. - person Paul; 07.12.2016