关于 JavaScript 对象不变性,你了解吗?

ops/2024/11/14 13:18:51/

1. 基本概念

在 JavaScript 语言中,不变性(Immutability)是一个重要的概念。它指的是对象一旦创建后其状态就不能改变。在函数式编程中,不变性是实现纯函数的基础,因为它可以确保函数的输出只依赖于输入参数,而不受外部状态的影响。对于 JavaScript 这样的多范式语言来说,理解并应用不变性可以带来代码的可预测性和易于测试等好处。

对象不变性在任何编程语言中都是一个重要的概念。它会限制对象修改并防止不需要的更改。简而言之,对象的不变性就是将它的状态变为只读的,下面就来看看在 JavaScript 中的对象不变性。 ​

在 JavaScript 中,一个对象可以有一个或多个属性。每个属性都是一个键值对,下面是一个对象:

const user = {name: "张三",age: 24,
};

这里使用 const 关键字定义了一个对象,它具有两个属性。默认情况下,对象是可变的,也就是说,我们可以给对象添加新属性,修改现有属性或者删除已有属性。而在某些情况下,我们可能希望对象是不可变的,即不能添加新属性,也不能修改和删除已有属性。

2. 冻结对象 Object.freeze()

使用Object.freeze()方法可以使一个对象变为不可变的。这意味着不能添加新属性,也不能修改现有属性的值,而且该对象的原型也是不可变的。

const obj = Object.freeze({ key: "value" });
obj.key = "new value"; // 尝试修改将失败,obj.key 仍为 'value'

顾名思义,freeze() 就是用来冻结对象的。只需要将不想被更改的对象传递给它,就会返回该对象的不可变版本:

const user = {name: "张三",age: 24,
};const freezeUser = Object.freeze(user);
freezeUser.age = 18;
console.log(freezeUser.age); // 24

这时,新的对象就不可变了,相当于被冻结了。 ​

在 JavaScript 了,提供了一个 Object.isFrozen(),它可以用来判断一个对象是否被冻结:

Object.isFrozen(user); // false
Object.isFrozen(freezeUser); // true

需要注意的是, Object.freeze() 方法不会影响嵌套对象,对于嵌套对象,冻结后仍然是可以操作的:

const user = {name: "张三",age: 24,article: {title: "learn javascript",number: 1234,},
};const freezeUser = Object.freeze(user);
freezeUser.age = 18;
freezeUser.article.number = 666;
console.log(freezeUser.age); // 24
console.log(freezeUser.article.number); // 666

可以看到,使用 Object.freeze() 方法冻结的对象,age 是不可以更改的,但是嵌套对象的 number 属性还是可以修改的。如果需要冻结嵌套对象使其不可变,就需要使用循递归来逐级冻结,下面是一个简单的递归冻结实现:

const deepFreeze = (obj) => {Object.keys(obj).forEach((prop) => {if (typeof obj[prop] === "object") {deepFreeze(obj[prop]);}});return Object.freeze(obj);
};deepFreeze(user);

Object.freeze()方法除了可以用来冻结对象以外,还可以用于冻结数组,使其不可变:

const number = [1, 2, 3, 4, 5];
const freezeNumber = Object.freeze(number);
freezeNumber.push(6);

此时就会报错了:

VM210:3 Uncaught TypeError: Cannot add property 5, object is not extensible

3. 密封对象 Object.seal()

Object.seal() 顾名思义就是密封对象,它是另一种使对象不可变的方法。相对于 freeze(),Object.seal() 方法仅保护对象不能添加和删除属性,它允许更新现有的属性。

const user = {name: "张三",age: 24,
};const sealUser = Object.seal(user);
sealUser.age = 18;
delete sealUser.name;
console.log(sealUser); // {name: '张三', age: 18}

可以看到,我们识图删除对象中的 name 属性,删除失败;而修改已有的 age 属性,修改成功。 ​

Object.seal()方法和 Object.freeze()一样,对于嵌套的对象,是无法实现不可变的,如果想要实现,同样要使用递归来一层层来密封对象。 ​

JavaScript 同样提供了一个 Object.isSealed() 方法来确认对象的密封状态:

Object.isSealed(user); // false
Object.isSealed(sealUser); // true

4. const 关键字

你是否会认为,使用 const 关键字也能达到相同的结果呢?实际上,他们是不一样的,当我们使用 const 关键字来创建对象时,它会阻止我们重新分配值,但是我们可以更新、添加、删除已有对象的属性:

const user = {name: "张三",age: 24,
};delete user.age;
user.height = 180;
user.name = "hello";
console.log(user); // {name: 'hello', height: 180}

而如果我们给 user 重新赋值,那么就会报错了:

Uncaught TypeError: Assignment to constant variable.

因此,const 关键字仅仅是提供了赋值的不变性,而不会提供值的不变性(对于对象来说)。

5. 使用不可变库

