如何跟踪 Javascript 中共享的不可变引用类型的变化
How to track changes with shared, immutable reference types in Javascript
考虑以下示例:
function add(x, y) { return x + y; }
var collection = Object.freeze([1, 2, 3, 4]);
var consumerA = collection; // expects steady data
var consumerB = collection; // requires the latest data
var updatedCollection = collection.concat(5);
consumerA.reduce(add, 0); // 10 (desired result)
consumerB.reduce(add, 0); // 10 (incorrect result, should be 15)
consumerA
使用它期望的不可变数据进行操作。在 Javascript 中可以做什么来确保 consumerB
始终访问最新数据?
请注意:仅深度复制 consumerA
并将 collection
视为可变数据不是一种选择。
更新:该示例仅用于说明由共享引用类型引起的基本问题:一些消费者(或引用持有者)依赖不可变数据,其他消费者依赖可变数据.我正在寻找一种适当的更改跟踪机制来解决这个问题,同时又不会破坏不可变数据的好处。
也许 "change tracking" 这个词太含糊了。对于更改跟踪,我指的是一种让 consumerB
了解更改(推送机制)或(更有趣)能够发现更改(拉动机制)的方法。后者需要 consumerB
以某种方式访问更新的集合。
声明 collection
时使用 Object.freeze
,因此无法向 collection
添加属性。
当您创建 consumerB
时,您执行对象的副本 collection
var consumerB = collection;
因此您不能向 consumerB
添加属性,例如 collection
。
您需要克隆对象而不是复制它。你可以这样做:
var consumerB = JSON.parse(JSON.stringify(collection));
好吧,这是我唯一的解决方案,但可能还有其他解决方案。我将我的不可变集合包装在一个可变对象中。需要常量数据的消费者持有对集合本身的引用。需要当前状态的消费者持有对包装器的引用。我使用原始形式的结构共享以避免克隆:
function add(x, y) { return x + y; }
var collection = Object.freeze([1, 2, 3, 4]);
var atom = {state: collection};
var consumerA = collection;
var consumerB = atom;
console.log(consumerA === consumerB.state); // true (obviously)
// naive structural sharing to avoid cloning
atom.state = Object.create(atom.state, {length: {value: atom.state.length, writable: true}});
atom.state.push(5);
Object.freeze(atom.state);
// as desired
console.log(consumerA.reduce(add, 0)); // 10
console.log(consumerB.state.reduce(add, 0)); // 15
// structural sharing is used
console.log(Object.getPrototypeOf(consumerB.state) === collection); // true
// object comparison simply by reference check
console.log(consumerA === consumerB.state); // false
通过将不可变集合包装在可变包装器中,它成为一种持久数据类型。这意味着它可以被视为一个普通的、可变的对象,但保持其以前的版本不变,因此是持久的。顺便说一句,将包装器命名为 atom
并非偶然,而是对 Clojure 中相应数据类型的引用。
请注意:使用原型系统进行结构共享会导致内存泄漏,请谨慎使用。
考虑以下示例:
function add(x, y) { return x + y; }
var collection = Object.freeze([1, 2, 3, 4]);
var consumerA = collection; // expects steady data
var consumerB = collection; // requires the latest data
var updatedCollection = collection.concat(5);
consumerA.reduce(add, 0); // 10 (desired result)
consumerB.reduce(add, 0); // 10 (incorrect result, should be 15)
consumerA
使用它期望的不可变数据进行操作。在 Javascript 中可以做什么来确保 consumerB
始终访问最新数据?
请注意:仅深度复制 consumerA
并将 collection
视为可变数据不是一种选择。
更新:该示例仅用于说明由共享引用类型引起的基本问题:一些消费者(或引用持有者)依赖不可变数据,其他消费者依赖可变数据.我正在寻找一种适当的更改跟踪机制来解决这个问题,同时又不会破坏不可变数据的好处。
也许 "change tracking" 这个词太含糊了。对于更改跟踪,我指的是一种让 consumerB
了解更改(推送机制)或(更有趣)能够发现更改(拉动机制)的方法。后者需要 consumerB
以某种方式访问更新的集合。
声明 collection
时使用 Object.freeze
,因此无法向 collection
添加属性。
当您创建 consumerB
时,您执行对象的副本 collection
var consumerB = collection;
因此您不能向 consumerB
添加属性,例如 collection
。
您需要克隆对象而不是复制它。你可以这样做:
var consumerB = JSON.parse(JSON.stringify(collection));
好吧,这是我唯一的解决方案,但可能还有其他解决方案。我将我的不可变集合包装在一个可变对象中。需要常量数据的消费者持有对集合本身的引用。需要当前状态的消费者持有对包装器的引用。我使用原始形式的结构共享以避免克隆:
function add(x, y) { return x + y; }
var collection = Object.freeze([1, 2, 3, 4]);
var atom = {state: collection};
var consumerA = collection;
var consumerB = atom;
console.log(consumerA === consumerB.state); // true (obviously)
// naive structural sharing to avoid cloning
atom.state = Object.create(atom.state, {length: {value: atom.state.length, writable: true}});
atom.state.push(5);
Object.freeze(atom.state);
// as desired
console.log(consumerA.reduce(add, 0)); // 10
console.log(consumerB.state.reduce(add, 0)); // 15
// structural sharing is used
console.log(Object.getPrototypeOf(consumerB.state) === collection); // true
// object comparison simply by reference check
console.log(consumerA === consumerB.state); // false
通过将不可变集合包装在可变包装器中,它成为一种持久数据类型。这意味着它可以被视为一个普通的、可变的对象,但保持其以前的版本不变,因此是持久的。顺便说一句,将包装器命名为 atom
并非偶然,而是对 Clojure 中相应数据类型的引用。
请注意:使用原型系统进行结构共享会导致内存泄漏,请谨慎使用。