Это верно и возвращает строку "10"
в JavaScript ( больше примеров здесь ):
console.log(++[[]][+[]]+[+[]])
Почему? Что здесь происходит?
Это верно и возвращает строку "10"
в JavaScript ( больше примеров здесь ):
console.log(++[[]][+[]]+[+[]])
Почему? Что здесь происходит?
Ответы:
Если мы разделим это, беспорядок равен:
++[[]][+[]]
+
[+[]]
В JavaScript это правда +[] === 0
. +
преобразует что-то в число, и в этом случае оно сводится к +""
или 0
(см. подробности спецификации ниже).
Следовательно, мы можем упростить его ( ++
имеет приоритет над +
):
++[[]][0]
+
[0]
Потому что [[]][0]
означает: получить первый элемент [[]]
, это правда, что:
[[]][0]
возвращает внутренний массив ( []
). Из-за ссылок это неправильно [[]][0] === []
, но давайте вызовем внутренний массив, A
чтобы избежать неправильных обозначений.
++
перед его операндом означает «увеличить на единицу и вернуть увеличенный результат». Так ++[[]][0]
что эквивалентно Number(A) + 1
(или +A + 1
).
Опять же, мы можем упростить беспорядок в нечто более разборчивое. Давайте заменим []
обратно A
:
(+[] + 1)
+
[0]
Прежде чем +[]
можно будет привести массив в число 0
, его нужно сначала привести в строку, то есть ""
снова. Наконец, 1
добавляется, что приводит к 1
.
(+[] + 1) === (+"" + 1)
(+"" + 1) === (0 + 1)
(0 + 1) === 1
Давайте упростим это еще больше:
1
+
[0]
Кроме того, это верно в JavaScript: [0] == "0"
потому что он объединяет массив с одним элементом. Присоединение объединит элементы, разделенные ,
. С одним элементом вы можете сделать вывод, что эта логика приведет к самому первому элементу.
В этом случае +
видит два операнда: число и массив. Сейчас он пытается принудить двух к тому же типу. Сначала массив приведен в строку "0"
, затем номер приведен в строку ( "1"
). Number +
String ===
String .
"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 ::: [empty] равно 0.
Так +"" === 0
и так +[] === 0
.
true
случае, если значение и тип совпадают. 0 == ""
возвращает true
(то же самое после преобразования типа), но 0 === ""
есть false
(не те же типы).
1 + [0]
, а не "1" + [0]
потому, что ++
оператор prefix ( ) всегда возвращает число. См. Bclary.com/2004/11/07/#a-11.4.4
++[[]][0]
возвращает действительно 1
, но ++[]
выдает ошибку. Это замечательно, потому что, похоже ++[[]][0]
, сводится к ++[]
. Возможно, у вас есть идеи, почему ++[]
выдает ошибку, а ++[[]][0]
нет?
PutValue
вызове (в терминологии ES3, 8.7.2) в операции префикса. PutValue
требует ссылки, тогда []
как само по себе выражение не создает ссылку. Выражение, содержащее ссылку на переменную (скажем, мы определили ранее, var a = []
затем ++a
работает) или доступ к свойству объекта (например, [[]][0]
), создает ссылку . Проще говоря, префиксный оператор не только создает значение, ему также нужно где-то поместить это значение.
var a = []; ++a
, a
равно 1. После выполнения ++[[]][0]
, массив , созданный [[]]
выражением теперь содержит только номер 1 по индексу 0. ++
требует Reference , чтобы сделать это.
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]
Тогда у нас есть конкатенация строк
1+[0].toString() = 10
===
, чем =>
?
Нижеследующее адаптировано из поста в блоге, отвечающего на этот вопрос, который я разместил, когда этот вопрос еще был закрыт. Ссылки на (HTML-копию) спецификации ECMAScript 3, по-прежнему являются базовыми для JavaScript в современных широко используемых веб-браузерах.
Во-первых, комментарий: такого рода выражения никогда не появятся в какой-либо (вменяемой) производственной среде и могут использоваться только в качестве упражнения для определения того, насколько хорошо читатель знает грязные грани JavaScript. Общий принцип, что операторы JavaScript неявно преобразуют между типами, полезен, как и некоторые из общих преобразований, но большая часть деталей в этом случае - нет.
Выражение ++[[]][+[]]+[+[]]
может поначалу выглядеть довольно внушительно и неясно, но на самом деле его относительно легко разделить на отдельные выражения. Ниже я просто добавил скобки для ясности; Я могу заверить вас, что они ничего не меняют, но если вы хотите проверить это, не стесняйтесь читать об операторе группировки . Таким образом, выражение может быть более четко написано как
( ++[[]][+[]] ) + ( [+[]] )
Разбивая это, мы можем упростить, наблюдая, что +[]
оценивает 0
. Для того, чтобы удовлетворить себя , почему это так, проверьте унарный оператор + и следовать слегка извилистому следу , который заканчивается с ToPrimitive преобразования пустого массива в пустую строку, которая затем , наконец , превращается в 0
по ToNumber . Теперь мы можем заменить 0
каждый экземпляр +[]
:
( ++[[]][0] ) + [0]
Уже проще. Что касается ++[[]][0]
, это комбинация префиксного оператора приращения ( ++
), литерала массива, определяющего массив с единственным элементом, который сам является пустым array ( [[]]
) и свойством accessor ( [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"
Этот оценивает то же самое, но немного меньше
+!![]+''+(+[])
так это оценивает
+(true) + '' + (0)
1 + '' + 0
"10"
Итак, теперь вы получили это, попробуйте это:
_=$=+[],++_+''+$
"10"
+ [] оценивается как 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.
++[[]][+[]]+[+[]] = 10
++[''][0] + [0] : First part is gives zeroth element of the array which is empty string
1+0
10
[]
это не эквивалентно ""
. Сначала элемент извлекается, затем конвертируется ++
.
Шаг за шагом, +
превратить значение в число, и если вы добавите в пустой массив +[]
... так как он пуст и равен 0
, он будет
Итак, оттуда, теперь посмотрите на ваш код, это ++[[]][+[]]+[+[]]
...
И между ними есть плюс ++[[]][+[]]
+[+[]]
Так что они [+[]]
вернутся, [0]
поскольку у них есть пустой массив, который преобразуется 0
в другой массив ...
Итак, представьте, что первое значение представляет собой 2-мерный массив с одним массивом внутри ... поэтому [[]][+[]]
будет равно, [[]][0]
которое вернет []
...
И в конце ++
преобразуйте его и увеличьте до 1
...
Таким образом, вы можете себе представить, 1
+ "0"
будет "10"
...
+[]
приведение пустого массива к0
... затем