尽管JavaScript允许我们更改对象,但我们可能选择不允许自己(和其他程序员)这样做。在当今的JavaScript世界中,最好的例子之一就是在React应用程序中设置状态。如果我们更改当前状态而不是更改当前状态的新副本,则可能会遇到难以诊断的问题。
在这篇文章中,我们滚动了我们自己的不可变代理函数,以防止对象突变!
什么是对象突变?
作为快速回顾,对象突变是指我们更改对象或数组的属性时。这与重新分配完全不同,在重新分配中,我们完全指向了不同的对象引用。以下是突变与重新分配的几个示例:
// Mutation
const person = { name: "Bo" };
person.name = "Jack";
// Reassignment
let pet = { name: "Daffodil", type: "dog" };
pet = { name: "Whiskers", type: "cat" };
我们必须记住,这也适用于数组:
// Mutation
const people = ["Jack", "Jill", "Bob", "Jane"];
people[1] = "Beverly";
// Reassignment
let pets = ["Daffodil", "Whiskers", "Ladybird"];
pets = ["Mousse", "Biscuit"];
对象突变的意外后果的一个例子
既然我们已经了解了什么是突变,那么突变如何产生意想不到的后果?让我们看下面的例子。
const person = { name: "Bo" };
const otherPerson = person;
otherPerson.name = "Finn";
console.log(person);
// { name: "Finn" }
kes,没错!双方person
并otherPerson
正在引用同一个对象,因此,如果我们发生变异name
的otherPerson
,这种变化将当我们访问体现person
。
与其让我们自己(以及我们项目中的其他开发人员)突变这样的对象,不如我们抛出一个错误该怎么办?那就是我们的不可变代理解决方案出现的地方。
我们的不可变代理解决方案
JavaScript Proxy
对象是我们可以使用的一些元编程。它允许我们使用自定义功能包装该对象,以实现该对象上的getter和setter之类的功能。
对于我们的不可变代理,让我们创建一个函数,该函数接受一个对象并返回该对象的新代理。当我们尝试get
对该对象设置属性时,我们会检查该属性是否是对象本身。如果是这样,那么我们以递归的方式返回包裹在不可变代理中的该属性。否则,我们只返回该属性。
当我们尝试set
代理对象的值时,简单地抛出一个错误,让用户知道他们不能set
在该对象上设置属性。
这是我们不可变的代理功能:
const person = {
name: "Bo",
animals: [{ type: "dog", name: "Daffodil" }],
};
const immutable = (obj) =>
new Proxy(obj, {
get(target, prop) {
return typeof target[prop] === "object"
? immutable(target[prop])
: target[prop];
},
set() {
throw new Error("Immutable!");
},
});
const immutablePerson = immutable(person);
const immutableDog = immutablePerson.animals[0];
immutableDog.type = "cat";
// Error: Immutable!
有了它:我们无法对不可变对象的属性进行突变!
我应该在生产中使用它吗
不,可能不会。这种练习在学术上非常棒,但是有各种各样的棒,健壮且经过测试的解决方案可以完成相同的工作(例如ImmutableJS和ImmerJS)。如果您希望在应用程序中包含不可变的数据结构,我建议您查看这些很棒的库!