如 Immutable.js 提供了一套完整的不可变数据结构,包括 List、Map、Set 等。这些数据结构在被“修改”时实际上会返回一个新的实例,而不是直接修改原对象。

const { Map } = require("immutable");
let map1 = Map({ a: 1, b: 2 });
let map2 = map1.set("b", 50); // 返回新的Map实例
console.log(map1.get("b")); // 输出 2
console.log(map2.get("b")); // 输出 50

6. 浅克隆与深克隆

通过创建对象的副本来进行“修改”,这种方式适用于简单的对象结构。对于复杂的嵌套对象,则需要进行深克隆。

function shallowClone(obj) {return { ...obj };
}function deepClone(obj) {return JSON.parse(JSON.stringify(obj));
}

7. 总结

本文简单介绍了几种可以用于使 JavaScript 不可变的方法。简而言之,Object.seal()方法会阻止更新、删除和向对象添加新属性,Object.seal()只会阻止添加和删除属性。、

除此之外,JavaScript 还提供了一个 Object.preventExtensions()方法,该方法可以让一个对象变的不可扩展,也就是永远不能再添加新的属性。

const user = {name: "张三",age: 24,
};const newUser = Object.preventExtensions(user);
newUser.height = 180;
console.log(newUser); //  {name: '张三', age: 24}

不变性的优点

  1. 减少副作用:不可变对象不会因为程序其他部分的修改而发生改变,这减少了潜在的副作用。

  2. 提高并发安全性:在多线程环境中,不可变对象不需要额外的同步机制来保证数据的一致性。

  3. 简化调试:由于对象的状态不会改变,因此更容易追踪到问题发生的源头。

  4. 增强缓存能力:如果一个对象是不可变的,那么它的哈希值可以在创建时计算一次,之后就不再需要重新计算。

  5. 优化性能:某些情况下,使用不可变数据结构可以避免不必要的复制操作,从而提高性能。


http://www.ppmy.cn/ops/133281.html

相关文章

家里电脑ip地址怎么设置?详细指导

在家庭网络环境中,正确设置电脑的IP地址是确保设备能够顺利接入互联网以及实现局域网内设备间通信的基础步骤。对于大多数家庭用户而言,IP地址的设置通常是通过路由器自动分配(动态IP)来完成的,这得益于DHCP&#xff0…

RabbitMQ 与 PHP Swoole 实现

RabbitMQ 与 PHP Swoole 的结合实现 一、概述 RabbitMQ 是一个开源的消息队列中间件,允许通过异步消息传递来解耦应用程序的各个部分。Swoole 是一个高性能的 PHP 扩展,支持异步编程和协程,适用于构建高并发的网络服务。将 RabbitMQ 与 Swo…

使用R语言survminer获取生存分析高风险和低风险的最佳截断值cut-off

使用R语言进行Cox比例风险模型分析和最佳截断值寻找 引言 在生存分析中,Cox比例风险模型是一种常用的统计方法,用于评估多个变量对生存时间的影响。在临床研究中,我们经常需要根据某些连续变量的预测值来对患者进行分组,以便更好…

XXL-TOOL v1.3.1 发布 | Java工具类库(Excel、Pipeline、Fiber…)

Release Notes 1、【强化】已有工具能力完善,包括:StringTool、GsonTool 等; 2、【新增】新增多个工具类模块,包括:FreemarkerTool、CookieTool、PageModel、CacheTool、StreamTool 等; 3、【完善】工具类…

OceanBase JDBC (Java数据库连接)的概念、分类与兼容性

本章将介绍 OceanBase JDBC的 概念与分类,已帮助使用 JDBC 的用户及技术人员更好的 了解JDBC,以及 OceanBase JDBC在与 MySQL 及 Oracle 兼容性方面的相关能力。 一、JDBC 基础 1.1 JDBC 的概念 JDBC 一般指 Java 数据库连接。Java 数据库连接&#xf…

ES5 和 ES6 数组的操作方法

在 JavaScript 中,数组的操作方法非常丰富,包括 ES5 和 ES6 中引入的各种方法。以下是对这些数组方法的详细介绍,分为 ES5 和 ES6。 目录 一、ES5 数组方法 1. 创建数组 2. 数组增加元素 3. 数组删除元素 4. 查找元素 5. 遍历数组 6.…

23. 管理架构债务

文章目录 第23章 管理架构债务23.1 确定你是否存在架构债务问题23.2 发现热点23.3 示例识别热点量化架构债务 23.4 自动化23.5 小结23.6 扩展阅读23.7 问题讨论 第23章 管理架构债务 与 Yuanfang Cai 合作 有些债务在你欠下的时候是有趣的,但当你着手偿还它们的时候…

XSS过滤器Filter实现

需求:xxs攻击过滤 测试发现代码转换成图片格式后,可以通过上传文件接口存在服务器上,再次打开时候会执行代码 项目背景:前端采用formajax提交数据,后端采用SpringMVC框架,RequestMapping注解的方法接收前…