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

1659

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

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

Почему? Что здесь происходит?

SapuSeven
источник
446
Начните с понимания того, что +[]приведение пустого массива к 0... затем
потеряйте
14
Связанный stackoverflow.com/questions/4170978/explain-why-this-works .
Юхо Вепсяляйнен
10
Взгляните на wtfjs.com - там есть несколько вещей с объяснением.
ThiefMaster
3
@deceze, где ты учишь такие вещи? Какие книги? Я изучаю JS из MDN, и они не учат этим вещам
Сиддхарт Теварил
6
@SiddharthThevaril Точно так же, как вы только что сделали: кто-то где-то написал об этом, и я случайно прочитал это.
deceze

Ответы:

2072

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

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

В 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 оценивается следующим образом:

  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 ::: [empty] равно 0.

Так +"" === 0и так +[] === 0.

pimvdb
источник
8
@harper: Это строгая проверка на равенство, то есть она возвращает только в том trueслучае, если значение и тип совпадают. 0 == ""возвращает true(то же самое после преобразования типа), но 0 === ""есть false(не те же типы).
pimvdb
41
Частично это не правильно. Выражение сводится к 1 + [0], а не "1" + [0]потому, что ++оператор prefix ( ) всегда возвращает число. См. Bclary.com/2004/11/07/#a-11.4.4
Тим Даун
6
@ Tim Down: Вы совершенно правы. Я пытаюсь это исправить, но, пытаясь это сделать, я нашел что-то еще. Я не уверен, как это возможно. ++[[]][0]возвращает действительно 1, но ++[]выдает ошибку. Это замечательно, потому что, похоже ++[[]][0], сводится к ++[]. Возможно, у вас есть идеи, почему ++[]выдает ошибку, а ++[[]][0]нет?
pimvdb
11
@pimvdb: Я уверен, что проблема в PutValueвызове (в терминологии ES3, 8.7.2) в операции префикса. PutValueтребует ссылки, тогда []как само по себе выражение не создает ссылку. Выражение, содержащее ссылку на переменную (скажем, мы определили ранее, var a = []затем ++aработает) или доступ к свойству объекта (например, [[]][0]), создает ссылку . Проще говоря, префиксный оператор не только создает значение, ему также нужно где-то поместить это значение.
Тим Даун
13
@pimvdb: Таким образом , после выполнения var a = []; ++a, aравно 1. После выполнения ++[[]][0], массив , созданный [[]]выражением теперь содержит только номер 1 по индексу 0. ++требует Reference , чтобы сделать это.
Тим Даун
124
++[[]][+[]] => 1 // [+[]] = [0], ++0 = 1
[+[]] => [0]

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

1+[0].toString() = 10
Shef
источник
7
Разве не было бы яснее написать ===, чем =>?
Матеин Улхак
61

Нижеследующее адаптировано из поста в блоге, отвечающего на этот вопрос, который я разместил, когда этот вопрос еще был закрыт. Ссылки на (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". Почему это происходит, оставлено в качестве упражнения для читателя ...

Тим Даун
источник
24

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

++[[]][+[]]+[+[]] = "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"
renatoluna
источник
13

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

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

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

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

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

_=$=+[],++_+''+$
Влад Шлосберг
источник
Ну нет, это все равно оценивается в «10». Однако это делает это по-другому. Попробуйте оценить это в инспекторе javascript, таком как chrome или что-то в этом роде.
Влад Шлосберг
_ = $ = + [], ++ _ + '' + $ -> _ = $ = 0, ++ _ + '' + $ -> _ = 0, $ = 0, ++ _ + '' + $ -> ++ 0 + '' + 0 -> 1 + '' + 0 -> '10' // Yei: v
LeagueOfJava
Этот оценивает то же самое, но это даже меньше, чем ваш:"10"
ADJenks
7

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

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

Eskat0n
источник
4

Возможно, кратчайшие возможные способы вычисления выражения в «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]"
оборота AsyncMind
источник
1
  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
Правин Ведант
источник
1
Ответ запутан / запутан, IOW неправильный. []это не эквивалентно "". Сначала элемент извлекается, затем конвертируется ++.
PointedEars
1

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

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

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

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

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

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

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

Почему возвращается строка «10»?

Алиреза
источник