JavaScript的不可变性
不可变性
不可变性是指一旦创建了一个值,它就不能被修改。在JavaScript中,原始类型(如字符串、数字和布尔值)是不可变的,而对象和数组则是可变的。
为什么
更改数据会产生难以读取且容易出错的代码。
首先,为什么不变性很重要? 理解和应用不可变性的重要性有以下几个方面:
避免副作用
可变性是引发许多编程问题的根源之一。当数据可以被修改时,很难追踪和理解程序中发生的副作用。通过使用不可变数据,可以降低代码的复杂性和错误的可能性。
提高性能
JavaScript中的不可变性可以使引擎进行更好的优化。由于不可变值在内存中不变,可以更轻松地进行缓存和重用,从而提高代码的执行效率。
支持时间旅行调试
不可变性使得跟踪和调试代码变得更加容易。通过保留历史版本的不可变数据,我们可以在代码中自由地回溯和比较不同时间点的状态,更轻松地定位和修复错误。
像Redux和NGRX类库, 其受欢迎程度激增。他们共同的要求是一个不可变的状态。
应用程序的状态是按顺序应用于初始状态的操作列表的结果。
应用程序的每个状态都是不可更改的, 新操作使用现有状态计算新操作。
这有助于我们通过可变操作避免意外状态更改.
函数式编程
函数式编程鼓励使用不可变数据和纯函数来构建应用程序。通过将不可变性视为核心原则,函数式编程能够降低代码复杂性、增加可测试性,并提供更好的并发性。
总结: 编写不可变的 Javascript 代码是一种好的做法. 不可变性编程与相关编程思想的结合,可以产生更具表达力、可维护性和并发性的代码。通过遵循不可变性原则,我们可以减少副作用、降低代码复杂性,并在多线程或并行环境中更轻松地处理数据共享和状态管理。
应用示例
数组
数组有几个可变操作, 这里回顾其用法与了解新方法
- push pop
- shift unshift
- splice reverse sort
push (从尾推进)
// mutable
const fruits = ['orange', 'apple', 'lemon'];
fruits.push('banana');
console.log(fruits)
// immutable
const fruits = ['orange', 'apple', 'lemon']
const newFruits = [...fruits, 'banana']
console.log(newFruits)
pop (从尾拿出)
// mutable
const fruits = ['orange', 'apple', 'lemon', 'banana'];
const lastFruit = fruits.pop();
console.log(fruits)
// immutable
const fruits = ['orange', 'apple', 'lemon', 'banana'];
const lastFruit = fruits[fruits.length - 1]; // 'banana'
const newFruits = fruits.slice(0, fruits.length - 1);
console.log(newFruits)
unshift (从头推进)
// mutable
const fruits = ['orange', 'apple', 'lemon'];
fruits.unshift('banana');
console.log(fruits)
// immutable
const fruits = ['orange', 'apple', 'lemon'];
const newFruits = ['banana', ...fruits];
console.log(newFruits)
shift (从头拿出)
// mutable
const fruits = ['orange', 'apple', 'lemon', 'banana'];
const firstFruit = fruits.shift();
console.log(firstFruit)
// immutable
const fruits = ['orange', 'apple', 'lemon', 'banana'];
const firstFruit = fruits[0];
const newFruits = fruits.slice(1);
console.log(newFruits)
splice slice (删除项目)
常用`splice`, 不可变用 `slice + spread`
// mutable
// 位置1,删除2个元素, 该位置增加 'strawberry'
const fruits = ['orange', 'apple', 'lemon', 'banana'];
fruits.splice(1, 2, 'strawberry');
console.log(fruits)
// immutable - slice spread
const fruits = ['orange', 'apple', 'lemon', 'banana'];
const newFruits = [...fruits.slice(0, 1), 'strawberry', ...fruits.slice(3)];
console.log(newFruits)
sort reverse (排序与反转)
// mutable
const fruits = ['orange', 'apple', 'lemon', 'banana'];
fruits.sort();
console.log(fruits)
fruits.reverse();
console.log(fruits)
// immutable
const fruits = ['orange', 'apple', 'lemon', 'banana'];
const sorted = [...fruits].sort();
console.log(sorted)
const inverted = [...fruits].reverse();
console.log(inverted)
const sortedAndInverted = [...sorted].reverse();
console.log(sortedAndInverted)
对象
修改/添加属性
const state = {
selected: 'apple',
quantity: 13,
fruits: ['orange', 'apple', 'lemon', 'banana']
};
// mutable
state.selected = 'orange';
state.quantity = 5;
state.origin = 'imported from Spain';
/*
state = {
selected: 'orange',
quantity: 5,
fruits: ['orange', 'apple', 'lemon', 'banana'],
origin: 'imported from Spain'
}
*/
console.log(state)
// immutable
const state = {
selected: 'apple',
quantity: 13,
fruits: ['orange', 'apple', 'lemon', 'banana']
};
const newState = {
...state,
selected: 'orange',
quantity: 5,
origin: 'imported from Spain'
};
/*
newState = {
fruits: ['orange', 'apple', 'lemon', 'banana'],
selected: 'orange',
quantity: 5,
origin: 'imported from Spain'
}
*/
console.log(newState)
删除属性
// mutable 只需调用 delete
const state = {
selected: 'apple',
quantity: 13,
fruits: ['orange', 'apple', 'lemon', 'banana']
};
delete state.quantity;
/*
state = {
selected: 'apple',
fruits: ['orange', 'apple', 'lemon', 'banana']
}
*/
console.log(state)
const state = {
selected: 'apple',
quantity: 13,
fruits: ['orange', 'apple', 'lemon', 'banana']
};
// immutable (rest + spread) 提取属性
const { quantity, ...newState } = state;
/*
quantity = 13
newState = {
selected: 'apple',
fruits: ['orange', 'apple', 'lemon', 'banana']
}
*/
console.log(newState)
复杂结构
当具有嵌套数组或对象时.
const state = {
selected: 4,
gang: [
'Mike',
'Dustin',
'Lucas',
'Will',
'Jane'
]
};
// mutable
// 对复杂结构执行操作会使结构的较浅(第一级)复制.
// 在这里,它只复制对数组的引用,而不是实际数组。
const newState = { ...state };
newState.selected = 11;
newState.gang.push('Max');
newState.gang.push('Suzie');
/*
state = {
selected: 4,
gang: [
'Mike',
'Dustin',
'Lucas',
'Will',
'Jane'
'Max',
'Suzie'
]
}
newState = {
selected: 11,
gang: [
'Mike',
'Dustin',
'Lucas',
'Will',
'Jane'
'Max',
'Suzie'
]
}
*/
console.log(state.gang === newState.gang) //true
// immutable
// 向数组添加新元素对 和 . 都有影响。要解决这个问题,我们需要单独分布数组。
const state = {
selected: 4,
gang: [
{ id: 1, name: 'Mike' },
{ id: 2, name: 'Dustin' },
{ id: 3, name: 'Lucas' },
{ id: 4, name: 'Will' },
{ id: 11, name: 'Jane' }
]
}
const newState = {
selected: 11,
gang: [...state.gang]
}
newState.gang[4].name = 'Eleven';
/*
state = {
selected: 4,
gang: [
{ id: 1, name: 'Mike' },
{ id: 2, name: 'Dustin' },
{ id: 3, name: 'Lucas' },
{ id: 4, name: 'Will' },
{ id: 11, name: 'Eleven' }
]
}
newState = {
selected: 11,
gang: [
{ id: 1, name: 'Mike' },
{ id: 2, name: 'Dustin' },
{ id: 3, name: 'Lucas' },
{ id: 4, name: 'Will' },
{ id: 11, name: 'Eleven' }
]
}
*/
console.log(state.gang === newState.gang) //false
关于性能的一句话
性能如何,创建新对象的时间和内存不费时吗?
嗯,真的,它附带多一点开销。但与优势相比,这种劣势非常小。
Javascript 中更复杂的操作之一是跟踪对象是否更改。
这样的解决方案相当沉重。
但是,如果保持状态不变,您可以只依靠检查状态是否更改,这就小了许多CPU要求
第二大优势是代码质量。
确保状态不变会迫使您更好地考虑应用程序结构。它鼓励以更实用的方式编程,使代码易于遵循,并减少了讨厌错误的可能性。
参考:
https://dev.to/glebec/four-ways-to-immutability-in-javascript-3b3l