简体   繁体   English

通过利用javascript原型系统创建共享结构的不可变对象是否有意义

[英]Does it make sense to create immutable objects that share structure by utilizing the javascript prototype system

So far, there seem to be two opposing solutions to immutability in Javascript: 到目前为止,在Javascript中似乎有两种相反的不变性解决方案:

  • immutable.js immutable.js
  • seamless-immutable 无缝不变

immutable.js introduces their own (shallowly) immutable objects that are incompatible with the default javascript protocols for objects and arrays. immutable.js引入了他们自己的(浅)不可变对象,这些对象与对象和数组的默认javascript协议不兼容。

seamless-immutable uses POJOs that are completely immutable without any magic, but do without structural sharing. 无缝不可变使用完全不可变的POJO,没有任何魔法,但没有结构共享。

It would be great to combine the best of both worlds. 将两者的优点结合起来会很棒。 Could immutable prototype chains/trees be a proper solution? 不可变的原型链/树可以成为一个合适的解决方案吗?

The underlying prototype mechanism gives hope: 底层原型机制带来了希望:

var a = [1, 2, 3];
var b = Object.create(a);

b[0]; // 1
b.map(function (x) { return ++x; }); // 2, 3, 4 

b.push(4, 5, 6); // initial assignment of b
a; // 1, 2, 3
b;  // 1, 2, 3, 4, 5, 6

for (var i = 0; i < b.length; i++) {
  console.log(b[i]);
} // 1, 2, 3, 4, 5, 6

a[1] = null; // prototype mutation
a; // 1, null, 3
b; // 1, null, 3, 4, 5, 6

b.unshift(0); // instance mutation
a; // 1, null, 3
b; // 0, 1, null, 3, 4, 5, 6 !!!

Whenever a mutation (unshift) of the current instance (b) makes it impossible for its prototypes to provide their values, the js engine seems to copy these values straight into the instance automatically. 每当当前实例(b)的突变(非移位)使其原型无法提供其值时,js引擎似乎会自动将这些值直接复制到实例中。 I didn't know that, but it makes total sense. 我不知道,但它完全有道理。

However, working with immutable (keyed/indexed) objects one quickly encounters problems: 但是,使用不可变(键控/索引)对象很快就会遇到问题:

var a = [1, 2, 3];
Object.freeze(a);
var b = Object.create(a);
b.push(4, 5, 6); // Error: Cannot assign to read only property "length"
Object.freeze(b);

This one is simple: The length property is inherited from the immutable prototype and hence not mutable. 这个很简单:length属性继承自不可变原型,因此不可变。 Fixing the problem isn't hard: 解决问题并不难:

var b = Object.create(a, {length: {value: a.length, writable: true}});

But there will probably be other issues, in particular in more complex, real world scenarios. 但可能会有其他问题,特别是在更复杂的现实世界场景中。

Maybe someone have already dealt with this idea and can tell me, if it's worth reasoning about it. 也许有人已经处理过这个想法,可以告诉我,是否值得推理。

Aadit's answer to a related question and Bergi's comment on it touches my question without giving an answer. Aadit对相关问题的回答以及Bergi对此的评论触及了我的问题而没有给出答案。

the js engine seems to copy these values straight into the instance automatically js引擎似乎会自动将这些值直接复制到实例中

That's because all the array methods ( shift , push etc) just use assignment to indices (and fortunately, to .length , which isn't automatically updated on non-arrays). 那是因为所有数组方法( shiftpush等)只是使用赋值给索引(幸运的是,到.length ,它不会在非数组上自动更新)。 As you know, assignment just creates a new property on the inheriting object, even if the prototype had that property (unless it has weird attributes, like in your frozen-length example). 如您所知,赋值只是在继承对象上创建一个新属性,即使原型具有该属性(除非它具有奇怪的属性,如在您的冻结长度示例中)。

Regardless, your actual question was 无论如何,你的实际问题是

Could immutable prototype chains/trees be a proper solution? 不可变的原型链/树可以成为一个合适的解决方案吗?

No . The problem is that a prototype chain is never garbage-collected. 问题是原型链永远不会被垃圾收集。 The engine doesn't know that you "don't need" the prototype any more once all the inherited properties are overwritten by your new "mutated" instance - and keeps it around forever. 一旦所有继承的属性被新的“变异”实例覆盖,引擎就不知道你“不再需要”原型 - 并永远保留它。
You'd need to manually garbage-collect (dereference) it, which is just what immutable.js with its structural sharing does. 你需要手动垃圾收集(取消引用)它,这正是immutable.js与其结构共享的作用。 Only that mutating the [[prototype]] is a bad idea , so you better manage your structures in other ways and do property lookup manually. 只有改变[[prototype]]是一个坏主意 ,所以你最好用其他方式管理你的结构并手动进行属性查找。

In addition to memory leaking you encounter further problems when you use the prototype system for structural sharing: 除了内存泄漏之外,当您使用原型系统进行结构共享时,还会遇到更多问题:

  • unfavorable lookup time in long prototype chains 在长原型链中不利的查找时间
  • in ES5 loss of [[DefineOwnProperty]] in connection with arrays 在ES5中与数组相关的[[DefineOwnProperty]]丢失

Lookups in hash tables are usually very efficient. 哈希表中的查找通常非常有效。 If an object is frequently mutated, however, very long prototype chains may arise. 但是,如果对象经常发生变异,则可能会出现很长的原型链。 In the worst case the entire chain must be traversed. 在最坏的情况下,必须遍历整个链。 If such behavior occurs on a regular basis, it can have an adverse effect on performance. 如果这种行为定期发生,则会对性能产生不利影响。

Array subclassing leads to the loss of [[DefineOwnProperty]] . 数组子类化会导致[[DefineOwnProperty]]的丢失。 This internal method is responsible for synchronizing the length property with the actual number of elements in arrays: 此内部方法负责将length属性与数组中的实际元素数同步:

function A() {}
A.prototype = Array.prototype;
var a = new A();

a[0] = 0, a[1] = 1;
console.log(a.length); // 0
a.length = 2;
console.log(a); // [0, 1]
a.length = 1;
console.log(a[1]); // 1

[opinion]I am convinced that all these problems can be solved and that with incredibly little code, a tiny API and a shallow learning curve. [意见]我确信所有这些问题都可以通过极少的代码,微小的API和浅薄的学习曲线来解决。 Such a system would likely not be as efficient as immutable data structures based on vector or hash map tries. 这样的系统可能不如基于向量或散列映射尝试的不可变数据结构有效。 But it would be much more efficient than mere copying of objects.[/opinion] 但它比仅仅复制对象更有效率。[/ views]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM