这篇博文回答了如下问题:html
主要内容c++
共享可变状态工做以下:正则表达式
function logElements(arr) {
while (arr.length > 0) {
console.log(arr.shift());
}
}
function main() {
const arr = ['banana', 'orange', 'apple'];
console.log('Before sorting:');
logElements(arr);
arr.sort(); // changes arr
console.log('After sorting:');
logElements(arr); // (A)
}
main();
// Output:
// 'Before sorting:'
// 'banana'
// 'orange'
// 'apple'
// 'After sorting:'
复制代码
这里有两个独立的部分:函数logElements()和函数main()。后者但愿在排序以前和以后记录一个数组。可是,它使用logElements()来清除参数。所以,main()在第A行记录一个空数组。json
在这篇文章的其他部分,咱们将讨论三种避免共享可变状态问题的方法:数组
避免经过复制数据来共享安全
经过非破坏性更新来避免突变bash
经过使数据不可变来防止突变数据结构
接下来,咱们将回到咱们刚刚看到的例子并修正它。app
在讨论复制如何避免共享以前,咱们须要看看如何在JavaScript中复制数据。函数
数据复制有两个“深度”:
下一节将介绍这两种复制。不幸的是,JavaScript只内置了对浅拷贝的支持。若是咱们须要深度复制,咱们须要本身实现它。
让咱们来看看几种简单复制数据的方法。
咱们能够扩展到对象文字和数组文字复制:
const copyOfObject = {...originalObject};
const copyOfArray = [...originalArray];
复制代码
不过,扩展复制有几个限制:
class MyClass {}
const original = new MyClass();
assert.equal(MyClass.prototype.isPrototypeOf(original), true);
const copy = {...original};
assert.equal(MyClass.prototype.isPrototypeOf(copy), false);
复制代码
特殊的对象,如正则表达式和日期,具备特殊属性的“内部插槽”,不会被复制
只复制本身的(非继承的)属性。考虑到原型链是如何工做的,这一般是最好的方法。但你仍然须要意识到这一点。在下面的示例中,original的继承属性. inheritedprop在copy中不可用,由于咱们只复制本身的属性,不保留原型。
const proto = { inheritedProp: 'a' };
const original = {__proto__: proto, ownProp: 'b' };
assert.equal(original.inheritedProp, 'a');
assert.equal(original.ownProp, 'b');
const copy = {...original};
assert.equal(copy.inheritedProp, undefined);
assert.equal(copy.ownProp, 'b');
复制代码
const arr = ['a', 'b'];
assert.equal(arr.length, 2);
assert.equal({}.hasOwnProperty.call(arr, 'length'), true);
const copy = {...arr};
assert.equal({}.hasOwnProperty.call(copy, 'length'), false);
复制代码
const original = Object.defineProperties({}, {
prop: {
value: 1,
writable: false,
configurable: false,
enumerable: true,
},
});
assert.deepEqual(original, {prop: 1});
const copy = {...original};
// Attributes `writable` and `configurable` of copy are different:
assert.deepEqual(Object.getOwnPropertyDescriptors(copy), {
prop: {
value: 1,
writable: true,
configurable: true,
enumerable: true,
},
});
复制代码
这意味着getter和setter也不会被忠实地复制:属性值(用于数据属性)、get(用于getter)和set(用于setter)是独立的。
const original = {
get myGetter() { return 123 },
set mySetter(x) {},
};
assert.deepEqual({...original}, {
myGetter: 123, // not a getter anymore!
mySetter: undefined,
});
复制代码
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = {...original};
// Property .name is a copy
copy.name = 'John';
assert.deepEqual(original,
{name: 'Jane', work: {employer: 'Acme'}});
assert.deepEqual(copy,
{name: 'John', work: {employer: 'Acme'}});
// The value of .work is shared
copy.work.employer = 'Spectre';
assert.deepEqual(
original, {name: 'Jane', work: {employer: 'Spectre'}});
assert.deepEqual(
copy, {name: 'John', work: {employer: 'Spectre'}});
复制代码
有些限制能够消除,有些则不能:
class MyClass {}
const original = new MyClass();
const copy = {
__proto__: Object.getPrototypeOf(original),
...original,
};
assert.equal(MyClass.prototype.isPrototypeOf(copy), true);
复制代码
或者,咱们能够在副本建立以后经过Object.setPrototypeOf()设置副本的原型。
assign()的工做方式大多相似于将对象扩展到对象中。也就是说,如下两种复制方式基本相同:
const copy1 = {...original};
const copy2 = Object.assign({}, original);
复制代码
使用方法而不是语法的好处是,它能够经过库在旧的JavaScript引擎上填充。
不过,Object.assign()并不彻底像spread那样。它有一个比较微妙的不一样点:它以不一样的方式建立属性。
在其余方面,赋值(assign)调用本身的和继承的setter,而定义(这里指的扩展)不调用(关于赋值与定义的更多信息)。这种差别不多被注意到。下面的代码是一个例子,但它是人为的:
const original = {['__proto__']: null};
const copy1 = {...original};
// copy1 has the own property '__proto__'
assert.deepEqual(
Object.keys(copy1), ['__proto__']);
const copy2 = Object.assign({}, original);
// copy2 has the prototype null
assert.equal(Object.getPrototypeOf(copy2), null);
复制代码
JavaScript容许咱们经过属性描述符建立属性,即指定属性属性的对象。例如,经过Object.defineProperties(),咱们已经在实际中看到了它。若是咱们把这个方法和Object.getOwnPropertyDescriptors()结合起来,咱们能够更忠实地复制:
function copyAllOwnProperties(original) {
return Object.defineProperties(
{}, Object.getOwnPropertyDescriptors(original));
}
复制代码
这消除了经过扩展复制对象的两个限制。
首先,正确复制本身属性的全部属性。所以,咱们如今能够复制本身的getter和setter:
const original = {
get myGetter() { return 123 },
set mySetter(x) {},
};
assert.deepEqual(copyAllOwnProperties(original), original);
复制代码
其次,因为Object.getOwnPropertyDescriptors(),不可枚举的属性也被复制了:
const arr = ['a', 'b'];
assert.equal(arr.length, 2);
assert.equal({}.hasOwnProperty.call(arr, 'length'), true);
const copy = copyAllOwnProperties(arr);
assert.equal({}.hasOwnProperty.call(copy, 'length'), true);
复制代码
如今是时候解决深层复制了。首先,咱们将手动深拷贝,而后咱们将检查通用方法。
若是咱们嵌套扩展,咱们获得深层拷贝:
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = {name: original.name, work: {...original.work}};
// We copied successfully:
assert.deepEqual(original, copy);
// The copy is deep:
assert.ok(original.work !== copy.work);
复制代码
HACK:经过JSON 进行通用深度复制 这是一个骇客方法,但在紧要关头,它提供了一个快速解决方案:为了深度复制一个对象的原始,咱们首先把它转换成一个JSON字符串和解析那个JSON字符串:
function jsonDeepCopy(original) {
return JSON.parse(JSON.stringify(original));
}
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = jsonDeepCopy(original);
assert.deepEqual(original, copy);
复制代码
这种方法的显著缺点是,咱们只能复制JSON支持的键和值的属性。
一些不支持的键和值被简单地忽略:
assert.deepEqual(
jsonDeepCopy({
[Symbol('a')]: 'abc',
b: function () {},
c: undefined,
}),
{} // empty object
);
复制代码
其余缘由例外:
assert.throws(
() => jsonDeepCopy({a: 123n}),
/^TypeError: Do not know how to serialize a BigInt$/);
复制代码
下面的函数通常深拷贝一个原始的值:
function deepCopy(original) {
if (Array.isArray(original)) {
const copy = [];
for (const [index, value] of original.entries()) {
copy[index] = deepCopy(value);
}
return copy;
} else if (typeof original === 'object' && original !== null) {
const copy = {};
for (const [key, value] of Object.entries(original)) {
copy[key] = deepCopy(value);
}
return copy;
} else {
// Primitive value: atomic, no need to copy
return original;
}
}
复制代码
该函数处理三种状况:
让咱们试试deepCopy():
const original = {a: 1, b: {c: 2, d: {e: 3}}};
const copy = deepCopy(original);
// Are copy and original deeply equal?
assert.deepEqual(copy, original);
// Did we really copy all levels
// (equal content, but different objects)?
assert.ok(copy !== original);
assert.ok(copy.b !== original.b);
assert.ok(copy.b.d !== original.b.d);
复制代码
请注意,deepCopy()只修复了一个扩展问题:浅复制。全部其余的都保持不变:原型不被复制,特殊对象只被部分复制,不可枚举的属性被忽略,大多数属性属性被忽略。
实现彻底的复制一般是不可能的:不是全部的数据都是树,有时你不想要全部的属性,等等。
deepCopy()的更简洁版本
若是咱们使用.map()和Object.fromEntries(),咱们可使以前的deepCopy()实现更简洁:
function deepCopy(original) {
if (Array.isArray(original)) {
return original.map(elem => deepCopy(elem));
} else if (typeof original === 'object' && original !== null) {
return Object.fromEntries(
Object.entries(original)
.map(([k, v]) => [k, deepCopy(v)]));
} else {
// Primitive value: atomic, no need to copy
return original;
}
}
复制代码
一般使用两种技术来实现类实例的深度复制:
.clone() methods
该技术为每一个要深度复制其实例的类引入了一个.clone()方法。它会返回一个深度副本。下面的示例显示了能够克隆的三个类。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
clone() {
return new Point(this.x, this.y);
}
}
class Color {
constructor(name) {
this.name = name;
}
clone() {
return new Color(this.name);
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
clone() {
return new ColorPoint(
this.x, this.y, this.color.clone()); // (A)
}
}
复制代码
第A行演示了此技术的一个重要方面:还必须递归地克隆复合实例属性值。
静态工厂方法
复制构造函数是使用当前类的另外一个实例来设置当前实例的构造函数。复制构造函数在静态语言(如c++和Java)中很流行,在这些语言中,能够经过静态重载(静态意味着在编译时发生)提供构造函数的多个版本。
在JavaScript中,你能够这样作(但不是很优雅):
class Point {
constructor(...args) {
if (args[0] instanceof Point) {
// Copy constructor
const [other] = args;
this.x = other.x;
this.y = other.y;
} else {
const [x, y] = args;
this.x = x;
this.y = y;
}
}
}
复制代码
你能够这样使用这个类:
const original = new Point(-1, 4);
const copy = new Point(original);
assert.deepEqual(copy, original);
复制代码
相反,静态工厂方法在JavaScript中工做得更好(静态意味着它们是类方法)。
在下面的例子中,三个类Point, Color和ColorPoint都有一个静态的工厂方法.from():
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static from(other) {
return new Point(other.x, other.y);
}
}
class Color {
constructor(name) {
this.name = name;
}
static from(other) {
return new Color(other.name);
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
static from(other) {
return new ColorPoint(
other.x, other.y, Color.from(other.color)); // (A)
}
}
复制代码
在第A行,咱们再次使用递归复制。
这就是ColorPoint.from()的工做方式:
const original = new ColorPoint(-1, 4, new Color('red'));
const copy = ColorPoint.from(original);
assert.deepEqual(copy, original);
复制代码
只要咱们只从共享状态读取,就不会有任何问题。在咱们修改它以前,咱们须要“取消共享”它,经过复制它(尽量深刻)。
防护性复制是一种在可能出现问题时进行复制的技术。它的目标是保持当前实体(函数、类等)的安全
请注意,这些措施保护咱们不受其余方的侵害,但它们也保护其余方不受咱们的侵害。
下一节将演示这两种防护性复制。
请记住,在本文开头的激励示例中,咱们遇到了麻烦,由于logElements()修改了它的参数arr:
function logElements(arr) {
while (arr.length > 0) {
console.log(arr.shift());
}
}
复制代码
让咱们添加防护复制到这个函数:
function logElements(arr) {
arr = [...arr]; // defensive copy
while (arr.length > 0) {
console.log(arr.shift());
}
}
复制代码
如今logElements()再也不引发问题,若是它是调用main():
function main() {
const arr = ['banana', 'orange', 'apple'];
console.log('Before sorting:');
logElements(arr);
arr.sort(); // changes arr
console.log('After sorting:');
logElements(arr); // (A)
}
main();
// Output:
// 'Before sorting:'
// 'banana'
// 'orange'
// 'apple'
// 'After sorting:'
// 'apple'
// 'banana'
// 'orange'
复制代码
让咱们从一个不复制其公开的内部数据的类StringBuilder开始(第A行):
class StringBuilder {
constructor() {
this._data = [];
}
add(str) {
this._data.push(str);
}
getParts() {
// We expose internals without copying them:
return this._data; // (A)
}
toString() {
return this._data.join('');
}
}
复制代码
只要没有使用.getParts(),一切均可以正常工做:
const sb1 = new StringBuilder();
sb1.add('Hello');
sb1.add(' world!');
assert.equal(sb1.toString(), 'Hello world!');
复制代码
可是,若是.getParts()的结果改变(行A),则StringBuilder将中止正常工做:
const sb2 = new StringBuilder();
sb2.add('Hello');
sb2.add(' world!');
sb2.getParts().length = 0; // (A)
assert.equal(sb2.toString(), ''); // not OK
复制代码
解决方案是在暴露以前防护性地复制内部的._data(行A):
class StringBuilder {
constructor() {
this._data = [];
}
add(str) {
this._data.push(str);
}
getParts() {
// Copy defensively
return [...this._data]; // (A)
}
toString() {
return this._data.join('');
}
}
复制代码
如今,更改.getParts()的结果再也不干扰sb的操做:
const sb = new StringBuilder();
sb.add('Hello');
sb.add(' world!');
sb.getParts().length = 0;
assert.equal(sb.toString(), 'Hello world!'); // OK
复制代码
咱们将首先探讨破坏性数据更新和非破坏性数据更新的区别。而后咱们将学习非破坏性更新如何避免突变。
咱们能够区分两种不一样的数据更新方式:
这是咱们如何破坏性地设置一个对象的属性.city:
const obj = {city: 'Berlin', country: 'Germany'};
const key = 'city';
obj[key] = 'Munich';
assert.deepEqual(obj, {city: 'Munich', country: 'Germany'});
复制代码
如下函数非破坏性地改变属性:
function setObjectNonDestructively(obj, key, value) {
const updatedObj = {};
for (const [k, v] of Object.entries(obj)) {
updatedObj[k] = (k === key ? value : v);
}
return updatedObj;
}
复制代码
它的用法以下:
const obj = {city: 'Berlin', country: 'Germany'};
const updatedObj = setObjectNonDestructively(obj, 'city', 'Munich');
assert.deepEqual(updatedObj, {city: 'Munich', country: 'Germany'});
assert.deepEqual(obj, {city: 'Berlin', country: 'Germany'});
复制代码
扩展使setobjectnondestrucative()更简洁:
function setObjectNonDestructively(obj, key, value) {
return {...obj, [key]: value};
}
复制代码
注意:两个版本的setobjectnondestrucative()更新都很浅。
这是咱们如何破坏性地设置一个数组的元素:
const original = ['a', 'b', 'c', 'd', 'e'];
original[2] = 'x';
assert.deepEqual(original, ['a', 'b', 'x', 'd', 'e']);
复制代码
非破坏性更新数组要比非破坏性更新对象复杂得多。
function setArrayNonDestructively(arr, index, value) {
const updatedArr = [];
for (const [i, v] of arr.entries()) {
updatedArr.push(i === index ? value : v);
}
return updatedArr;
}
const arr = ['a', 'b', 'c', 'd', 'e'];
const updatedArr = setArrayNonDestructively(arr, 2, 'x');
assert.deepEqual(updatedArr, ['a', 'b', 'x', 'd', 'e']);
assert.deepEqual(arr, ['a', 'b', 'c', 'd', 'e']);
复制代码
.slice()和spread使setarraynondestructive()更简洁:
function setArrayNonDestructively(arr, index, value) {
return [
...arr.slice(0, index), value, ...arr.slice(index+1)]
}
复制代码
注意:setarraynondestrucsive()的两个版本更新都很浅。
到目前为止,咱们只是粗略地更新了数据。让咱们来解决深层更新。下面的代码演示了如何手动执行此操做。咱们正在更改姓名和雇主。
const original = {name: 'Jane', work: {employer: 'Acme'}};
const updatedOriginal = {
...original,
name: 'John',
work: {
...original.work,
employer: 'Spectre'
},
};
assert.deepEqual(
original, {name: 'Jane', work: {employer: 'Acme'}});
assert.deepEqual(
updatedOriginal, {name: 'John', work: {employer: 'Spectre'}});
复制代码
下面的函数实现了通用的深度更新。
function deepUpdate(original, keys, value) {
if (keys.length === 0) {
return value;
}
const currentKey = keys[0];
if (Array.isArray(original)) {
return original.map(
(v, index) => index === currentKey
? deepUpdate(v, keys.slice(1), value) // (A)
: v); // (B)
} else if (typeof original === 'object' && original !== null) {
return Object.fromEntries(
Object.entries(original).map(
(keyValuePair) => {
const [k,v] = keyValuePair;
if (k === currentKey) {
return [k, deepUpdate(v, keys.slice(1), value)]; // (C)
} else {
return keyValuePair; // (D)
}
}));
} else {
// Primitive value
return original;
}
}
复制代码
若是咱们将值视为正在更新的树的根,那么deepUpdate()只会对单个分支(第A行和第C行)进行深度更改,而对其余全部分支的复制都比较浅(第B行和第D行)。
这就是使用deepUpdate()的样子:
const original = {name: 'Jane', work: {employer: 'Acme'}};
const copy = deepUpdate(original, ['work', 'employer'], 'Spectre');
assert.deepEqual(copy, {name: 'Jane', work: {employer: 'Spectre'}});
assert.deepEqual(original, {name: 'Jane', work: {employer: 'Acme'}});
复制代码
使用非破坏性更新,共享数据就变得不成问题,由于咱们从不改变共享数据。(显然,这只有在各方都这么作的状况下才有效。)
有趣的是,复制数据变得很是简单:
const original = {city: 'Berlin', country: 'Germany'};
const copy = original;
复制代码
只有在必要的状况下,而且咱们正在进行非破坏性的更改时,才会实际复制原件。
咱们能够经过使数据不可变来防止共享数据的突变。接下来,咱们将研究JavaScript如何支持不变性。而后,咱们将讨论不可变数据如何帮助共享可变状态。
JavaScript有三层保护对象:
鉴于咱们但愿咱们的对象是彻底不可变的,咱们在这篇博客文章中只使用Object.freeze()。
Object.freeze(obj)只冻结obj及其属性。它不会冻结这些属性的值-例如:
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
Object.freeze(teacher);
assert.throws(
() => teacher.name = 'Elizabeth Hoover',
/^TypeError: Cannot assign to read only property 'name'/);
teacher.students.push('Lisa');
assert.deepEqual(
teacher, {
name: 'Edna Krabappel',
students: ['Bart', 'Lisa'],
});
复制代码
若是咱们想要深度冻结,咱们须要本身来实施:
function deepFreeze(value) {
if (Array.isArray(value)) {
for (const element of value) {
deepFreeze(element);
}
Object.freeze(value);
} else if (typeof value === 'object' && value !== null) {
for (const v of Object.values(value)) {
deepFreeze(v);
}
Object.freeze(value);
} else {
// Nothing to do: primitive values are already immutable
}
return value;
}
复制代码
从新访问上一节的例子,咱们能够检查deepFreeze()是否真的冻结得很深:
const teacher = {
name: 'Edna Krabappel',
students: ['Bart'],
};
deepFreeze(teacher);
assert.throws(
() => teacher.name = 'Elizabeth Hoover',
/^TypeError: Cannot assign to read only property 'name'/);
assert.throws(
() => teacher.students.push('Lisa'),
/^TypeError: Cannot add property 1, object is not extensible$/);
复制代码
不可变包装器包装可变集合并提供相同的API,但没有破坏性操做。如今,对于同一个集合,咱们有两个接口:一个是可变的,另外一个是不可变的。当咱们须要安全地公开可变的内部数据时,这是很是有用的。
接下来的两个部分将展现映射和数组的包装器。二者都有如下局限性:
类 ImmutableMapWrapper 为 Map 产生包装器:
class ImmutableMapWrapper {
constructor(map) {
this._self = map;
}
}
// Only forward non-destructive methods to the wrapped Map:
for (const methodName of ['get', 'has', 'keys', 'size']) {
ImmutableMapWrapper.prototype[methodName] = function (...args) {
return this._self[methodName](...args);
}
}
复制代码
示例以下:
const map = new Map([[false, 'no'], [true, 'yes']]);
const wrapped = new ImmutableMapWrapper(map);
// Non-destructive operations work as usual:
assert.equal(
wrapped.get(true), 'yes');
assert.equal(
wrapped.has(false), true);
assert.deepEqual(
[...wrapped.keys()], [false, true]);
// Destructive operations are not available:
assert.throws(
() => wrapped.set(false, 'never!'),
/^TypeError: wrapped.set is not a function$/);
assert.throws(
() => wrapped.clear(),
/^TypeError: wrapped.clear is not a function$/);
复制代码
对于数组arr,普通的包装是不够的,由于咱们不只须要拦截方法调用,还须要拦截属性访问,好比arr[1] = true。JavaScript代理使咱们可以作到这一点:
const RE_INDEX_PROP_KEY = /^[0-9]+$/;
const ALLOWED_PROPERTIES = new Set([
'length', 'constructor', 'slice', 'concat']);
function wrapArrayImmutably(arr) {
const handler = {
get(target, propKey, receiver) {
// We assume that propKey is a string (not a symbol)
if (RE_INDEX_PROP_KEY.test(propKey) // simplified check!
|| ALLOWED_PROPERTIES.has(propKey)) {
return Reflect.get(target, propKey, receiver);
}
throw new TypeError(`Property "${propKey}" can’t be accessed`);
},
set(target, propKey, value, receiver) {
throw new TypeError('Setting is not allowed');
},
deleteProperty(target, propKey) {
throw new TypeError('Deleting is not allowed');
},
};
return new Proxy(arr, handler);
}
复制代码
让咱们包装一个数组:
const arr = ['a', 'b', 'c'];
const wrapped = wrapArrayImmutably(arr);
// Non-destructive operations are allowed:
assert.deepEqual(
wrapped.slice(1), ['b', 'c']);
assert.equal(
wrapped[1], 'b');
// Destructive operations are not allowed:
assert.throws(
() => wrapped[1] = 'x',
/^TypeError: Setting is not allowed$/);
assert.throws(
() => wrapped.shift(),
/^TypeError: Property "shift" can’t be accessed$/);
复制代码
若是数据是不可变的,那么能够毫无风险地共享它。特别是,没有必要采起防护性的复制。
非破坏性的更新补充了不可变数据,并使其与可变数据同样多用途,但没有相关的风险。
JavaScript有几个库可使用,它们支持具备非破坏性更新的不可变数据。两个流行的是:
在其存储库中,Immutable.js被描述为:
用于JavaScript的不可变的持久数据收集,提升了效率和简单性。
js提供了不可变的数据结构,如:
在下面的例子中,咱们使用一个不可变的映射:
import {Map} from 'immutable/dist/immutable.es.js';
const map0 = Map([
[false, 'no'],
[true, 'yes'],
]);
const map1 = map0.set(true, 'maybe'); // (A)
assert.ok(map1 !== map0); // (B)
assert.equal(map1.equals(map0), false);
const map2 = map1.set(true, 'yes'); // (C)
assert.ok(map2 !== map1);
assert.ok(map2 !== map0);
assert.equal(map2.equals(map0), true); // (D)
复制代码
解释:
在其分支中,Immer库被描述为:
经过改变当前状态来建立下一个不可变状态。
Immer有助于无破坏性地更新(多是嵌套的)普通对象和数组。也就是说,不涉及任何特殊的数据结构。
使用Immer是这样的:
import {produce} from 'immer/dist/immer.module.js';
const people = [
{name: 'Jane', work: {employer: 'Acme'}},
];
const modifiedPeople = produce(people, (draft) => {
draft[0].work.employer = 'Cyberdyne';
draft.push({name: 'John', work: {employer: 'Spectre'}});
});
assert.deepEqual(modifiedPeople, [
{name: 'Jane', work: {employer: 'Cyberdyne'}},
{name: 'John', work: {employer: 'Spectre'}},
]);
assert.deepEqual(people, [
{name: 'Jane', work: {employer: 'Acme'}},
]);
复制代码
原始数据存储在people.produce()为咱们提供了一个可变的草稿。咱们假设这个变量是people,并使用一般用于进行破坏性更改的操做。Immer拦截了这些行动。而不是突变草稿,它无损地改变people。结果被修改过的people引用。生成modifiedPeople,它是不可改变的。
Ron Korvig提醒我使用静态工厂方法,而不是重载构造函数来进行JavaScript的深度复制。
结构赋值(也是说扩展赋值): 《JavaScript for impatient programmers》 “Spreading into object literals” ,“Spreading into Array literals” exploringjs.com/impatient-j…
属性: 《Speaking JavaScript》“Property Attributes and Property Descriptors” “Protecting Objects” speakingjs.com/es5/ch17.ht…
原型链: 《JavaScript for impatient programmers》“Prototype chains” 《Speaking JavaScript》 “Properties: Definition Versus Assignment”
《Speaking JavaScript》“Metaprogramming with proxies”