Почему ++[[]][+[]]+[+[]] возвращает строку "10"?

Это верно и возвращает строку "10" в JavaScript ( больше примеров здесь):

console.log(++[[]][+[]]+[+[]])

Зачем? Что здесь происходит?

10 ответов

Решение

Если мы разделим это, беспорядок равен:

++[[]][+[]]
+
[+[]]

В JavaScript верно, что +[] === 0, + преобразует что-то в число, и в этом случае оно сводится к +"" или же 0 (см. технические характеристики ниже).

Поэтому мы можем упростить это (++ имеет приоритет над +):

++[[]][0]
+
[0]

Так как [[]][0] означает: получить первый элемент из [[]], правда, что:

  • [[]][0] возвращает внутренний массив ([]). Из-за ссылок неправильно говорить [[]][0] === [], но давайте назовем внутренний массив A чтобы избежать неправильной записи.
  • ++[[]][0] == A + 1, поскольку ++ означает "увеличение на единицу".
  • ++[[]][0] === +(A + 1); другими словами, это всегда будет число (+1 не обязательно возвращает число, тогда как ++ всегда делает - спасибо Тим Даун за указание на это).

Опять же, мы можем упростить беспорядок до чего-то более разборчивого. Давайте заменим [] назад для A:

+([] + 1)
+
[0]

В JavaScript это также верно: [] + 1 === "1", так как [] == "" (присоединение к пустому массиву), так:

  • +([] + 1) === +("" + 1), а также
  • +("" + 1) === +("1"), а также
  • +("1") === 1

Давайте упростим это еще больше:

1
+
[0]

Кроме того, это верно в JavaScript: [0] == "0"потому что он объединяет массив с одним элементом. Присоединение объединит элементы, разделенные ,, С одним элементом вы можете сделать вывод, что эта логика приведет к самому первому элементу.

Итак, в конце мы получаем (число + строка = строка):

1
+
"0"

=== "10" // Yay!

Детали спецификации для +[]:

Это довольно лабиринт, но делать +[]сначала он преобразуется в строку, потому что это то, что + говорит:

11.4.6 Унарный + Оператор

Унарный оператор + преобразует свой операнд в числовой тип.

Производство UnaryExpression: + UnaryExpression оценивается следующим образом:

  1. Пусть expr будет результатом вычисления UnaryExpression.

  2. Возврат ToNumber(GetValue(expr)).

ToNumber() говорит:

объект

Примените следующие шаги:

  1. Пусть primValue будет ToPrimitive(входной аргумент, строка подсказки).

  2. Возврат ToString(primValue).

ToPrimitive() говорит:

объект

Вернуть значение по умолчанию для объекта. Значение объекта по умолчанию извлекается путем вызова внутреннего метода [[DefaultValue]] объекта, передавая необязательную подсказку PreferredType. Поведение внутреннего метода [[DefaultValue]] определяется этой спецификацией для всех собственных объектов ECMAScript в 8.12.8.

[[DefaultValue]] говорит:

8.12.8 [[DefaultValue]] (подсказка)

Когда внутренний метод [[DefaultValue]] для O вызывается с hint String, предпринимаются следующие шаги:

  1. Пусть toString будет результатом вызова внутреннего метода [[Get]] объекта O с аргументом "toString".

  2. Если IsCallable(toString) равен true, тогда

а. Пусть str будет результатом вызова внутреннего метода [[Call]] для toString с O в качестве значения this и пустым списком аргументов.

б. Если str является примитивным значением, вернуть str.

.toString массива говорит:

15.4.4.2 Array.prototype.toString ()

Когда вызывается метод toString, предпринимаются следующие шаги:

  1. Пусть массив будет результатом вызова ToObject для значения this.

  2. Пусть func будет результатом вызова внутреннего метода [[Get]] для массива с аргументом "join".

  3. Если IsCallable(func) имеет значение false, пусть func будет стандартным встроенным методом Object.prototype.toString (15.2.4.2).

  4. Вернуть результат вызова внутреннего метода [[Call]] для func, предоставив массив в качестве значения this и пустой список аргументов.

Так +[] сводится к +"", так как [].join() === "",

Опять же, + определяется как:

