不可变对象
不变性
不可变对象(Immutable Object)指那些创建之后无法再被修改的对象,与之相对的可变对象const
与不可变性之间的区别。const
声明的变量名会绑定到某个内存空间而不可以被二次分配,其并没有创建真正的不可变对象。你可以不修改变量的指向,但是可以修改该对象的某个属性值,因此const
创建的还是可变对象。Object.freeze()
函数,其可以创建一层不可变对象:
const a = Object.freeze({
foo: "Hello",
bar: "world",
baz: "!",
});
a.foo = "Goodbye";
// Error: Cannot assign to read only property 'foo' of object Object
不过这种对象并不是彻底的不可变数据,譬如如下的对象就是可变的:
const a = Object.freeze({
foo: { greeting: "Hello" },
bar: "world",
baz: "!",
});
a.foo.greeting = "Goodbye";
console.log(`${a.foo.greeting}, ${a.bar}${a.baz}`);
如上所见,顶层的基础类型属性是不可以改变的,不过如果对象类型的属性,譬如数组等,仍然是可以变化的。在很多函数式编程语言中,会提供特殊的不可变数据结构for
while
do
repeat
这些关键字,我们在函数式编程中可以使用递归来实现原本的循环需求
// 简单的循环构造
const acc = 0;
for (const i = 1; i <= 10; ++i) acc += i;
console.log(acc); // prints 55
// 递归方式实现
function sumRange(start, end, acc) {
if (start > end) return acc;
return sumRange(start + 1, end, acc + start);
}
console.log(sumRange(1, 10, 0)); // prints 55
注意在递归中,与变量
sumRange start end acc =
if start > end then
acc
else
sumRange (start + 1) end (acc + start)
其每一次的迭代记录如下
sumRange 1 10 0 = -- sumRange (1 + 1) 10 (0 + 1)
sumRange 2 10 1 = -- sumRange (2 + 1) 10 (1 + 2)
sumRange 3 10 3 = -- sumRange (3 + 1) 10 (3 + 3)
sumRange 4 10 6 = -- sumRange (4 + 1) 10 (6 + 4)
sumRange 5 10 10 = -- sumRange (5 + 1) 10 (10 + 5)
sumRange 6 10 15 = -- sumRange (6 + 1) 10 (15 + 6)
sumRange 7 10 21 = -- sumRange (7 + 1) 10 (21 + 7)
sumRange 8 10 28 = -- sumRange (8 + 1) 10 (28 + 8)
sumRange 9 10 36 = -- sumRange (9 + 1) 10 (36 + 9)
sumRange 10 10 45 = -- sumRange (10 + 1) 10 (45 + 10)
sumRange 11 10 55 = -- 11 > 10 => 55
55
在实际编程中,多个不可变对象之间可能会共享部分对象:

函数操作库
Immutable.js
Immutable 对象一旦被创建之后即不可再更改,这样可以使得应用开发工作变得简化,不再需要大量的保护性拷贝,使用简单的逻辑控制即可以保证内存控制与变化检测。
a={a:1}; b=a; b.a=10;
你发现a.a
也变成
const defaultConfig = {
/* 默认值 */
};
const config = $.extend({}, defaultConfig, initConfig); // jQuery用法。initConfig是自定义值
const config = $.extend(true, {}, defaultConfig, initConfig); // 如果对象是多层的,就用到deep-copy了
而
const stateV1 = Immutable.fromJS({
users: [{ name: "Foo" }, { name: "Bar" }],
});
const stateV2 = stateV1.updateIn(["users", 1], function () {
return Immutable.fromJS({
name: "Barbar",
});
});
stateV1 === stateV2; // false
stateV1.getIn(["users", 0]) === stateV2.getIn(["users", 0]); // true
stateV1.getIn(["users", 1]) === stateV2.getIn(["users", 1]); // false
如上,我们可以使用
Immer

import produce from "immer";
const baseState = [
{
todo: "Learn typescript",
done: true,
},
{
todo: "Try immer",
done: false,
},
];
const nextState = produce(baseState, (draftState) => {
draftState.push({ todo: "Tweet about it" });
draftState[1].done = true;
});
import produce from "immer";
const byId = produce(
(draft, action) => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach((product) => {
draft[product.id] = product;
});
return;
}
},
// 传入默认初始状态
{
1: { id: 1, name: "product-1" },
}
);
或者直接在
onBirthDayClick2 = () => {
this.setState(
produce((draft) => {
draft.user.age += 1;
})
);
};