Перейти к содержимому

Equal var false что означает

  • автор:

Отличия == и === в JavaScript

В JavaScript есть два похожих оператора: == и ===. Если не знать их отличия, это может обернуться кучей ошибок. Так что решил раскрыть эту тему. Чем именно отличаются == и ===, как они работают, почему так происходит, и как избежать ошибок.

Оператор == сравнивает на равенство, а вот === — на идентичность. Плюс оператора === состоит в том, что он не приводит два значения к одному типу. Именно из-за этого он обычно и используется.

abc == undefined; // true, если abc = undefined | null abc === undefined; // true - только если abc = undefined!
abc == false; // true, если abc = false | 0 | '' | [] abc === false; // true, только если abc = false!

Ведь путать false и 0 (или », или []) — вряд ли очень хорошо.

5 === 5; // true true === true; // true 'abc' === 'abc'; // true

А теперь интересный пример.

5 == 5; // true 5 === 5; // true new Number(5) == 5; // true new Number(5) === 5; // false!

Почему так происходит? Да, любое число — это объект класса Number. Но можно представить число как цифру — некоторой константой. Она единожды объявлена, и всегда идентична сама себе. Но в то же время объявляя новый объект класса Number — он равен ей по значению, но не идентичен (так как это два совершенно разных объекта класса Number).

Arrays / Objects

А вот для массивов и объектов оба оператора работают одинаково, сравнивая на идентичность:

var a = <>; a == <>; // false a === <>; // false a == a; // true a === a; // true

Для сравнения массивов и объектов можно написать специальную функцию:

function isEq(a, b) < if(a == b) return true; for(var i in a)< if(!isEq(a[i], b[i])) return false; >for(var i in b) < if(!isEq(a[i], b[i])) return false; >return true; >

Немножко неаккуратно, два цикла, да и про hasOwnProperty забыли; ну да сойдёт.

This

Есть ещё один подводный камень. Это передача в this.

