2

Я пытаюсь использовать ES6 от TypeScript через lib.es6.d.ts. Мне нужно понять, как enforce equality comparison and sameness для объекта для использования в Set<T>. Например, объект выглядит следующим образом.Как проверить равенство и идентичность экземпляров класса в отношении использования коллекции Set ES6?

class Product { 
metadata: Map<String, String> 

constructor(id: number) { 
    metadata = new Map<String, String>(); 
} 

public addMetadata(key: String, val: String): Product { 
    this.metadata.set(key, val); 
    return this; 
} 
} 

Обратите внимание, что id значение поля в Product является то, что определяет его уникальность. Если два экземпляра продукта имеют одинаковый идентификатор, они считаются одинаковыми в моем приложении, даже если metadata отличается. В общем, я хочу только лишь подмножество полей, которые будут использоваться как часть тестирования для равенства и одинаковости.

В Java мы переопределяем метод equals для контроля и проверки на идентичность. В JavaScript, что нам нужно сделать, чтобы определить сходство?

В link on MDN гласит следующее:

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

Я предполагаю, что при значении равенства они означают ===? Опять же, MDN shows 4 equality algorithms только в ES2015.

В принципе, в моем классе выше, я хочу сделать что-то вроде следующего.

let p1 = new Product(1); 
let p2 = new Product(2); 
let p3 = new Product(1); //duplicate of p1 by id 

p1.addMetadata('k1','v1').addMetadata('k2','v2'); 
p2.addMetadata('k1','v1'); 

let mySet = new Set<Product>(); 
mySet.add(p1); 
mySet.add(p2); 
mySet.add(p3); 

assertEquals(2, mySet.size); //some assertion method 
+0

У меня нет хорошего ответа, но я могу прояснить ситуацию. В javascript «==» или «===» для не-примитивов используется ссылочное равенство. Таким образом, это должен быть тот же самый экземпляр для оценки «true» (вообще не проверяет свойства). Я долго не смотрел в «Set» и «Map», но, надеюсь, это помогло. – Adrian

+1

Возможный дубликат [Как настроить выравнивание объектов для набора JavaScript] (http://stackoverflow.com/questions/29759480/how-to-customize-object-equality-for-javascript-set) – Adrian

+0

@Aliester - как один который дал ответ на этот другой вопрос, я действительно не думаю, что это дурак. Вопрос здесь совершенно другой. Чтение этого другого ответа, вероятно, даст вам некоторую информацию, которую можно было бы использовать, чтобы ответить на этот вопрос, но никто не скажет, что этот вопрос такой же, как этот вопрос. – jfriend00

ответ

1

Set или Map считает ключ, который является объектом такой же, только если он точно такой же объект, а не другой объект с таким же содержанием, но тот же реальный объект. Другими словами, он работает так же, как obj1 === obj2.

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

В этом вопросе How to customize object equality for JavaScript Set обсуждается, можно ли это настроить для Set или нет (это не так).


Если вы с помощью Map (который, кажется, что ваш код относится, несмотря на то, что это не то, что говорится в тексте Вашего вопроса), то вы можете использовать .id свойство в качестве ключа и объект как значение. Пока свойство .id является примитивным (например, строкой или числом), вы получите только один элемент в Map для любого заданного id.

1

Непосредственно отвечая на ваш вопрос, но «помеченные наборы» (из-за отсутствия лучшего имени) могут работать лучше, чем переопределенные операторы равенства. Таким образом, «равенство» является атрибутом самого набора, а не базовых объектов.Вот пример JS:

class SetBy extends Set { 
 

 
    constructor(pred) { 
 
     super(); 
 
     this.pred = pred; 
 
     this.inner = new Set(); 
 
    } 
 

 
    has(obj) { 
 
     return this.inner.has(this.pred(obj)); 
 
    } 
 

 
    add(obj) { 
 
     if (!this.has(obj)) { 
 
      this.inner.add(this.pred(obj)); 
 
      super.add(obj); 
 
     } 
 
    } 
 
} 
 

 

 
s = new SetBy(x => x.id); 
 

 
a = {id: 1, name: 'a'}; 
 
b = {id: 1, name: 'b'}; 
 
c = {id: 1, name: 'c'}; 
 
d = {id: 2, name: 'd'}; 
 
e = {id: 2, name: 'e'}; 
 

 
s.add(a); 
 
s.add(b); 
 
s.add(c); 
 
s.add(d); 
 
s.add(e); 
 

 
console.log([...s]);

+0

Этот подход пропускает 'delete'. Реализация это менее забавно, чем 'add'. – estus

+0

@estus: это тоже однострочный, нет? – georg

+0

@georg Нет, так как вам нужно удалить объект из внешнего набора, который имеет тот же предикат, что и тот, который удаляется. Внутренняя «Карта» (вместо «Set»), которая указывает на внешний объект, может решить эту проблему. – Bergi

2

Простой (а также производительный) способ проверить, если объекты равны, чтобы сравнить JSON.stringify() строк. В связи с тем, что только собственной перечисляемыми свойствами, строковыми, metadata собственности должна быть не перечислима:

class Product { 
metadata: Map<String, String> 

constructor(public id: number) { 
    Object.defineProperty(this, 'metadata', { 
     configurable: true, 
     writable: true 
    }) 

    this.metadata = new Map<String, String>(); 
} 
... 
} 

Это приведет:

new Product(1) !== new Product(1); 
JSON.stringify(new Product(1)) === JSON.stringify(new Product(1)); 
JSON.stringify(new Product(1)) !== JSON.stringify(new Product(2)); 

Этот подход может быть использован в пользовательских Set:

class ObjectedSet extends Set { 
    protected _find(searchedValue) { 
    for (const value of Array.from(this.values())) 
     if (JSON.stringify(value) === JSON.stringify(searchedValue)) 
     return searchedValue; 
    } 

    has(value) { 
    return !!this._find(value); 
    } 

    add(value) { 
    if (!this.has(value)) 
     super.add(value); 

    return this; 
    } 

    delete(value) { 
    const foundValue = this._find(value); 

    if (foundValue) 
     super.delete(foundValue); 

    return !!foundValue; 
    } 
} 
+0

Строка сериализации звучит как ужасно бесперспективный способ сравнения сравнений. Почему вы думаете, что это быстро? – Bergi

+0

@ Bergi Я бы ожидал этого, но в моих собственных тестах сравнение «JSON.stringify» выполняется наравне с '_.isEqual'. Оба были быстрыми, поэтому это соображение попадает в категорию предварительной оптимизации. Если Lodash не используется в проекте, я бы счел «JSON.stringify» без проблем. – estus

+0

Да, для небольших и равных входов я бы поставил их на один уровень. Но если два входа имеют большую структуру данных (что '_.isEqual' может проверять по ссылочному идентификатору) или для больших, но неравных структур данных (что' JSON.stringify' по-прежнему нужно полностью пересекать), я бы ожидал серьезных различий. – Bergi

Смежные вопросы