--- title: Перевірка на рівність та однаковість slug: Web/JavaScript/Перевірка_на_рівність_та_однаковість tags: - JavaScript - NaN - SameValue - SameValueZero - Однаковість - Рівність - Тотожність - порівняння translation_of: Web/JavaScript/Equality_comparisons_and_sameness ---
У ES2015 існує чотири алгоритми рівності:
==
);===
): вживається у методах Array.prototype.indexOf
, Array.prototype.lastIndexOf
та у блоках case
;String.prototype.includes
та Array.prototype.includes
, починаючи з ES2016;JavaScript надає три різні операції порівняння значень:
Вибір оператора залежитиме від того, який різновид порівняння вам потрібен. Коротко:
==
) виконає приведення типів при порівнянні двох величин, і оброблятиме NaN
, -0
та +0
особливим чином, у відповідності з IEEE 754 (тому NaN != NaN
, а -0 == +0
);===
) виконує таке саме порівняння, як і подвійне дорівнює (і так само поводиться з NaN
, -0
та +0
), але без приведення типів; якщо типи величин відрізняються, повертається false
.Object.is
не виконує приведення типів та не обробляє NaN
, -0
та +0
особливим чином (має таку саму поведінку, як і ===
, за винятком цих спеціальних числових значень).Зауважте, що усі відмінності операторів стосуються їхнього поводження з простими величинами; жоден з них не порівнює, чи є параметри концептуально однаковими за структурою. Для будь-яких не примітивних об'єктів x
та y
, що мають однакову структуру, але є двома окремими об'єктами, всі вищенаведені форми порівняння вертатимуть false
.
===
Строга рівність перевіряє рівність двох значень. До жодного з них не застосовується неявне приведення перед порівнянням. Якщо значення мають різні типи, вони вважаються нерівними. Якщо значення мають однаковий тип, не є числами, і мають однакові значення, вони вважаються рівними. Нарешті, якщо обидва значення є числами, вони вважаються рівними, якщо обидва не дорівнюють NaN
та мають однакові значення, або якщо одне дорівнює +0
, а інше -0
.
var num = 0; var obj = new String('0'); var str = '0'; console.log(num === num); // true console.log(obj === obj); // true console.log(str === str); // true console.log(num === obj); // false console.log(num === str); // false console.log(obj === str); // false console.log(null === undefined); // false console.log(obj === null); // false console.log(obj === undefined); // false
Строга рівність майже завжди є доречною операцією порівняння. Для усіх значень, крім чисел, вона використовує очевидну семантику: значення дорівнює лише самому собі. Для чисел вона використовує трохи відмінну семантику, щоб згладити два граничні випадки. Перший полягає в тому, що нуль з рухомою комою є або додатним, або від'ємним. Це корисно для представлення деяких математичних рішень, але, оскільки у більшості ситуацій різниця між +0
та -0
не має значення, строга рівність вважає їх одним значенням. Другий випадок полягає в тому, що рухома кома містить концепцію не числа, NaN
(not a number), для вирішення деяких нечітко визначених математичних проблем: наприклад, від'ємна нескінченність, додана до позитивної нескінченності. Строга рівність вважає значення NaN
нерівним будь-якій іншій величині, в тому числі самому собі. (Єдиний випадок, у якому (x !== x)
дорівнює true
, це коли x
дорівнює NaN
.)
Нестрога рівність порівнює два значення після приведення обох значень до спільного типу. Після приведення (один чи обидва значення можуть зазнати перетворення), фінальне порівняння виконується так само, як його виконує оператор ===
. Нестрога рівність є симетричною: вираз A == B
за семантикою завжди ідентичний B == A
для будь-яких значень A
та B
(за винятком порядку, в якому виконуються перетворення).
Порівняльна операція виконується наступним чином для операндів різних типів:
Операнд B | |||||||
---|---|---|---|---|---|---|---|
Undefined | Null | Number | String | Boolean | Object | ||
Операнд A | Undefined | true |
true |
false |
false |
false |
false |
Null | true |
true |
false |
false |
false |
false |
|
Number | false |
false |
A === B |
A === ToNumber(B) |
A === ToNumber(B) |
A == ToPrimitive(B) |
|
String | false |
false |
ToNumber(A) === B |
A === B |
ToNumber(A) === ToNumber(B) |
A == ToPrimitive(B) |
|
Boolean | false |
false |
ToNumber(A) === B |
ToNumber(A) === ToNumber(B) |
A === B |
ToNumber(A) == ToPrimitive(B) |
|
Object | false |
false |
ToPrimitive(A) == B |
ToPrimitive(A) == B |
ToPrimitive(A) == ToNumber(B) |
A === B |
У наведеній вище таблиці ToNumber(A)
пробує перетворити свій аргумент на число перед порівнянням. Його поведінка еквівалентна операції +A
(унарний оператор +). ToPrimitive(A)
пробує перетворити свій аргумент-об'єкт на просту величину, викликаючи в різній послідовності методи A.toString
та A.valueOf
на операнді A
.
Традиційно, та згідно з ECMAScript, усі об'єкти нестрого нерівні undefined
та null
. Але більшість переглядачів дозволяють дуже вузькому класу об'єктів (зокрема, об'єкту document.all
на будь-якій сторінці) у певному контексті поводитись, як наче вони емулюють значення undefined
. Нестрога рівність у такому контексті: null == A
та undefined == A
оцінить як true тільки за умови, що A є об'єктом, який емулює undefined
. У всіх інших випадках об'єкт ніколи не є нестрого рівним undefined
або null
.
var num = 0; var obj = new String('0'); var str = '0'; console.log(num == num); // true console.log(obj == obj); // true console.log(str == str); // true console.log(num == obj); // true console.log(num == str); // true console.log(obj == str); // true console.log(null == undefined); // true // обидва дорівнюють false, крім виняткових випадків console.log(obj == null); console.log(obj == undefined);
Деякі розробники вважають, що краще ніколи не використовувати нестрогу рівність. Результат порівняння за допомогою строгої рівності легше передбачити, а, оскільки жодного приведення типів не виконується, обчислення може відбуватись швидше.
Порівняння same-value (однакове значення) спрямоване на останній випадок використання: визначення того, чи є два значення функціонально ідентичними в усіх контекстах. (Цей випадок використання демонструє приклад принципу підстановки Лісков.) Один приклад виникає, коли робиться спроба змінити незмінну властивість:
// Додати незмінну властивість NEGATIVE_ZERO у конструктор Number. Object.defineProperty(Number, 'NEGATIVE_ZERO', { value: -0, writable: false, configurable: false, enumerable: false }); function attemptMutation(v) { Object.defineProperty(Number, 'NEGATIVE_ZERO', { value: v }); }
Object.defineProperty
викине виняток, коли спроба змінити незмінну властивість дійсно змінить її, але нічого не зробить, якщо не робиться запитів на реальну зміну. Якщо v
дорівнює -0
, запитів на зміну не виконувалось, жодна помилка не буде викинута. Внутрішньо, коли перевизначається незмінна властивість, нове значення порівнюється з наявним значенням за допомогою алгоритму same-value.
Алгоритм same-value надається методом {{jsxref("Object.is")}}.
Схожий на алгоритм same-value, але вважає +0 та -0 рівними.
У ES5 порівняння, що виконується за допомогою ==
, описане у Розділі 11.9.3, Алгоритм абстрактної рівності. Порівняння ===
у 11.9.6, Алгоритм строгої рівності. (Сходіть почитайте. Вони короткі та легкі для читання. Підказка: читайте спочатку алгоритм строгої рівності.) ES5 також описує, у Розділі 9.12, Алгоритм SameValue, для внутрішнього використання рушієм JS. Він значною мірою такий самий, як алгоритм строгої рівності, за винятком того, як 11.9.6.4 та 9.12.4 відрізняються у поводженні з {{jsxref("Number","числами")}}. ES2015 просто відкриває цей алгоритм через {{jsxref("Object.is")}}.
Щодо подвійного та потрійного дорівнює, можна побачити, що, за винятком попередньої перевірки типу у 11.9.6.1, алгоритм строгої рівності є підмножиною алгоритму абстрактної рівності, бо 11.9.6.2–7 відповідають 11.9.3.1.a–f.
До ES2015 ви, можливо, сказали б щодо подвійного та потрійного дорівнює, що один є "посиленою" версією іншого. Наприклад, хтось може сказати, що подвійне дорівнює є посиленою версією потрійного дорвінює, тому що перше робить усе, що робить друге, але з приведенням типів у операндах. Наприклад, 6 == "6"
. (Альтернативно, хтось може сказати, що подвійне дорівнює є основою, а потрійне дорівнює є посиленою версією, тому що воно вимагає, щоб два операнди були однакового типу, і таким чином, вводить додаткове обмеження. Яка модель краща для розуміння, залежить від того, яким чином ви розглядаєте питання.)
Однак, така модель вбудованих операторів однаковості не може бути поширена на ES2015 з включенням у свій "діапазон" методу {{jsxref("Object.is")}}. Метод {{jsxref("Object.is")}} не просто "абстрактніший", ніж подвійне, або "суворіший", ніж потрійне дорівнює, він також не вписується десь посередині (тобто, будучи одночасно суворішим за подвійне дорівнює, але менш строгим за потрійне дорівнює). Як можна побачити з наведеної нижче порівняльної таблиці, все через поводження {{jsxref("Object.is")}} з {{jsxref("NaN")}}. Зверніть увагу, що, якби значення Object.is(NaN, NaN)
дорівнювало false
, ми могли б сказати, що метод вписується у абстрактно-суворий діапазон як ще суворіший за потрійне дорівнює, такий, що розрізняє -0
та +0
. Однак, його поводження з {{jsxref("NaN")}} означає, що це не так. На жаль, {{jsxref("Object.is")}} просто має сприйматись з точки зору його особливих характеристик, а не його абстрактності чи суворості у порівнянні з операторами рівності.
x | y | == |
=== |
Object.is |
SameValueZero |
---|---|---|---|---|---|
undefined |
undefined |
true |
true |
true |
true |
null |
null |
true |
true |
true |
true |
true |
true |
true |
true |
true |
true |
false |
false |
true |
true |
true |
true |
'foo' |
'foo' |
true |
true |
true |
true |
0 |
0 |
true |
true |
true |
true |
+0 |
-0 |
true |
true |
false |
true |
+0 |
0 |
true |
true |
true |
true |
-0 |
0 |
true |
true |
false |
true |
0 |
false |
true |
false |
false |
false |
"" |
false |
true |
false |
false |
false |
"" |
0 |
true |
false |
false |
false |
'0' |
0 |
true |
false |
false |
false |
'17' |
17 |
true |
false |
false |
false |
[1, 2] |
'1,2' |
true |
false |
false |
false |
new String('foo') |
'foo' |
true |
false |
false |
false |
null |
undefined |
true |
false |
false |
false |
null |
false |
false |
false |
false |
false |
undefined |
false |
false |
false |
false |
false |
{ foo: 'bar' } |
{ foo: 'bar' } |
false |
false |
false |
false |
new String('foo') |
new String('foo') |
false |
false |
false |
false |
0 |
null |
false |
false |
false |
false |
0 |
NaN |
false |
false |
false |
false |
'foo' |
NaN |
false |
false |
false |
false |
NaN |
NaN |
false |
false |
true |
true |
Загалом, єдиний випадок, коли особлива поведінка {{jsxref("Object.is")}} щодо нулів може становити інтерес, це впровадження певних схем метапрограмування, особливо тих, що стосуються дескрипторів властивостей, коли бажано, щоб ваша робота відображала певні характеристики {{jsxref("Object.defineProperty")}}. Якщо ваш випадок цього не вимагає, пропонується уникати {{jsxref("Object.is")}} та використовувати натомість ===
. Навіть якщо у вашому випадку вимагається, щоб порівняння двох {{jsxref("NaN")}} повертало true
, загалом, легше перевіряти {{jsxref("NaN")}} окремо (за допомогою методу {{jsxref("isNaN")}}, доступного у попередніх версіях ECMAScript), ніж розбиратися, як навколишні обчислення можуть вплинути на знаки нулів, які зустрінуться вам у порівняннях.
Ось невичерпний список вбудованих методів та операцій, що можуть спричинити відмінності між -0
та +0
, які можуть проявити себе у коді:
- (унарний мінус)
let stoppingForce = obj.mass * -obj.velocity;
Якщо obj.velocity
дорівнює 0
(або обчислюється як 0
), в цьому місці отримуємо -0
, який переходить далі у stoppingForce
.
-0
може потрапити у вираз як результат одного з цих методів, навіть коли жоден параметр не дорівнює -0
. Наприклад, при використанні {{jsxref("Math.pow")}} для піднесення {{jsxref("Infinity", "-Infinity")}} до будь-якого від'ємного непарного степеня, отримуємо -0
. Звертайтесь до документації щодо окремих методів.-0
як результат виконання даних методів у певних випадках, коли -0
присутній як один з параметрів. Наприклад, Math.min(-0, +0)
повертає -0
. Звертайтесь до документації щодо окремих методів.~
<<
>>
-0
не збережеться після подвійної операції інверсії. Наприклад, і Object.is(~~(-0), -0)
, і Object.is(-0 << 2 >> 2, -0)
повернуть false
.Покладатися на метод {{jsxref("Object.is")}}, не беручи до уваги знаки нулів, може бути небезпечно. Звісно, якщо на меті стоїть розрізнити -0
та +0
, він робить саме те, що потрібно.
Специфікація {{jsxref("Object.is")}} вважає усі екземпляри {{jsxref("NaN")}} тим самим об'єктом, але, оскільки нам доступні типізовані масиви, ми можемо мати окремі екземпляри, які не поводитимуться ідентично в усіх контекстах. Приклад:
var f2b = x => new Uint8Array(new Float64Array([x]).buffer); var b2f = x => new Float64Array(x.buffer)[0]; var n = f2b(NaN); n[0] = 1; var nan2 = b2f(n); nan2 > NaN Object.is(nan2, NaN) > true f2b(NaN) > Uint8Array(8) [0, 0, 0, 0, 0, 0, 248,127) f2b(nan2) > Uint8Array(8) [1, 0, 0, 0, 0, 0, 248,127)