(function()< this == 5; // true this === 5; // false >).call(5);

Вот такой вот момент. Стоит о нём не забывать.

Итого.

Ну а теперь представим, что мы пишем свой суперфреймворк, активно юзаем там оператор === вместо == просто потому что он красивее, и некто находит у нас несколько багов.

func(new Number(5)); (function()< func(this); >).call(5);

Кажется, что такие примеры нежизнеспособны? Пожалуйста!

$.each([1, 2, 3, 4, 5], function()< func(this); >);

Ну или захотелось расширить цифру.

var Five = new Number(5); Five.a = 2; // захотелось расширить, а просто 5 не расширяется // здесь как-то используем. func(Five);

На этом всё, надеюсь кому-то будет полезно. Спасибо за внимание.

UPD. Спасибо за ссылку vermilion1, JS Гарден.

  • равенство
  • идентичность

Операторы равенства — проверка того, равны ли два объекта или нет

Операторы == (равенство) и != (неравенство) проверяют равенство или неравенство своих операндов. Типы значений равны, если их содержимое равно. Ссылочные типы равны, если две переменные ссылаются на одно хранилище.

Оператор равенства ==

Оператор равенства == возвращает значение true , если его операнды равны. В противном случае возвращается значение false .

Равенство типов значений

Операнды встроенных типов значений равны, если равны их значения.

int a = 1 + 2 + 3; int b = 6; Console.WriteLine(a == b); // output: True char c1 = 'a'; char c2 = 'A'; Console.WriteLine(c1 == c2); // output: False Console.WriteLine(c1 == char.ToLower(c2)); // output: True 

У операторов == , < , >, = , если какой-то из операндов не является числом (Double.NaN или Single.NaN), результатом операции является false . Это означает, что значение NaN не больше, не меньше и не равно любому другому значению double (или float ), включая NaN . Дополнительные сведения и примеры см. в справочных статьях по Double.NaN или Single.NaN.

Два операнда одного типа enum равны, если равны соответствующие значения базового целочисленного типа.

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

Равенство ссылочных типов

По умолчанию два операнда ссылочного типа, отличные от записи, являются равными, если они ссылаются на один и тот же объект.

public class ReferenceTypesEquality < public class MyClass < private int id; public MyClass(int id) =>this.id = id; > public static void Main() < var a = new MyClass(1); var b = new MyClass(1); var c = a; Console.WriteLine(a == b); // output: False Console.WriteLine(a == c); // output: True >> 

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

Равенство типов записей

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

public class RecordTypesEquality < public record Point(int X, int Y, string Name); public record TaggedNumber(int Number, ListTags); public static void Main() < var p1 = new Point(2, 3, "A"); var p2 = new Point(1, 3, "B"); var p3 = new Point(2, 3, "A"); Console.WriteLine(p1 == p2); // output: False Console.WriteLine(p1 == p3); // output: True var n1 = new TaggedNumber(2, new List() < "A" >); var n2 = new TaggedNumber(2, new List() < "A" >); Console.WriteLine(n1 == n2); // output: False > > 

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

Равенство строк

Два операнда string равны, если они оба имеют значение null или оба экземпляра строки имеют одинаковую длину и идентичные символы в каждой позиции символа.

string s1 = "hello!"; string s2 = "HeLLo!"; Console.WriteLine(s1 == s2.ToLower()); // output: True string s3 = "Hello!"; Console.WriteLine(s1 == s3); // output: False 

Сравнение равенства строк — это порядковые сравнения с учетом регистра. Дополнительные сведения о том, как сравнивать строки, см. в статье Сравнение строк в C#.

Равенство делегатов

Два операнда делегатов одного типа среды выполнения равны, если оба из них имеют значение null или их списки вызовов имеют одинаковую длину и содержат одинаковые записи в каждой позиции:

Action a = () => Console.WriteLine("a"); Action b = a + a; Action c = a + a; Console.WriteLine(object.ReferenceEquals(b, c)); // output: False Console.WriteLine(b == c); // output: True 

Подробные сведения см. в разделе Delegate equality operators (Операторы равенства делегатов) в спецификации языка C#.

Делегаты, созданные из оценки семантических лямбда-выражений , не равны, как показано в следующем примере:

Action a = () => Console.WriteLine("a"); Action b = () => Console.WriteLine("a"); Console.WriteLine(a == b); // output: False Console.WriteLine(a + b == a + b); // output: True Console.WriteLine(b + a == a + b); // output: False 

Оператор неравенства !=

Оператор неравенства != возвращается true , если его операнды не равны, false в противном случае. Для операндов встроенных типов выражение x != y дает тот же результат, что и выражение !(x == y) . Дополнительные сведения о равенстве типов см. в разделе Оператор равенства.

В следующем примере иллюстрируется использование оператора != .

int a = 1 + 1 + 2 + 3; int b = 6; Console.WriteLine(a != b); // output: True string s1 = "Hello"; string s2 = "Hello"; Console.WriteLine(s1 != s2); // output: False object o1 = 1; object o2 = 1; Console.WriteLine(o1 != o2); // output: True 

Возможность перегрузки оператора

Определяемый пользователем тип может перегружать операторы == и != . Если тип перегружает один из двух операторов, он должен также перегружать и другой.

Тип записи не может явно перегружать == операторы и != операторы. Если необходимо изменить поведение операторов == и != для типа записи T , реализуйте метод IEquatable.Equals со следующей сигнатурой.

public virtual bool Equals(T? other); 

Спецификация языка C#

Дополнительные сведения о равенстве типов записей см. в разделе Элементы равенствапредложения функции записей.

См. также

  • справочник по C#
  • Операторы и выражения C#
  • System.IEquatable
  • Object.Equals
  • Object.ReferenceEquals
  • Сравнения на равенство
  • Операторы сравнения

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

О сравнении объектов по значению — 2, или Особенности реализации метода Equals

В предыдущей публикации мы рассмотрели общие принципы реализации минимально необходимых доработок класса для возможности сравнения объектов класса по значению с помощью стандартной инфраструктуры платформы .NET.

Эти доработки включают перекрытие методов Object.Equals(Object) и Object.GetHashCode().

Остановимся подробнее на особенностях реализации метода Object.Equals(Object) для соответствия следующему требованию в документации:

x.Equals(y) returns the same value as y.Equals(x).

Класс Person, созданный в предыдущей публикации, содержит следующую реализацию метода Equals(Object):

Person.Equals(Object)

public override bool Equals(object obj)

После проверки ссылочного равенства текущего и входящего объекта, в случае отрицательного результата проверки, происходит приведение входящего объекта к типу Person для возможности сравнения объектов по значению.

В соответствии с примером, приведенным в документации, приведение производится с помощью оператора as. Проверим, дает ли это корректный результат.

Реализуем класс PersonEx, унаследовав класс Person, добавив в персональные данные свойство Middle Name, и перекрыв соответствующим образом методы Person.Equals(Object) и Person.GetHashCode().

class PersonEx

using System; namespace HelloEquatable < public class PersonEx : Person < public string MiddleName < get; >public PersonEx( string firstName, string middleName, string lastName, DateTime? birthDate ) : base(firstName, lastName, birthDate) < this.MiddleName = NormalizeName(middleName); >public override int GetHashCode() => base.GetHashCode() ^ this.MiddleName.GetHashCode(); protected static bool EqualsHelper(PersonEx first, PersonEx second) => EqualsHelper((Person)first, (Person)second) && first.MiddleName == second.MiddleName; public override bool Equals(object obj) < if ((object)this == obj) return true; var other = obj as PersonEx; if ((object)other == null) return false; return EqualsHelper(this, other); >> >

Легко заметить, что если у объекта класса Person вызвать метод Equals(Object) и передать в него объект класса PersonEx, то, если у этих объектов (персон) совпадают имя, фамилия и дата рождения, метод Equals возвратит true, в противном случае метод возвратит false.

(При выполнении метода Equals, входящий объект, имеющий во время выполнения (runtime) тип PersonEx, будет успешно приведен к типу Person с помощью оператора as, и далее будет произведено сравнение объектов по значениям полей, имеющихся только в классе Person, и будет возвращен соответствующий результат.)

Очевидно, что с предметной точки зрения это неверное поведение:

Совпадение имени, фамилии и даты рождения не означает, что это одна и та же персона, т.к. у одной персоны отсутствует атрибут middle name (речь не о неопределенном значении атрибута, а об отсутствии самого атрибута), а у другой имеется атрибут middle name.
(Это разные типы сущностей.)

Если же, напротив, у объекта класса PersonEx вызвать метод Equals(Object) и передать в него объект класса Person, то метод Equals в любом случае возвратит false, независимо от значений свойств объектов.

(При выполнении метода Equals, входящий объект, имеющий во время выполнения (runtime) тип Person, не будет успешно приведен к типу PersonEx с помощью оператора as — результатом приведения будет null, и метод возвратит false.)

Здесь мы наблюдаем верное с предметной точки зрения поведение, в отличие от предыдущего случая.

Эти виды поведения можно легко проверить, выполнив следующий код:

var person = new Person("John", "Smith", new DateTime(1990, 1, 1)); var personEx = new PersonEx("John", "Teddy", "Smith", new DateTime(1990, 1, 1)); bool isSamePerson = person.Equals(personEx); bool isSamePerson2 = personEx.Equals(person);

Однако, в разрезе данной публикации нас в большей степени интересует соответствие реализованного поведения Equals(Object) требованиям в документации, нежели корректность логики с предметной точки зрения.

А именно соответствие требованию:

x.Equals(y) returns the same value as y.Equals(x).

Это требование не выполняется.

(А с точки зрения здравого смысла, какие могут быть проблемы при текущей реализации Equals(Object)?
У разработчика типа данных нет информации, каким именно способом будут сравниваться объекты — x.Equals(y) или y.Equals(x) — как в клиентском коде (при явном вызове Equals), так и при помещении объектов в хеш-наборы (хеш-карты) и словари (внутри самих наборов/словарей).

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

Рассмотрим, каким именно образом можно реализовать метод Equals(Object), обеспечив ожидаемое поведение.

На текущий момент представляется корректным способ, предложенный Джеффри Рихтером (Jeffrey Richter) в книге CLR via C# (Part II «Designing Types», Chapter 5 «Primitive, Reference, and Value Types», Subchapter «Object Equality and Identity»), когда перед сравнением объектов непосредственно по значению, типы объектов во время выполнения (runtime), полученные с помощью метода Object.GetType() проверяются на равенство (вместо односторонних проверки на совместимость и приведения типов объектов с помощью оператора as):

if (this.GetType() != obj.GetType()) return false; 

Следует отметить, что использование данного способа не является однозначным, т.к. существует три различных способа проверки на равенство экземпляров класса Type, с теоретически различными результатами для одних и тех же операндов:

For two objects x and y that have identical runtime types, Object.ReferenceEquals(x.GetType(),y.GetType()) returns true. 

Таким образом, объекты класса Type можно проверить на равенство с помощью сравнения по ссылке:

bool isSameType = (object)obj1.GetType() == (object)obj2.GetType();
bool isSameType = Object.ReferenceEquals(obj1.GetType(), obj2.GetType());

2. Класс Type имеет методы Equals(Object) и Equals(Type), поведение которых определено следующим образом:

Determines if the underlying system type of the current Type object is the same as the underlying system type of the specified Object.

Return Value
Type: System.Boolean
true if the underlying system type of o is the same as the underlying system type of the current Type; otherwise, false. This method also returns false if:
o is null.
o cannot be cast or converted to a Type object.

Remarks
This method overrides Object.Equals. It casts o to an object of type Type and calls the Type.Equals(Type) method.

Determines if the underlying system type of the current Type is the same as the underlying system type of the specified Type.

Return Value
Type: System.Boolean
true if the underlying system type of o is the same as the underlying system type of the current Type; otherwise, false.

Внутри эти методы реализованы следующим образом:

public override bool Equals(Object o)
public virtual bool Equals(Type o)

Как видим, результат выполнения обоих методов Equals для объектов класса Type в общем случае может отличаться от сравнения объектов по ссылке, т.к. в случае использования методов Equals, сравниваются по ссылке не сами объекты класса Type, а их свойства UnderlyingSystemType, относящиеся к тому же классу.

Однако, из описания методов Equals класса Type.Equals(Object) представляется, что они не предназначены для сравнения непосредственно объектов класса Type.

Примечание:
Для метода Type.Equals(Object) проблема несоответствия требованию (как следствие использования оператора as)

x.Equals(y) returns the same value as y.Equals(x).

не возникнет, если только в потомках класса Type метод не будет перекрыт некорректным образом.
Для предотвращения этой потенциальной проблемы, возможно, разработчиками было бы целесообразно объявить метод как sealed.

3. Класс Type, начиная с .NET Framework 4.0, имеет перегруженные операторы == или !=, поведение которых описывается простым образом, без описания деталей реализации:

Indicates whether two Type objects are equal.

Return Value
Type: System.Boolean
true if left is equal to right; otherwise, false.

Indicates whether two Type objects are not equal.

Return Value
Type: System.Boolean
true if left is not equal to right; otherwise, false.

Изучение исходных кодов тоже не дает информации по деталям реализации, для выяснения внутренней логики операторов:

public static extern bool operator ==(Type left, Type right);
public static extern bool operator !=(Type left, Type right);

Реализуем классы Person и PersonEx соответствующим образом:

class Person (with new Equals method)

using System; namespace HelloEquatable < public class Person < protected static string NormalizeName(string name) =>name?.Trim() ?? string.Empty; protected static DateTime? NormalizeDate(DateTime? date) => date?.Date; public string FirstName < get; >public string LastName < get; >public DateTime? BirthDate < get; >public Person(string firstName, string lastName, DateTime? birthDate) < this.FirstName = NormalizeName(firstName); this.LastName = NormalizeName(lastName); this.BirthDate = NormalizeDate(birthDate); >public override int GetHashCode() => this.FirstName.GetHashCode() ^ this.LastName.GetHashCode() ^ this.BirthDate.GetHashCode(); protected static bool EqualsHelper(Person first, Person second) => first.BirthDate == second.BirthDate && first.FirstName == second.FirstName && first.LastName == second.LastName; public override bool Equals(object obj) < if ((object)this == obj) return true; if (obj == null) return false; if (this.GetType() != obj.GetType()) return false; return EqualsHelper(this, (Person)obj); >> > 

class PersonEx (with new Equals method)

using System; namespace HelloEquatable < public class PersonEx : Person < public string MiddleName < get; >public PersonEx( string firstName, string middleName, string lastName, DateTime? birthDate ) : base(firstName, lastName, birthDate) < this.MiddleName = NormalizeName(middleName); >public override int GetHashCode() => base.GetHashCode() ^ this.MiddleName.GetHashCode(); protected static bool EqualsHelper(PersonEx first, PersonEx second) => EqualsHelper((Person)first, (Person)second) && first.MiddleName == second.MiddleName; public override bool Equals(object obj) < if ((object)this == obj) return true; if (obj == null) return false; if (this.GetType() != obj.GetType()) return false; return EqualsHelper(this, (PersonEx)obj); >> > 

Теперь следующее требование к реализации метода Equals(Object) будет соблюдаться:

x.Equals(y) returns the same value as y.Equals(x).

что легко проверяется выполнением кода:

var person = new Person("John", "Smith", new DateTime(1990, 1, 1)); var personEx = new PersonEx("John", "Teddy", "Smith", new DateTime(1990, 1, 1)); bool isSamePerson = person.Equals(personEx); bool isSamePerson2 = personEx.Equals(person);

Примечания к реализации метода Equals(Object):

  1. вначале проверяются на равенство ссылки, указывающие на текущий и входящий объекты, и, в случае совпадения ссылок, возвращается true;
  2. затем проверяется на null ссылка на входящий объект, и, в случае положительного результата проверки, возвращается false;
  3. затем проверяется идентичность типов текущего и входящего объекта, и, в случае отрицательного результата проверки, возвращается false;
  4. на последнем этапе производятся приведение входящего объекта к типу данного класса и непосредственно сравнение объектов по значению.
Таким образом, мы нашли оптимальный способ реализации ожидаемого поведения метода Equals(Object).

На десерт проверим корректность реализации Equals(Object) в стандартной библиотеке.

Compares two Uri instances for equality.

Syntax
public override bool Equals(object comparand)

Parameters
comparand
Type: System.Object
The Uri instance or a URI identifier to compare with the current instance.

Return Value
Type: System.Boolean
A Boolean value that is true if the two instances represent the same URI; otherwise, false.

Uri.Equals(Object)

public override bool Equals(object comparand) < if ((object)comparand == null) < return false; >if ((object)this == (object)comparand) < return true; >Uri obj = comparand as Uri; // // we allow comparisons of Uri and String objects only. If a string // is passed, convert to Uri. This is inefficient, but allows us to // canonicalize the comparand, making comparison possible // if ((object)obj == null) < string s = comparand as string; if ((object)s == null) return false; if (!TryCreate(s, UriKind.RelativeOrAbsolute, out obj)) return false; >// method code . > 

Логично предположить, что следующее требование к реализации метода Equals(Object) не выполняется:

x.Equals(y) returns the same value as y.Equals(x).

Т.к. класс String и метод String.Equals(Object), в свою очередь, не «знают» о существовании класса Uri.

Это легко проверить на практике, выполнив код:

const string uriString = "https://www.habrahabr.ru"; Uri uri = new Uri(uriString); bool isSameUri = uri.Equals(uriString); bool isSameUri2 = uriString.Equals(uri);

Операторы сравнения

JavaScript предоставляет три оператора сравнения величин:

  • равенство («двойное равно») использует == (en-US),
  • строгое равенство («тройное равно» или «идентично») использует === (en-US),
  • и Object.is (новшество из ECMAScript 6).

Выбор оператора зависит от типа сравнения, которое необходимо произвести.

В общих чертах, двойное равно перед сравнением величин производит приведение типов; тройное равно сравнивает величины без приведения (если величины разных типов, вернёт false , даже не сравнивая); ну и Object.is ведёт себя так же, как и тройное равно, но со специальной обработкой для NaN , -0 и +0 , возвращая false при сравнении -0 и +0 , и true для операции Object.is(NaN, NaN) . (В то время как двойное или тройное равенство вернут false согласно стандарту IEEE 754.) Следует отметить, что все эти различия в сравнениях применимы лишь для примитивов. Для любых не примитивных объектов x и y , которые имеют одинаковые структуры, но представляют собой два отдельных объекта (переменные x и y не ссылаются на один и тот же объект), все операторы сравнения вернут false .

Сравнение с использованием ==

Перед сравнением оператор равенства приводит обе величины к общему типу. После приведений (одного или обоих операндов), конечное сравнение выполняется также как и для === . Операция сравнения симметрична: A == B возвращает то же значение, что и B == A для любых значений A и B .

В следующей таблице приведены результаты сравнения оператора равенства для различных значений:

В таблице выше, ToNumber(A) пытается перед сравнением привести свой аргумент к числу. Такое поведение эквивалентно +A (унарный оператор + ). Если ToPrimitive(A) получает объект в качестве аргумента, то производятся попытки привести его к примитиву, вызывая на нем методы A.toString и A.valueOf .

Традиционно (и в соответствии с ECMAScript), ни один из объектов не равен undefined или null . Но большинство браузеров позволяет определённому классу объектов (в частности, объектам document.all для любой страницы) эмулировать значение undefined. Оператор равенства вернёт значение true для null == A и undefined == A , тогда, и только тогда, когда объект эмулирует значение undefined . Во всех остальных случаях объект не может быть равен undefined или null .

var num = 0; var obj = new String("0"); var str = "0"; var b = false; 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); 

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

Строгое равенство с использованием ===

Строгое равно проверяет на равенство две величины, при этом тип каждой из величин перед сравнением не изменяется (не приводится). Если значения имеют различающиеся типы, то они не могут быть равными. С другой стороны все не числовые переменные, принадлежащие одному типу, считаются равными между собой, если содержат одинаковые величины. Ну и, наконец, числовые переменные считаются равными, если они имеют одинаковые значения, либо одна из них +0 , а вторая -0 . В то же время, если хотя бы одна из числовых переменных содержит значение NaN , выражение вернёт false .

var num = 0; var obj = new String("0"); var str = "0"; var b = false; 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 . Знак для нуля введён для упрощения некоторых вычислений с плавающей запятой, однако, с точки зрения математики, разницы между +0 и -0 не существует, поэтому оператор строгого равенства считает их равными. Во-вторых, сравнение величин NaN . NaN (Not a number) представляет из себя значение не определённой величины, которое применяется для не чётко определённых математических задач (например +∞ + -∞ ). Для оператора строго равенства NaN не равен ни одной из величин, в том числе и самому себе (единственный случай, когда (x!==x) вернёт true).

Равенство одинаковых величин

Равенство одинаковых величин определяет функциональную идентичность во всех контекстах сравниваемых величин. (Данный способ сравнения основывается на принципе подстановки Барбары Лисков.) Рассмотрим пример попытки изменения неизменяемого (immutable) свойства :

// Добавление неизменяемого свойства 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 , изменений не произойдёт, а значит, код отработает без выброса исключений. Однако, если же v содержит +0 , Number.NEGATIVE_ZERO утратит свою неизменяемую величину.

Именно для сравнения нового и текущего неизменяемых свойств используется сравнение одинаковых величин, представленное методом Object.is .

Равенство одинаковых величин и нулей

Аналогично равенству одинаковых величин, но +0 и -0 считаются равными.

Равенство одинаковых величин и нулей не предоставляется как JavaScript API, но может быть реализовано с помощью пользовательского кода:

function sameValueZero(x, y)  if (typeof x === "number" && typeof y === "number")  // x и y равны (могут быть -0 и 0) или они оба равны NaN return x === y || (x !== x && y !== y); > return x === y; > 

Равенство одинаковых величин и нулей отличается от строгого равенства тем, что принимает каждое значение NaN равным любому другому значению NaN , а от равенства одинаковых величин тем, что принимает -0 равным 0 . Подобное поведение обычно оказывается самым уместным при поиске в списках, особенно при работе с NaN . Данная стратегия сравнения используется в методах Array.prototype.includes() , TypedArray.prototype.includes() (en-US) , а так же в Map (en-US) и Set для оценки равенства ключей.

Спецификации для равенства, строгого равенства и равенства одинаковых величин

В стандарте ES5, сравнение выполняемое оператором == (en-US) описывается в секции 11.9.3, The Abstract Equality Algorithm. Описание оператора === (en-US) находится в секции 11.9.6, The Strict Equality Algorithm. В секции 9.12, The SameValue Algorithm ES5 описывает операцию сравнение одинаковых величин для внутреннего движка JS. Строгое равенство и равенство одинаковых величин, практически одинаковы, за исключением обработки числовых типов. ES6 предлагает использовать алгоритм сравнения одинаковых величин через вызов Object.is .

Как понимать все эти способы сравнения?

До выхода редакции ES6 считалось, что оператор строгого равенства просто «улучшенная» версия оператора нестрогого равенства. Например, некоторые считали, что == просто улучшенная версия === потому, что первый оператор делает всё то же, что и второй, плюс приведение типов своих операндов. То есть 6 == «6». (Или же наоборот: оператор нестрогого равенства базовый, а оператор строгого равенства просто его улучшенная версия, ведь он добавляет ещё одно условие — требует, чтобы оба операнда были одного и того же типа. Какой вариант ближе вам, зависит только от вашей точки зрения на вещи.)

Но эти точки зрения уже нельзя применить к новому методу сравнения Object.is из новой редакции ES6. Нельзя сказать, что Object.is более или менее строг относительно существующих способов сравнения, или что это нечто среднее между ними. Ниже в таблице показаны основные различия операторов сравнения. Object.is интересен тем, что различает -0 и +0, а также умеет сравнивать два не числа NaN.

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
0n -0n ✅ true ✅ true ✅ true ✅ 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
❌ 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

Когда использовать Object.is(), а когда ===

Особое поведение Object.is по отношению к нулям, вероятно, будет представлять интерес при реализации определенных схем метапрограммирования, особенно в отношении дескрипторов свойств, когда желательно, чтобы ваш алгоритм имел такое же поведение, как Object.defineProperty . В случае, если вам этого не требуется, рекомендуется избегать Object.is и вместо этого использовать === . Как правило, даже при необходимости сравнения NaN -значений, эффективнее выполнить проверку таких значений отдельно, при помощи метода isNaN , чем выяснять, как окружающие вычисления могут повлиять на знаки нулей и как это отразится на вашем алгоритме.

Вот неполный список встроенных методов и операторов, при использовании которых, различие между -0 и +0 может оказаться важным для вашего кода:

Рассмотрим следующий пример:

const stoppingForce = obj.mass * -obj.velocity; 

Если obj.velocity равно 0 (или вычисляется как 0 ), в этом месте возникает -0 и присваивается в stoppingForce .

In some cases, it’s possible for a -0 to be introduced into an expression as a return value of these methods even when no -0 exists as one of the parameters. For example, using Math.pow to raise -Infinity to the power of any negative, odd exponent evaluates to -0 . Refer to the documentation for the individual methods.

It’s possible to get a -0 return value out of these methods in some cases where a -0 exists as one of the parameters. E.g., Math.min(-0, +0) evaluates to -0 . Refer to the documentation for the individual methods.

Each of these operators uses the ToInt32 algorithm internally. Since there is only one representation for 0 in the internal 32-bit integer type, -0 will not survive a round trip after an inverse operation. E.g., both Object.is(~~(-0), -0) and Object.is(-0 > 2, -0) evaluate to false .

Relying on Object.is when the signedness of zeros is not taken into account can be hazardous. Of course, when the intent is to distinguish between -0 and +0 , it does exactly what’s desired.

Caveat: Object.is() and NaN

The Object.is specification treats all instances of NaN as the same object. However, since typed arrays are available, we can have distinct floating point representations of NaN which don’t behave identically in all contexts. For example:

const f2b = (x) => new Uint8Array(new Float64Array([x]).buffer); const b2f = (x) => new Float64Array(x.buffer)[0]; // Get a byte representation of NaN const n = f2b(NaN); // Change the first bit, which is the sign bit and doesn't matter for NaN n[0] = 1; const nan2 = b2f(n); console.log(nan2); // NaN console.log(Object.is(nan2, NaN)); // true console.log(f2b(NaN)); // Uint8Array(8) [0, 0, 0, 0, 0, 0, 248, 127] console.log(f2b(nan2)); // Uint8Array(8) [1, 0, 0, 0, 0, 0, 248, 127] 

Смотрите также

Found a content problem with this page?

  • Edit the page on GitHub.
  • Report the content issue.
  • View the source on GitHub.

This page was last modified on 3 авг. 2023 г. by MDN contributors.

Your blueprint for a better internet.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *