JS对象复制(深拷贝、浅拷贝)
如何在 JS 中复制对象
在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法。
在开始之前,有一些基础知识值得一提:Javascript 中的对象只是对内存地址的引用。创建引用的副本会导致2个引用指向同一个的内存地址。
var foo = {
a: "abc"
}
console.log(foo.a); // abc
var bar = foo;
console.log(bar.a); // abc
foo.a = "yo foo";
console.log(foo.a); // yo foo
console.log(bar.a); // yo foo
bar.a = "whatup bar?";
console.log(foo.a); // whatup bar?
console.log(bar.a); // whatup bar?
如上所示,对 foo 和 bar 两个对象中的任一个做修改,另一个都会发生相应变化。因此,在 JS 中复制对象要格外小心。
浅拷贝
如果对象比较简单、只具有值类型的属性,可以使用扩展运算符(spread)或 Object.assign(...)
var obj = { foo: "foo", bar: "bar" };
var copy = { ...obj }; // Object {foo: "foo", bar: "bar"}
var obj = { foo: "foo", bar: "bar" };
var copy = Object.assign({}, obj); // Object {foo: "foo", bar: "bar"}
注:上述两种方法都可以将属性值从多个源对象复制到目标对象:
var obj1 = { foo: "foo" };
var obj2 = { bar: "bar" };
var copySpread = { ...obj1, ...obj2 }; // Object {foo: "foo", bar: "bar"}
var copyAssign = Object.assign({}, obj1, obj2); // Object {foo: "foo", bar: "bar"}
不过,如果对象的属性值也是一个对象,那么用上述方法拷贝就会有问题了:那样做只是创建了一个对象属性值引用的副本(但共享的还是一个内存),和本文开篇第一段代码示例中 var bar = foo; 的效果一样:
var foo = { a: 0 , b: { c: 0 } };
var copy = { ...foo };
copy.a = 1;
copy.b.c = 2;
// 修改副本 copy 中的属性 c,原对象 foo 中的属性也跟着变化
console.dir(foo); // { a: 0, b: { c: 2 } }
console.dir(copy); // { a: 1, b: { c: 2 } }
深拷贝(有注意事项)
深拷贝对象,一种解决方案是将对象序列化为字符串,然后再将其反序列化:
var obj = { a: 0, b: { c: 0 } };
var copy = JSON.parse(JSON.stringify(obj));
然而,此方法仅在原对象包含可序列化值类型且没有任何循环引用时才有效。不可序列化值类型的一个例子是 Date 对象 - JSON.parse 只能将其解析为字符串而无法解析回其原始的 Date 对象 :(。
复杂对象的深拷贝
对于更复杂的对象,可以使用新的由 HTML5 规范定义的 "结构化克隆(structured clone)" 算法。不过,虽然这种方法支持的内容类型多于 JSON.parse,但在实际运用中,仍局限于某些内置类型,如:Date,RegExp,Map,Set,Blob,FileList,ImageData,稀疏和类型化数组(typed Array)。它还保留了克隆数据中的引用,支持循环和递归结构。
目前,没有直接的方法来调用结构化克隆算法,但是有一些较新的浏览器功能使用这种算法。因此,有一些可用的深拷贝对象的方法。
通过 MessageChannels:利用通信功能使用的序列化算法。由于此功能是基于事件的,因此生成的克隆也是异步操作。
class StructuredCloner {
constructor() {
this.pendingClones_ = new Map();
this.nextKey_ = 0;
const channel = new MessageChannel();
this.inPort_ = channel.port1;
this.outPort_ = channel.port2;
this.outPort_.onmessage = ({data: {key, value}}) => {
const resolve = this.pendingClones_.get(key);
resolve(value);
this.pendingClones_.delete(key);
};
this.outPort_.start();
}
cloneAsync(value) {
return new Promise(resolve => {
const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}
const structuredCloneAsync = window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
const main = async () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
main();
通过 history API:history.pushState() 和 history.replaceState() 创建里面第一个参数 obj 的结构化克隆!注意,虽然此方法是同步的,但操纵浏览器的历史记录并不是一项快速的操作,也需要一定的过程和时间,反复调用此方法可能会导致浏览器无响应。
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
通过 notification API:在创建新通知时,构造函数会创建其关联数据的结构化克隆。请注意,它还会尝试向用户显示浏览器通知,但除非应用程序已申请显示通知的权限,否则将以静默方式失败。在授予权限的情况下,通知会立即关闭。
const structuredClone = obj => {
const n = new Notification("", {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
NODE.JS 中的深拷贝
从 8.0.0 版开始,Node.js 提供了与结构化克隆兼容的 serialization API。请注意,在撰写本文时,此 API 已标记为实验性:
const v8 = require('v8');
const buf = v8.serialize({a: 'foo', b: new Date()});
const cloned = v8.deserialize(buf);
cloned.b.getMonth();
对于低于 8.0.0 的版本或为了更稳定地实现克隆,可以使用 lodash 的 cloneDeep 方法,该方法也基于结构化克隆算法。
总结
总而言之,在 JS 中用哪种方式复制对象,在很大程度上取决于你要复制的对象的上下文和类型。虽然 lodash 是通用深拷贝最安全的选择,但如果自己动手写,可能会有更具针对性、更高效的实现办法,以下是一个适用于深度克隆日期的简单示例:
function deepClone(obj) {
var copy;
// 如果 obj 是 null、undefined 或 不是对象,直接返回 obj
// Handle the 3 simple types, and null or undefined
if (null == obj || "object" != typeof obj) return obj;
// Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
}
// Handle Array
if (obj instanceof Array) {
copy = [];
for (var i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
}
// Handle Function
if (obj instanceof Function) {
copy = function() {
return obj.apply(this, arguments);
}
return copy;
}
// Handle Object
if (obj instanceof Object) {
copy = {};
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj as type isn't supported " + obj.constructor.name);
}
就个人而言,我期待能够在任何地方使用结构化克隆。
JS对象复制(深拷贝、浅拷贝)的更多相关文章
- js对象的深拷贝及其的几种方法
深拷贝和浅拷贝是javascript中一个比较复杂的问题,也是面试官最喜欢问的问题之一,通过这个为可以看出是否入门,深拷贝和浅拷贝也是初学者经常犯错一个点. 简单来说深拷贝是拷贝储存在栈中的对象,而浅 ...
- JS对象复制
在JavaScript很多人复制一个对象的时候都是直接用"=",因为大家都觉得脚本语言是没有指针.引用.地址之类的,所以直接用"="就可以把一个对象复制给另外一 ...
- js对象的深拷贝
关于对象的深拷贝一直是大家津津乐道一个话题,本骚年通过研究(yuedu)发现还是很easy的. 首推的方法简单有效,JSON.stringfy()和JSON.parse()即可搞定.但是这种简单粗暴的 ...
- JS 对象的深拷贝和浅拷贝
转载于原文:https://www.cnblogs.com/dabingqi/p/8502932.html 这篇文章是转载于上面的链接地址,觉得写的非常好,所以收藏了,感谢原创作者的分享. 浅拷贝和深 ...
- python: 序列化/反序列化及对象的深拷贝/浅拷贝
一.序列化/反序列化 python中内置了很多序列化/反序列化的方式,最常用的有json.pickle.marshal这三种,示例用法如下: import json import pickle imp ...
- js 对象的深拷贝
function deepCopy(obj) { var result = Array.isArray(obj) ? [] : {}; for (var key in obj) { if (obj.h ...
- node种buffer对象数组 深拷贝浅拷贝问题
node的一个上位机和下位机通信的转发程序,用的是udp转发. 其中在发送的时候会进行一次rc4加密数据 出现问题就在这个加密数据这一块,因为这个是升级包广播发送.提前生成了升级用的广播报文,是一个b ...
- js 中数组或者对象的深拷贝和浅拷贝
浅拷贝 : 就是两个js 对象指向同一块内存地址,所以当obj1 ,obj2指向obj3的时候,一旦其中一个改变,其他的便会改变! 深拷贝:就是重新复制一块内存,这样就不会互相影响. 有些时候我们定义 ...
- js对象进行浅复制,深拷贝的方法
js对象浅拷贝和深拷贝详解 本文为大家分享了JavaScript对象的浅拷贝和深拷贝代码,供大家参考,具体内容如下 1.浅拷贝 拷贝就是把父对像的属性,全部拷贝给子对象. 下面这个函数,就是在做拷 ...
随机推荐
- Luogu P4707 重返现世
题目描述 为了打开返回现世的大门,Yopilla 需要制作开启大门的钥匙.Yopilla 所在的迷失大陆有 \(n\) 种原料,只需要集齐任意 \(k\) 种,就可以开始制作. Yopilla 来到了 ...
- 简明 Vim 练级攻略------转自陈皓coolshell
vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆VIM的命令分类,你一定会对这个编辑器失去兴趣的.下面的文章翻译自<Learn Vim Progress ...
- [零基础学python]为什么要开设本栏目
这个栏目的名称叫做"零基础学Python". 如今网上已经有不少学习python的课程.当中也不乏精品.按理说,不缺少我这个基础类型的课程了.可是,我注意到一个问题.无论是课程还是 ...
- 3D数学读书笔记——矩阵基础
本系列文章由birdlove1987编写,转载请注明出处. 文章链接:http://blog.csdn.net/zhurui_idea/article/details/24975031 矩 ...
- 【转】避免全表扫描的sql优化
对查询进行优化,应尽量避免全表扫描,首先应考虑在where 及order by 涉及的列上建立索引: .尝试下面的技巧以避免优化器错选了表扫描:· 使用ANALYZE TABLE tbl_name为扫 ...
- 【转】escape,encodeURI,encodeURIComponent有什么区别?
在这个页面里面试着搜了一下 「UTF-8」 ,居然没有搜到. escape 和 encodeURI 都属于 Percent-encoding,基本功能都是把 URI 非法字符转化成合法字符,转化后形式 ...
- Visual Studio 工具选项设置
1.显示行号 2.颜色主题 3.显示引用 一 显示行号 1)设置方式:工具-选项-文本编辑器-C#,勾选行号确定 二 颜色主题 1)设置方式:工具-选项-环境-常规,下拉选择:浅色.蓝色.深色,点确定 ...
- GIT 分支管理:分支管理策略、Bug分支、Feature分支
通常,合并分支时,如果可能,Git会用Fast forward模式,但这种模式下,删除分支后,会丢掉分支信息. 如果要强制禁用Fast forward模式,Git就会在merge时生成一个新的comm ...
- [06] JSTL标准标签库
1.JSTL概述 之前在<[03-01] JSP自定义标签>中已经说明了自定义标签的概况,而JSTL也是一套标签库,不过是厂商已经定义好的标签库,我们不再需要自行进行定制,直接使用即可. ...
- Linux系列教程(四)——Linux常用命令之文件和目录处理命令
这个系列教程的前面我们讲解了如何安装Linux系统,以及学习Linux系统的一些方法.那么从这篇博客开始,我们就正式进入Linux命令的学习.学习命令,首先要跟大家纠正的一点就是,我们不需要记住每一条 ...