blog
blog copied to clipboard
Immutable JavaScript
immutable 不可变的 immutability 不变性 mutating data 变异数据
编写 immutable 的 JS 代码,是个比较好的实践。已经有库 Immutable.js 可以帮我们实现这个特性了。这篇博客来聊下,在 ES6+ 里如何使用 Immutable 特性。
问题
为什么 immutability 很重要?因为,变异数据会让代码不易阅读,也容易引入bug。
对于基本类型(比如 number 和 string),书写 immutable 代码是非常简单的,因为基本类型自己不会变。指向基本类型的变量一直指向实际的值,如果你把它赋值给了另一个变量,那另一个变量是得到了该值的一个新拷贝。
对象(和数组)就不一样了,因为它们传的是引用。在这种情况下,新变量和原始变量是指向的同一个对象。不论你修改新变量还是原始变量,都会 mutate 该对象。先来感受下。
const person = {
name: 'anjia',
age: 18
};
const newPerson = person;
newPerson.age = 30;
console.log(newPerson === person); // true
console.log(newPerson, person); // age 都是30了
这就是问题的所在。当你修改了 newPerson
,我们竟然也自动修改了旧对象 person
。在大多数情况下,这是不希望的行为,也是不好的编码实践。
那下面,我们看看如何解决这个问题。
Immutable
对象
不传引用,我们可以创建一个全新的对象。
const person = {
name: 'anjia',
age: 18
};
const newPerson = Object.assign({}, person, {
age: 30
});
console.log(newPerson === person); // false
console.log(newPerson, person); // newPerson 是30岁,person 还是18岁
Object.assign
是个 ES6 的新特性,它把所有对象合并到第一个。
这样,就保持了旧变量 person
的独立性和完整性,我们把它称之为 immutable。
在 ES6 里,我们有更简洁的写法。可以用 object spread 操作符 ...
,这样:
const person = {
name: 'anjia',
age: 18
};
const newPerson = {
...person,
age: 30
};
console.log(newPerson === person); // false
console.log(newPerson, person); // newPerson 是30岁,person 还是18岁。同上
那么,如何删除一个属性呢?当然,不能用 delete,因为它会 mutate 到原始值。我们可以这样:
const person = {
name: 'anjia',
gender: 'female',
age: 18
};
const property = 'age'; // 删除age属性
const newPerson = Object.keys(person).reduce((obj, key) => {
if(key !== property){
return {...obj, [key]: person[key]}
}
return obj
}, {});
console.log(newPerson === person); // false
console.log(newPerson); // 只有 name 和 gender
console.log(person); // 有 name, gender, age
呃,好吧,删除的话我们需要自己写整个逻辑代码。你可以把它封装成一个公共方法。
数组
下面的例子,以 mutating 的方式向数组里添加新项。
const names = ['an', 'jia'];
const newNames = names;
newNames.push('zora');
console.log(newNames === names); // true
console.log(newNames, names); // 都是 ["an", "jia", "zora"]
解决办法,思路同上。
const names = ['an', 'jia'];
const newNames = [...names, 'zora'];
console.log(newNames === names); // false
console.log(newNames); // ["an", "jia", "zora"]
console.log(names); // ["an", "jia"]
这样我们就创建了一个新数组newNames
,而且还能让老数组names
保持自身的独立性和完整性。
关于数组的,还有一些方法也能非常方便地生成新数组,而不影响老值。比如 map
filter
等。详见之前的一篇博客数组遍历。看代码(点进去链接看看),有没有觉得写 immutable 的代码更方便了。再配合着箭头函数,就更简洁了。它们每次都返回一个全新的数组。
当然,有一个例外就是sort()
const names = ['zora', 'anjia', 'an', 'jia'];
const newNames = names.sort();
console.log(newNames === names); // true
console.log(newNames); // 都输出 ["an", "anjia", "jia", "zora"]
console.log(names);
有没有解决办法呢?有,如下:
const names = ['zora', 'anjia', 'an', 'jia'];
const newNames = names.slice().sort(); // 利用 slice(),虽然有点 hacky
console.log(newNames === names); // false
console.log(newNames); // ["an", "anjia", "jia", "zora"]
console.log(names); // ["zora", "anjia", "an", "jia"] 原值
如上,现代JS,可以让我们轻松实现 immutability。良好的编码,可以避免让JS变得不可预测。
性能
每次都创建新对象会耗费时间和内存哦?嗯~是的,它会带来一点开销。但是,与它带来的优势来比,这点缺点可以忽略了。
- JS 里有个更复杂的操作是:追踪对象是否改变。
Object.observe(object, callback)
是非常重的,且开销也很大。如果是 immutable,那可以用oldObject === newObject 来判断
,会很省内存哦 - 第二个好处是:代码质量。确保声明是 immutable,会迫使自己更好的思考自己的程序结构,然后写出更健壮的代码。
REF:https://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/