Примитивы и эквивалентность обёртки объектов в JavaScript

РЕДАКТИРОВАТЬ: Судя по отзывам, исходная версия этого вопроса больше связана с дизайном, а не со стандартами. Делаем SO-дружественным.


Оригинал:

Должен ли JS-примитив считаться «эквивалентным» объектной версии этого примитива в соответствии со стандартами ECMA?


Пересмотренный вопрос

Существует ли универсальное соглашение о том, как сравнивать объекты с примитивной оболочкой в ​​текущем JavaScript?

var n = new Number(1),
    p = 1;
n === p;     // false
typeof n;    // "object"
typeof p;    // "number"
+n === p;    // true, but you need coercion.

ИЗМЕНИТЬ:

Как прокомментировал @Pointy, спецификация ECMA (262, S15.1.2.4) описывает метод Number.isNaN(), который ведет себя следующим образом:

Number.isNaN(NaN);                // true
Number.isNaN(new Number(NaN));    // false
Number.isNaN(+(new Number(NaN))); // true, but you need coercion.

Очевидно, такое поведение оправдано тем, что isNaN вернет true, ЕСЛИ аргумент приводит к NaN. new Number(NaN) не принуждает напрямую, основываясь на том, как работает родной isNaN.

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

Посмотрите этот JSPerf.


person sjhcockrell    schedule 02.01.2013    source источник
comment
Кажется, вы уже продемонстрировали, что примитивные числа и номера объектов эквивалентны по значению, но не по типу; каков твой вопрос?   -  person Mathletics    schedule 03.01.2013
comment
Мой вопрос заключается в том, следует ли считать их эквивалентными, если они эквивалентны по значению, но не по типу?   -  person sjhcockrell    schedule 03.01.2013
comment
Зачем ты пишешь такую ​​функцию? JavaScript уже дает вам == и ===...   -  person Pointy    schedule 03.01.2013
comment
@Pointy Это может быть полезно для объектов. IE: {one:1} == {one:1} возвращает false.   -  person Shmiddty    schedule 03.01.2013
comment
Что ж, стандарт фактически говорит, что они равны в смысле ==, но не равны в смысле ===. Какое именно определение равенства вы ищете?   -  person pimvdb    schedule 03.01.2013
comment
@Pointy Семантика. Если вы хотите узнать, эквивалентны ли значения [1,2] и [1,2], === всегда будет возвращать false. Хотя они эквивалентны по значению, структуре и типу, они не являются строго эквивалентными по текущим стандартам ECMA.   -  person sjhcockrell    schedule 03.01.2013
comment
@Mathletics ID не Internet Explorer :P   -  person Shmiddty    schedule 03.01.2013
comment
@sjhcockrell да, но для этого и нужен ==.   -  person Pointy    schedule 03.01.2013
comment
@Shmiddty LOL, упс! браузеры на мозг!   -  person Mathletics    schedule 03.01.2013
comment
@Pointy ​console.log([1,2]==[1,2], {one:1}=={one:1}); регистрирует false false   -  person Shmiddty    schedule 03.01.2013
comment
Похоже, вы придумываете собственное определение равенства, где [1, 2] равно [1, 2]. В этом случае вам, вероятно, также следует решить для себя, следует ли считать 1 и new Number(1) равными. Если вы хотите использовать реализацию, точно соответствующую спецификации, используйте == или ===.   -  person pimvdb    schedule 03.01.2013
comment
@Pointy Я думаю, что {one:1} == {one:1} // false задумано, потому что каждый объект размещается в другом месте в памяти (?) И считается двумя разными объектами и, следовательно, не эквивалентен. Если бы это было a = b = {one: 1}, то a == b // true.   -  person sjhcockrell    schedule 03.01.2013
comment
@pimvdb Думаю, ты прав. Так что это может быть вопрос дизайна, а не практический вопрос, и в этом случае мой вопрос не подходит для SO, и кто-то может его закрыть. :) Ваше здоровье.   -  person sjhcockrell    schedule 03.01.2013
comment
Да, боюсь, это не совсем ответ. На самом деле, вы также можете создавать свои собственные объекты с замыканием. Тогда значения двух объектов могут казаться одинаковыми, но вызов функции для этих объектов вернет разные значения. В этом случае еще труднее определить, могут ли такие объекты быть равными.   -  person pimvdb    schedule 03.01.2013
comment
Вместо того, чтобы полностью убивать этот поток, возможно, было бы разумнее просто немного ограничить его объем? Вопрос о том, существует ли универсальное соглашение о том, как сравнивать объекты с примитивной оболочкой? является законным ТАК, и у него есть ответ (нет). Я мог видеть, что этот вопрос представляет ценность/интерес для других будущих читателей SO.   -  person machineghost    schedule 03.01.2013
comment
@Shmiddty, конечно, потому что JavaScript не выполняет глубокое сравнение (что в общем случае чрезвычайно сложно сделать должным образом; возможно, вообще невозможно каким-либо полезным способом из-за нюансов того, что равно должно означать для сложных структурированных типов ).   -  person Pointy    schedule 03.01.2013
comment
Я думаю, что это может быть полезно - я новичок в SO, поэтому я попытаюсь ограничить вопрос выше.   -  person sjhcockrell    schedule 03.01.2013
comment
@ Пойнти (и Шмидти) Верно. Частично это связано с тем, что я пытался спроектировать эту конкретную функцию isEquals() для аппроксимации глубокого сравнения и предоставления более эффективной альтернативы Underscore.isEquals(). Посмотреть этот JSPerf   -  person sjhcockrell    schedule 03.01.2013


