Это старый вопрос, но я думаю, что у меня хороший случай, когда замораживание может помочь. У меня была эта проблема сегодня.
Проблема
class Node {
constructor() {
this._children = [];
this._parent = undefined;
}
get children() { return this._children; }
get parent() { return this._parent; }
set parent(newParent) {
// 1. if _parent is not undefined, remove this node from _parent's children
// 2. set _parent to newParent
// 3. if newParent is not undefined, add this node to newParent's children
}
addChild(node) { node.parent = this; }
removeChild(node) { node.parent === this && (node.parent = undefined); }
...
}
Как вы можете видеть, когда вы изменяете родитель, он автоматически обрабатывает связь между этими узлами, держа детей и родителей в синхронизации. Однако, есть одна проблема:
let newNode = new Node();
myNode.children.push(newNode);
Теперь myNode
имеет newNode
в своем children
, но newNode
не myNode
как его parent
. Таким образом, вы только что сломали его.
(OFF-TOPIC) Почему вы все равно подвергаете детей?
Да, я мог бы просто создать множество методов: countChildren(), getChild (index), getChildrenIterator() (который возвращает генератор), findChildIndex (узел) и т. Д. ... но действительно ли это лучший подход, чем просто возврат массива, который предоставляет интерфейс, который уже знают программисты javascript?
- Вы можете получить доступ к своему
length
, чтобы узнать, сколько у него детей;
- Вы можете получить доступ к детям по их индексу (то есть
children[i]
);
- Вы можете перебирать его, используя
for .. of
;
- И вы можете использовать другие полезные методы, предоставляемые массивом.
Примечание: возврат копии массива не может быть и речи! Это стоит линейного времени, и любые обновления исходного массива не распространяются на копию!
Раствор
get children() { return Object.freeze(Object.create(this._children)); }
// OR, if you deeply care about performance:
get children() {
return this._PUBLIC_children === undefined
? (this._PUBLIC_children = Object.freeze(Object.create(this._children)))
: this._PUBLIC_children;
}
Готово!
Object.create
: мы создаем объект, который наследует от this._children
(т.е. имеет this._children
в качестве своего __proto__
). Это само по себе решает почти все проблемы:
- Это просто и быстро (постоянное время)
- Вы можете использовать что-либо предоставленное интерфейсом массива
- Если изменить возвращаемый объект, он не изменяет оригинал!
Object.freeze
: однако тот факт, что вы можете изменить возвращаемый объект, НО изменения не влияют на исходный массив, крайне запутанный для пользователя класса! Итак, мы просто замораживаем это. Если он пытается его модифицировать, исключается исключение (при условии строгого режима), и он знает, что не может (и почему). Печально, что не исключено исключение для myFrozenObject[x] = y
, если вы не находитесь в строгом режиме, но myFrozenObject
не изменяется в любом случае, так что это все еще не так странно.
Конечно, программист может обойти это путем доступа __proto__
, например:
someNode.children.__proto__.push(new Node());
Но мне нравится думать, что в этом случае они на самом деле знают, что они делают, и есть хороший повод, чтобы сделать это ,
ВАЖНО: обратите внимание, что это не так хорошо работает для объектов: использование hasOwnProperty в for .. in всегда будет возвращать false.
UPDATE: использование Proxy для решения тех же задач для объектов
Только для завершения: если у вас есть объект вместо массива вы можете решить эту проблему с помощью прокси-сервера. На самом деле, это общее решение, которое должно работать с любым типом элемента, но я не рекомендую (если вы можете избежать его) из-за проблем с производительностью:
get myObject() { return Object.freeze(new Proxy(this._myObject, {})); }
Это еще возвращает объект, который не может быть изменен , но сохраняет все функциональные возможности только для чтения. Если вам действительно нужно, вы можете отказаться от Object.freeze
и реализовать требуемые ловушки (set, deleteProperty, ...) в прокси, но это требует дополнительных усилий, и именно поэтому Object.freeze
пригодится прокси.
Зачем вам нужно сортировать массив? Потому что вам нужно его сортировать для того, чтобы X Y Z. – Jon
Невосприимчивость ортогональна статической типизации. Если ваша программа ожидает/требует, чтобы объект не был изменен, тогда «замораживание» объекта будет защищать от кода, который [неправильно] пытается его изменить. – 2013-02-09 20:44:34
@bonomo Это ортогонально, на самом деле. Возьмем такой язык, как Java. И, скажем, интерфейс 'List'. Предположим, мы создаем новый изменяемый объект, например 'l = new ArrayList '. Теперь предположим, что мы делаем 'Collections.unmodifiableList (l)', который возвращает новый список, а также 'List '. Однако этот новый список является неизменным, а исходный список изменен. Оба соответствуют «Перечислению ». –
2013-02-09 22:29:58