11.4.6 Унарный + Оператор

Унарный оператор + преобразует свой операнд в числовой тип.

Производство UnaryExpression: + UnaryExpression оценивается следующим образом:

  1. Пусть expr будет результатом вычисления UnaryExpression.

  2. Возврат ToNumber(GetValue(expr)).

ToNumber определяется для "" как:

МЗ StringNumericLiteral::: [пусто] равно 0.

Так +"" === 0, и поэтому +[] === 0,

++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

Тогда у нас есть конкатенация строк

1+[0].toString() = 10

Нижеследующее адаптировано из поста в блоге, отвечающего на этот вопрос, который я разместил, когда этот вопрос был еще закрыт Ссылки на (HTML-копию) спецификации ECMAScript 3, по-прежнему являются базовыми для JavaScript в современных широко используемых веб-браузерах.

Во-первых, комментарий: такого рода выражения никогда не появятся в какой-либо (вменяемой) производственной среде и пригодятся только для проверки того, насколько хорошо читатель знает грязные грани JavaScript. Общий принцип, что операторы JavaScript неявно преобразуют между типами, полезен, как и некоторые из общих преобразований, но большая часть деталей в этом случае - нет.

Выражение ++[[]][+[]]+[+[]] может поначалу выглядеть довольно внушительно и неясно, но на самом деле сравнительно легко разбить на отдельные выражения. Ниже я просто добавил скобки для ясности; Я могу заверить вас, что они ничего не меняют, но если вы хотите проверить это, не стесняйтесь читать об операторе группировки. Таким образом, выражение может быть более четко написано как

( ++[[]][+[]] ) + ( [+[]] )

Разбивая это, мы можем упростить, наблюдая, что +[] оценивает 0, Чтобы понять, почему это так, проверьте унарный оператор + и следуйте по слегка извилистому пути, который заканчивается ToPrimitive, преобразующим пустой массив в пустую строку, которая затем окончательно преобразуется в 0 по номеру. Теперь мы можем заменить 0 для каждого экземпляра +[]:

( ++[[]][0] ) + [0]

Уже проще. Что касается ++[[]][0] это комбинация оператора приращения префикса (++), литерал массива, определяющий массив с единственным элементом, который сам является пустым массивом ([[]]) и свойство собственности ([0]) вызывается в массиве, определенном литералом массива.

Итак, мы можем упростить [[]][0] чтобы просто [] и у нас есть ++[], право? На самом деле, это не так, потому что оценка ++[] выдает ошибку, которая поначалу может показаться запутанной. Тем не менее, немного подумать о природе ++ делает это понятным: он используется для увеличения переменной (например, ++i) или свойство объекта (например, ++obj.count). Он не только оценивает значение, но и сохраняет это значение где-то. В случае ++[], ему некуда поставить новое значение (каким бы оно ни было), потому что нет ссылки на свойство объекта или переменную для обновления. В терминах спецификации это покрывается внутренней операцией PutValue, которая вызывается оператором приращения префикса.

Итак, что же ++[[]][0] делать? Ну по логике +[] внутренний массив преобразуется в 0 и это значение увеличивается на 1 чтобы дать нам окончательную стоимость 1, Стоимость имущества 0 во внешнем массиве обновляется до 1 и все выражение оценивается в 1,

Это оставляет нас с

1 + [0]

... который является простым использованием оператора сложения. Оба операнда сначала преобразуются в примитивы, и если любое из значений примитива является строкой, выполняется конкатенация строк, в противном случае выполняется сложение чисел. [0] превращается в "0", поэтому используется конкатенация строк, производящая "10",

И, наконец, что-то, что может быть неочевидным, заключается в том, что toString() или же valueOf() методы Array.prototype изменит результат выражения, потому что оба проверяются и используются, если присутствуют, при преобразовании объекта в примитивное значение. Например, следующее

Array.prototype.toString = function() {
  return "foo";
};
++[[]][+[]]+[+[]]

... производит "NaNfoo", Почему это происходит, оставлено в качестве упражнения для читателя...

Давайте сделаем это просто:

++[[]][+[]]+[+[]] = "10"

var a = [[]][+[]];
var b = [+[]];

// so a == [] and b == [0]

++a;

// then a == 1 and b is still that array [0]
// when you sum the var a and an array, it will sum b as a string just like that:

1 + "0" = "10"

Этот оценивает то же самое, но немного меньше

+!![]+''+(+[])
  • [] - это преобразованный массив, который преобразуется в 0 при добавлении или вычитании из него, поэтому + [] = 0
  • ! [] - оценивается как ложное, поэтому!! [] оценивается как истинное
  • +!![] - преобразует значение true в числовое значение, которое оценивается как значение true, поэтому в данном случае 1
  • + '' - добавляет к выражению пустую строку, приводя к преобразованию числа в строку
  • + [] - оценивает до 0

так это оценивает

+(true) + '' + (0)
1 + '' + 0
"10"

Итак, теперь вы получили это, попробуйте это:

_=$=+[],++_+''+$
      ++[[]][+[]]+[+[]]
             ^^^
             |
             v
++[[]][+[]]+[0]
       ^^^
       |
       v
++[[]][0]+[0]
  ^^^^^^^
  |
  v
++[]+[0]
     ^^^
     |
     v
++[]+"0"
^^^^
|
v
++0+"0"
^^^
|
v
1+"0"
^^^^^
|
v
"10"

Оператор приводит любой нечисловой операнд через .valueOf(). Если это не возвращает число, тогда .toString() вызывается.

Мы можем проверить это просто с помощью:

Так +[] это то же самое, что принуждение "" в число, которое 0.

Если какой-либо операнд является строкой, тогда + объединяет.

+[] оценивается как 0 [...], затем суммируя (+ операция), что угодно, преобразует содержимое массива в его строковое представление, состоящее из элементов, соединенных запятой.

Все остальное, например получение индекса массива (имеет более высокий приоритет, чем операция +), является порядковым и ничего интересного.

Возможно, кратчайшие возможные способы вычисления выражения в "10" без цифр:

+!+[] + [+[]] // "10"

-~[] + [+[]] // "10"

// ========== Объяснение ========== \\

+!+[]: +[] Преобразует в 0. !0 превращается в true, +true конвертируется в 1.-~[] знак равно -(-1) который 1

[+[]]: +[] Преобразует в 0. [0] массив с одним элементом 0.

Затем JS оценивает 1 + [0]таким образом Number + Array выражение. Тогда спецификация ECMA работает: + Оператор преобразует оба операнда в строку, вызывая toString()/valueOf() функции от базы Object прототип. Он работает как аддитивная функция, если оба операнда выражения являются только числами. Хитрость в том, что массивы легко преобразуют свои элементы в составное строковое представление.

Некоторые примеры:

1 + {} //    "1[object Object]"
1 + [] //    "1"
1 + new Date() //    "1Wed Jun 19 2013 12:13:25 GMT+0400 (Caucasus Standard Time)"

Есть хорошее исключение, что два Objects результаты сложения в NaN:

[] + []   //    ""
[1] + [2] //    "12"
{} + {}   //    NaN
{a:1} + {b:2}     //    NaN
[1, {}] + [2, {}] //    "1,[object Object]2,[object Object]"

Шаг за шагом, + превратить значение в число, и если вы добавите в пустой массив +[]... так как он пуст и равен 0, будет

Итак, оттуда, теперь посмотрите на ваш код, это ++[[]][+[]]+[+[]]...

И между ними есть плюс ++[[]][+[]] + [+[]]

Так что эти [+[]] вернусь [0] поскольку у них есть пустой массив, который преобразуется в 0 внутри другого массива...

Итак, представьте, первое значение - это двумерный массив с одним массивом внутри... [[]][+[]] будет равен [[]][0] который вернется []...

И в конце ++ преобразовать его и увеличить его до 1...

Так что вы можете себе представить, 1 + "0" будет "10"...

  1. Унарный плюс заданная строка преобразуется в число
  2. Оператор приращения заданной строки преобразует и увеличивает на 1
  3. [] == ''. Пустой строки
  4. +'' или +[] оценивает 0.

    ++[[]][+[]]+[+[]] = 10 
    ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 
    1+0 
    10
    
Другие вопросы по тегам