這篇博文回答瞭如下問題: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」