Ответы (1)


Короткий ответ на ваш вопрос: нет, нет единого мнения о том, как сравнивать значения в JS, потому что вопрос слишком ситуативный; это сильно зависит от ваших конкретных обстоятельств.

Но, чтобы дать несколько советов / дать более развернутый ответ .... объектные версии примитивов - зло (в смысле «они вызовут у вас много ошибок», а не в моральном смысле), и их следует избегать, если это возможно. Поэтому, если у вас нет веских причин для обработки обоих, я бы посоветовал вам не учитывать примитивы с объектной оболочкой и просто придерживаться необработанных примитивов в вашем коде.

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

* Изменить *

Только что увидел ваш последний комментарий, и если вам нужно сравнить массивы, то встроенные == и === не помогут. Тем не менее, я бы рекомендовал использовать метод arrayEquals, а не только метод equals, так как вы избежите большого количества драмы, сохраняя свою функцию как можно более сфокусированной и максимально используя встроенные компараторы JS.

И если вы оберните это какой-то общей функцией, для удобства:

function equals(left, right) {
    if (left.slice && right.slice) { // lame array check
        return arrayEquals(left, right);
    }
    return left == right;
}

Я бы по-прежнему рекомендовал не обрабатывать объекты с примитивной оболочкой, если только с помощью «обработки» вы не заставите свою функцию выдавать ошибку, если она передала объект с примитивной оболочкой. Опять же, поскольку эти объекты доставят вам только неприятности, вы должны стараться избегать их, насколько это возможно, и не оставлять себе лазеек для внедрения плохого кода.

person machineghost    schedule 02.01.2013
comment
Я согласен с тем, чтобы держаться подальше от объектов с примитивной оболочкой - в настоящее время они намного медленнее и более запутанны. Я создаю некоторый библиотечный код и получил отзывы о том, что то, как я его обработал (isEqual( 1, new Number(1)) // возвращает false, потому что 1 === new Number(1) возвращает false) неверно. Если нет стандартизированного способа обработки эквивалентности Obj-обертки/примитива, мой вопрос может быть скорее вопросом о дизайне библиотеки того типа, которого SO пытается избежать. - person sjhcockrell; 03.01.2013
comment
Да, я думаю, это все. Не существует универсального консенсуса (за исключением, может быть, того, что объекты в примитивной оболочке - это боль), поэтому в конечном итоге все сводится к тому, что имеет смысл для вашего случая. Я пытался объяснить некоторые принципы (например, стараться не изобретать колесо, насколько это возможно), которые должны быть довольно универсальными, но помимо них вы просто попадаете в субъективную дискуссию, которая, как вы заметили, здесь осуждается. (не говоря уже о бессмысленности: что правильно, то и подходит для вашего случая). - person machineghost; 03.01.2013
comment
@sjhcockrell Обратите внимание на тот факт, что (относительно новый) Number.isNaN() API возвращает false, если вы передаете ему коробочную версию NaN, то есть Number.isNaN(new Number(NaN)) равно false. - person Pointy; 03.01.2013
comment
@Pointy Это чрезвычайно полезно знать - на основе 15.1.2.4 в спецификации ECMA-262 это потому, что только NaN можно привести к NaN. Спасибо, что подняли это; Я добавлю это к вопросу выше. - person sjhcockrell; 03.01.2013