Почему ++[[]][+[]]+[+[]] возвращает строку "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 оценивается следующим образом:
Пусть expr будет результатом вычисления UnaryExpression.
Возврат ToNumber(GetValue(expr)).
ToNumber()
говорит:
объект
Примените следующие шаги:
Пусть primValue будет ToPrimitive(входной аргумент, строка подсказки).
Возврат ToString(primValue).
ToPrimitive()
говорит:
объект
Вернуть значение по умолчанию для объекта. Значение объекта по умолчанию извлекается путем вызова внутреннего метода [[DefaultValue]] объекта, передавая необязательную подсказку PreferredType. Поведение внутреннего метода [[DefaultValue]] определяется этой спецификацией для всех собственных объектов ECMAScript в 8.12.8.
[[DefaultValue]]
говорит:
8.12.8 [[DefaultValue]] (подсказка)
Когда внутренний метод [[DefaultValue]] для O вызывается с hint String, предпринимаются следующие шаги:
Пусть toString будет результатом вызова внутреннего метода [[Get]] объекта O с аргументом "toString".
Если IsCallable(toString) равен true, тогда
а. Пусть str будет результатом вызова внутреннего метода [[Call]] для toString с O в качестве значения this и пустым списком аргументов.
б. Если str является примитивным значением, вернуть str.
.toString
массива говорит:
15.4.4.2 Array.prototype.toString ()
Когда вызывается метод toString, предпринимаются следующие шаги:
Пусть массив будет результатом вызова ToObject для значения this.
Пусть func будет результатом вызова внутреннего метода [[Get]] для массива с аргументом "join".
Если IsCallable(func) имеет значение false, пусть func будет стандартным встроенным методом Object.prototype.toString (15.2.4.2).
Вернуть результат вызова внутреннего метода [[Call]] для func, предоставив массив в качестве значения this и пустой список аргументов.
Так +[]
сводится к +""
, так как [].join() === ""
,
Опять же, +
определяется как:
11.4.6 Унарный + Оператор
Унарный оператор + преобразует свой операнд в числовой тип.
Производство UnaryExpression: + UnaryExpression оценивается следующим образом:
Пусть expr будет результатом вычисления UnaryExpression.
Возврат 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
- [] == ''. Пустой строки
+'' или +[] оценивает 0.
++[[]][+[]]+[+[]] = 10 ++[''][0] + [0] : First part is gives zeroth element of the array which is empty string 1+0 10