объявляя переменную дважды в IIFE - PullRequest
20 голосов
/ 07 апреля 2019

Я прошел эту веселую викторину в интернете.

console.log((function(x, f = (() => x)){
  var x;
  var y = x;
  x = 2;
  return [x, y, f()]
})(1))

и выбор был:

  1. [2,1,1]

  2. [2, undefined, 1]

  3. [2, 1, 2]

  4. [2, не определено, 2]

Я выбрал решение 2 TBH, основываясь на том, что x был переопределен, y был объявлен и определен без значения, и что f имеет другую область видимости, поэтому получает глобальную область памяти x, отличную от функции x памяти.

Однако я попробовал это в jsbin.com

и я обнаружил, что это было решение 1, хотя я не был уверен, почему это произошло, я испортил тело функции и удалил var x из тела функции, я обнаружил, что ответ изменился на # 3, что имеет смысл как x значение изменилось и, следовательно, оно показало x и f как 2, а y как 1, что было объявлено глобально.

но я все еще не могу понять, почему он показывает 1 вместо неопределенного.

Ответы [ 2 ]

24 голосов
/ 07 апреля 2019

но все же я не могу понять, почему он показывает 1 вместо неопределенного.

Это не только вы.Это глубокая, темная часть спецификации.: -)

Ключ в том, что есть два x s.Да, действительно.Это параметр x, а есть переменная x.

Список параметров, содержащий выражений (например, f 's значение по умолчанию) имеет собственную область действия , отдельную от области действия тела функции.Но до того, как списки параметров, возможно, имели выражения, наличие var x в функции с параметром x не имело никакого эффекта (x все еще оставалось параметром со значением параметра).Таким образом, чтобы сохранить это, когда есть список параметров с выражениями в нем, создается отдельная переменная, и значение параметра копируется в переменную в начале тела функции.Что является причиной этого , казалось бы, странного (нет, не только, казалось бы) странного поведения.(Если вы любите углубляться в спецификацию, это копирование с шага 28 из FunctionDeclarationInstantiation .)

Поскольку значение по умолчанию f, () => x, равносозданный в области списка параметров, он ссылается на параметр x, а не на переменную.

Итак, первое решение, [2, 1, 1], является правильным, поскольку:

  • 2 было присвоено переменной x в теле функции.Таким образом, в конце функции переменная x равна 2.
  • 1 была назначена для y из переменной x до того, как x получила значение 2,поэтому в конце функции y равен 1.
  • Значение параметра * x никогда не менялось, поэтому f() приводит к 1 приконец функции

Это , как если бы код был написан так (вместо этого я удалил ненужные скобки и добавил пропущенные точки с запятой):

console.log(function(param_x, f = () => param_x) {
  var var_x = param_x;
  var y = var_x;
  var_x = 2;
  return [var_x, y, f()];
}(1));

... Я удалил var x из тела функции и обнаружил, что ответ изменился на # 3 ...

# 3 - [2, 1, 2].Это правильно, потому что когда вы удаляете var x из функции, остается только один x, параметр (наследуемый телом функции из списка параметров).Таким образом, присвоение 2 x изменяет значение параметра, которое возвращает f.

Взяв более ранний пример с param_x и var_x, вот как это выглядит, если вы удалите var x; из него:

console.log(function(param_x, f = () => param_x) {
  var y = param_x;
  param_x = 2;
  return [param_x, y, f()];
}(1));

Вот аннотированное описание исходного кода (с удалением лишних скобок и добавлением пропущенных точек с запятой):

//                   /---- the parameter "x"
//                   v  vvvvvvvvvvv--- the parameter "f" with a default value
console.log(function(x, f = () => x) {
  var x;      // <=== the *variable* x, which gets its initial value from the
              //      parameter x
  var y = x;  // <=== sets y to 1 (x's current value)
  x = 2;      // <=== changes the *variable* x's value to 2
  //      +---------- 2, because this is the *variable* x
  //      |  +------- 1, because this is the variable y
  //      |  |   +--- 1, because f is () => x, but that x is the *parameter* x,
  //      |  |   |       whose value is still 1
  //      v  v  vvv
  return [x, y, f()];
}(1));

Последнее замечание относительно вашего заголовка:

объявление переменной дважды в IIFE

Переменная объявляется только один раз.Другая вещь - это параметр, а не переменная.Различие редко важно ... это один из тех редких моментов.: -)

2 голосов
/ 07 апреля 2019

Сложность этого кода в том, что функция => создается как часть выражения значения параметра по умолчанию.В выражениях значений параметров по умолчанию область включает в себя параметры, объявленные слева, что в данном случае включает параметр x.Таким образом, по этой причине x в функции => фактически является первым параметром.

Функция вызывается только с одним параметром, 1, поэтому, когда вызывается функция =>, эточто он возвращает, давая [2, 1, 1].

Декларация var x, как указывает г-н Краудер, имеет (несколько странный, по крайней мере для меня) эффект создания нового x в области действия функции, в которую копируется значение параметра x.Без него есть только один (параметр).

...