Почему 2 == [2] в JavaScript?

164

Я недавно обнаружил это 2 == [2]в JavaScript. Как выясняется, у этой причуды есть несколько интересных последствий:

var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

Точно так же работает следующее:

var a = { "abc" : 1 };
a[["abc"]] === a["abc"]; // this is also true

Еще более странно, это работает также:

[[[[[[[2]]]]]]] == 2; // this is true too! WTF?

Такое поведение выглядит одинаково во всех браузерах.

Есть идеи, почему это языковая функция?

Вот более безумные последствия этой «особенности»:

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

var a = [0];
a == a // true
a == !a // also true, WTF?

Эти примеры были найдены jimbojw http://jimbojw.com известностью, а также walkeyerobot .

Хави
источник

Ответы:

134

Вы можете посмотреть алгоритм сравнения в спецификации ECMA (соответствующие разделы ECMA-262, 3-е издание для вашей проблемы: 11.9.3, 9.1, 8.6.2.6).

Если вы переводите задействованные абстрактные алгоритмы обратно в JS, то, что происходит при оценке, 2 == [2]в основном так:

2 === Number([2].valueOf().toString())

где valueOf()для массивов возвращает сам массив, а строковое представление массива из одного элемента является строковым представлением одного элемента.

Это также объясняет третий пример, поскольку [[[[[[[2]]]]]]].toString()он все еще является строкой 2.

Как вы можете видеть, за кулисами происходит довольно много магии, поэтому я обычно использую только оператор строгого равенства ===.

Первый и второй пример легче следовать, поскольку имена свойств всегда являются строками, поэтому

a[[2]]

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

a[[2].toString()]

который просто

a["2"]

Имейте в виду, что даже числовые ключи обрабатываются как имена свойств (то есть строки), прежде чем произойдет какое-либо волшебство массива.

Christoph
источник
10

Это из-за неявного преобразования типа ==оператора.

[2] преобразуется в число 2 при сравнении с числом. Попробуйте унарный +оператор на [2].

> +[2]
2
Четан С
источник
Другие говорят, что [2] преобразуется в строку. +"2"также номер 2.
Дламблин
1
На самом деле, это не так просто. [2] конвертируется в строку будет ближе, но посмотрите на ecma-international.org/ecma-262/5.1/#sec-11.9.3
neo
10
var a = [0, 1, 2, 3];
a[[2]] === a[2]; // this is true

В правой части уравнения мы имеем a [2], который возвращает тип числа со значением 2. Слева мы сначала создаем новый массив с единственным объектом 2. Затем мы вызываем [( массив находится здесь)]. Я не уверен, оценивает ли это строку или число. 2 или «2». Давайте сначала возьмем строковый регистр. Я считаю, что ["2"] создаст новую переменную и вернет ноль. null! == 2. Итак, давайте предположим, что он фактически неявно преобразуется в число. a [2] вернет 2. 2 и 2 совпадут по типу (так === работает) и значению. Я думаю, что это неявное преобразование массива в число, потому что [значение] ожидает строку или число. Похоже, число имеет более высокий приоритет.

Кстати, мне интересно, кто определяет этот приоритет? Это потому, что [2] имеет число в качестве первого элемента, поэтому он преобразуется в число? Или же при передаче массива в [массив] он пытается сначала превратить массив в число, а затем в строку. Кто знает?

var a = { "abc" : 1 };
a[["abc"]] === a["abc"];

В этом примере вы создаете объект с именем a и членом с именем abc. Правая часть уравнения довольно проста; это эквивалентно a.abc. Это возвращает 1. Левая сторона сначала создает буквенный массив ["abc"]. Затем вы ищете переменную в объекте, передавая только что созданный массив. Поскольку это ожидает строку, он преобразует массив в строку. Теперь это оценивается как ["abc"], что равно 1. 1 и 1 имеют одинаковый тип (поэтому === работает) и равное значение.

[[[[[[[2]]]]]]] == 2; 

Это просто неявное преобразование. === не будет работать в этой ситуации, потому что есть несоответствие типов.

Шон
источник
Ответ на ваш вопрос о приоритете: ==применяется ToPrimitive()к массиву, который, в свою очередь, вызывает его toString()метод, поэтому в действительности вы сравниваете число 2со строкой "2"; сравнение строки и числа выполняется путем преобразования строки
Кристоф
8

В этом ==случае, именно поэтому Даг Крокфорд рекомендует всегда использовать ===. Это не делает никакого неявного преобразования типов.

Для примеров с ===неявное преобразование типов выполняется до вызова оператора равенства.

Дэн Хук
источник
7
[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

Это интересно, это не так, что [0] является и истинным, и ложным, на самом деле

[0] == true // false

Это забавный способ обработки оператора if () в javascript.

Александр Абрамов
источник
4
на самом деле, это забавный способ ==работы; если вы используете фактическое явное приведение (то есть Boolean([0])или !![0]), вы обнаружите, что оно [0]будет оцениваться trueв булевых контекстах так, как должно: в JS рассматривается любой объектtrue
Christoph
6

Массив из одного элемента может рассматриваться как сам элемент.

Это из-за утки. Так как "2" == 2 == [2] и возможно больше.

Олафур Вааге
источник
4
потому что они не совпадают по типу. в первом примере левая сторона вычисляется первой, и они совпадают по типу.
Шон
8
Кроме того, я не думаю, что печатание утки - правильное слово здесь. Это больше связано с неявным преобразованием типов, выполняемым ==оператором перед сравнением.
Четан С
14
это не имеет ничего общего с утиной типизацией, а скорее со слабой типизацией, то есть неявным преобразованием типов
Кристоф
@Chetan: что он сказал;)
Кристоф
2
Что сказали Четан и Кристоф.
Тим Даун
3

Чтобы добавить немного деталей к другим ответам ... при сравнении Arrayс a Number, Javascript преобразует Arrayс parseFloat(array). Вы можете попробовать это самостоятельно в консоли (например, Firebug или Web Inspector), чтобы увидеть, во что Arrayпреобразуются различные значения.

parseFloat([2]); // 2
parseFloat([2, 3]); // 2
parseFloat(['', 2]); // NaN

Для Arrays parseFloatвыполняет операцию Arrayс первым членом и отбрасывает остальные.

Редактировать: Согласно сведениям Кристофа, возможно, он использует более длинную форму внутри, но результаты всегда идентичны parseFloat, так что вы всегда можете использовать parseFloat(array)как сокращенную форму, чтобы точно знать, как она будет преобразована.

eyelidlessness
источник
2

В каждом случае вы сравниваете 2 объекта. Не используйте ==, если вы думаете о сравнении, вы имеете в виду ===, а не ==. == часто может дать безумные эффекты. Ищите хорошие части на языке :)

Jaseem
источник
0

Пояснение к разделу РЕДАКТИРОВАТЬ вопроса:

1-й пример

[0] == false // true
if ([0]) { /* executes */ } // [0] is both true and false!

Первое приведение типа [0] к примитивному значению в соответствии с ответом Кристофа выше, мы имеем «0» ( [0].valueOf().toString())

"0" == false

Теперь введите тип Boolean (false) для Number и затем String ("0") для Number

Number("0") == Number(false)
or  0 == 0 
so, [0] == false  // true

Что касается ifутверждения, если в самом условии if нет явного сравнения, условие оценивается для истинных значений.

Есть только 6 ложных значений : false, null, undefined, 0, NaN и пустая строка "". И все, что не является ложной ценностью, является истинной ценностью.

Поскольку [0] не является ложным значением, это истинное значение, ifоператор оценивается как истинный и выполняет оператор.


2-й пример

var a = [0];
a == a // true
a == !a // also true, WTF?

Снова введите приведение значений к примитиву,

    a = a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == "0" // true; same type, same value


a == !a
or  [0].valueOf().toString() == [0].valueOf().toString()
or  "0" == !"0"
or  "0" == false
or  Number("0") == Number(false)
or  0 = 0   // true
n4m31ess_c0d3r
источник