利用javascript原型系统创建共享结构的不可变对象有意义吗
到目前为止,对于Javascript中的不变性,似乎有两种相反的解决方案:利用javascript原型系统创建共享结构的不可变对象有意义吗,javascript,prototype,immutability,prototypal-inheritance,immutable.js,Javascript,Prototype,Immutability,Prototypal Inheritance,Immutable.js,到目前为止,对于Javascript中的不变性,似乎有两种相反的解决方案: immutable.js 无缝不变 immutable.js引入了它们自己的(浅显的)不可变对象,这些对象与对象和数组的默认javascript协议不兼容 无缝不可变使用POJO,POJO完全不可变,没有任何魔力,但没有结构共享 将这两个世界的优点结合在一起将是非常好的。不变的原型链/树是否是合适的解决方案 潜在的原型机制带来了希望: var a = [1, 2, 3]; var b = Object.create(
- immutable.js
- 无缝不变
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 !!!
这一点很简单:length属性继承自不可变原型,因此不可变。解决问题并不难:
var b = Object.create(a, {length: {value: a.length, writable: true}});
但可能还有其他问题,特别是在更复杂的现实世界场景中
也许有人已经处理过这个想法,可以告诉我,如果它值得推理的话
Aadit是关于一个相关问题的,Bergi对此的评论触及了我的问题,但没有给出答案
js引擎似乎会自动将这些值直接复制到实例中
这是因为所有数组方法(shift
,push
等)都只使用索引赋值(幸运的是,赋值到.length
,在非数组上不会自动更新)。正如您所知,赋值只是在继承对象上创建一个新属性,即使原型具有该属性(除非它具有奇怪的属性,如冻结长度示例中所示)
不管怎样,你真正的问题是
不变的原型链/树是否是合适的解决方案
否。问题是,原型链永远不会被垃圾收集。一旦所有继承的属性被新的“变异”实例覆盖,引擎就不会知道您“不再需要”原型,并将其永久保留。您需要手动对其进行垃圾收集(取消引用),这正是immutable.js及其结构共享所做的。只有这样,您才能更好地以其他方式管理您的结构,并手动执行属性查找。除了内存泄漏之外,在使用原型系统进行结构共享时,您还会遇到更多问题:
- 长原型链中的不利查找时间
- 在ES5中,与阵列相关的
丢失[[DefineOwnProperty]]
[[DefineOwnProperty]]
的丢失。此内部方法负责将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
[点评]我相信,所有这些问题都可以解决,而且只需极少量的代码、很小的API和很浅的学习曲线。这样一个系统可能不会像基于向量或哈希映射的不可变数据结构那样高效。但这比仅仅复制对象要有效得多。[/opinion]我觉得这会是一个非常有趣的讨论,但对于堆栈溢出(更适合于特定问题)来说,这并不是一种正确的讨论。一旦你打了20个代表,你完全应该来和我们讨论一下。是的,我不想和那边的Aadit讨论,但我很高兴在这里提供一个答案。希望@AaditMShah能给出一些反驳,我想学习一些新的东西:-)我在提到原型树时提到了共享原型的概念。事实上,这在原型上下文中是误导和笨拙的。当然,每个javascript对象只能有一个直接原型。谢谢你的澄清@艾文玛夸特:啊,我明白了。只是有其他语言确实允许多个原型从中继承,并更容易地交换它们,所以我想保证您不会被这些语言弄糊涂。是的,
[[prototype]]
不是一个选项。我想你不能防止内存泄漏,至少不能在这么低的级别上。也许你可以确定实例I及其原型p在创建I时的属性的相对补充。由于i和p保证保持不变(就像链中的所有其他原型一样),这些计算应该只发生一次(在创建新实例时)。若并没有留下任何差异或达到某个阈值,则深入复制i并标记为GC。如果我不再持有隐式或显式引用,它将被垃圾收集。这听起来很复杂,如果它在任何方面都实用的话,我也没有任何线索。我刚刚意识到,当你对数组进行子类化时,你在[[Class]]
中松开了数组,因此破坏了Object.prototype.toString.call
。太糟糕了!
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