Чому 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"]

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


10

Це через неявний тип перетворення == оператора.

[2] перетворюється в число 2 у порівнянні з числом. Спробуйте +оператор одинарний [2].

> +[2]
2

Інші кажуть, що [2] перетворюється на рядок. +"2"також номер 2.
dlamblin

1
Насправді це не так просто. [2] перетворення в рядок було б ближче, але подивіться на ecma-international.org/ecma-262/5.1/#sec-11.9.3
нео

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

Це веселий спосіб обробки JavaScript в операторі if ().


4
насправді це смішний спосіб ==працює; якщо ви використовуєте фактичний чіткий ролик (тобто Boolean([0])або !![0]), ви виявите, що [0]оцінюватимете trueв булевих контекстах як слід: у JS будь-який об’єкт розглядаєтьсяtrue
Крістоф

6

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

Це пов’язано з набором качок. Оскільки "2" == 2 == [2] і можливо більше.


4
тому що вони не відповідають типу. у першому прикладі спочатку оцінюється лівий бік, і вони завершують відповідність типу.
Шон

8
Крім того, я не думаю, що введення качки є правильним словом. Це більше пов'язано з неявним перетворенням типу, виконаним компанією== оператором перед порівнянням.
Chetan S

14
це не має нічого спільного з набиранням качок, а скоріше зі слабким набором тексту, тобто неявним перетворенням типу
Крістоф

@Chetan: що він сказав;)
Крістоф

2
Що сказали Четан і Крістоф.
Tim Down

3

Для того, щоб додати трохи деталей до інших відповідей ... коли порівнюючи Arrayдо 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)як стенограму, щоб точно знати, як вона буде перетворена.


2

Ви порівнюєте по 2 об’єкти в кожному випадку .. Не використовуйте ==, якщо ви думаєте про порівняння, ви маєте на увазі ===, а не ==. == часто може дати шалені ефекти. Шукайте хороші частини мови :)


0

Пояснення до розділу EDIT питання:

1-й приклад

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

Перший набір [0] для примітивного значення, відповідно до відповіді Крістофа, маємо "0" ( [0].valueOf().toString())

"0" == false

Тепер наберіть Boolean (false) у число, а потім String ("0") у число

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

Що стосується ifтвердження, якщо немає явного порівняння в самій умові if, умова оцінюється за правдою значень.

Є лише 6 фальшивих значень : false, null, undefined, 0, NaN та порожній рядок "". І все, що не є хибною цінністю, - цінність.

Оскільки [0] не є хибним значенням, а є істинним значенням, ifвисловлювання оцінюється як true і виконує оператор.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.