读懂javascript深拷贝与浅拷贝
1. 认识深拷贝和浅拷贝
javascript中一般有按值传递和按引用传递两种复制,按值传递的是基本数据类型(Number,String,Boolean,Null,Undefined),一般存放于内存中的栈区,存取速度快,存放量小;按引用传递的是引用类型(Object,Array,Function,Symbol),一般存放与内存中的堆区,存取速度慢,存放量大,其引用指针存于栈区,并指向引用本身。
深拷贝和浅拷贝是相对于引用类型而言的:
浅拷贝: 指两个js 对象指向同一个内存地址,其中一个改变会影响另一个;
深拷贝: 指复制后的新对象重新指向一个新的内存地址,两个对象改变互不影响。
2. 浅拷贝
浅拷贝常用的方法如下:
1. 简单的赋值操作:
var arr = [1,2,3];
var newarr = arr;
newarr[0] = "one";
console.log(arr); // ["one", 2, 3]
console.log(newarr); // ["one", 2, 3]
console.log(arr==newarr); // true
console.log(arr===newarr); // true
2. Object.assign()方法是ES6的新函数,
可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。拷贝的是对象的属性的引用,而不是对象本身,但是也可以实现一层深拷贝:
var obj = { a: {a: "hello"}, b: 33 };
var newObj = Object.assign({}, obj);
newObj.a.a = "hello world";
console.log(obj); // { a: {a: "hello world"}, b: 33 };
console.log(newObj); // { a: {a: "hello world"}, b: 33 };
console.log(obj.a.a==newObj.a.a); // true
console.log(obj.a.a===newObj.a.a); // true
3.$.extend({},obj)使用递归思路实现了浅拷贝和深拷贝,第一个参数类型为Boolean,当为false的时候必须省略不写则是浅拷贝,当为true的时候为深拷贝:
var obj = { a: {a: "hello"}, b: 33 };
var newObj = $.extend({}, obj);
newObj.a.a = "hello world";
console.log(obj); // { a: {a: "hello world"}, b: 33 };
console.log(newObj); // { a: {a: "hello world"}, b: 33 };
console.log(obj.a.a==newObj.a.a); // true
console.log(obj.a.a===newObj.a.a); // true
浅拷贝是我们经常使用的操作一些对象或数组的有效方法,具体的使用需要结合实际场景来合理的使用,还要考虑一些兼容性的问题,但是在大多实际情景下我们需要使用更多的是深拷贝,尤其是在各种MVVM框架中,引入了状态管理,更多的体现在数据流中希望我们不改变原对象,这样的话实现深拷贝就显得尤为重要。
3. 深拷贝
简单深拷贝常用的方法如下(一维的数据结构):
1.手动的赋值操作:
var obj = { a: 10, b: 20};
var newObj = { a: obj.a, b: obj.b};
newObj.b = 100;
console.log(obj); // { a: 10, b: 20}
console.log(newObj); // { a: 10, b: 100};
console.log(obj == newObj); // false
console.log(obj === newObj); // false
2.Object.assign()方法是ES6的新函数,只能简单的复制一层属性到目标对象,还得考虑兼容性:
var obj = { a: {a: "hello"}, b: 33 };
var newObj = Object.assign({}, obj);
newObj.b = 100;
console.log(obj); // { a: "hello", b: 33 };
console.log(newObj); // { a: "hello", b: 100 };
console.log(obj==newObj); // false
console.log(obj===newObj); // false
复杂深拷贝常用的方法如下(二维的数据结构及以上):
1.JSON.parse(JSON.stringify(obj))是最简单粗暴的深拷贝,能够处理JSON格式的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深拷贝,而且会直接丢失相应的值,还有就是它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object。同时如果对象中存在循环引用的情况也无法正确处理:
var obj = { a: {a: "hello"}, b: 33 };
var newObj = JSON.parse(JSON.stringify(obj));
newObj.b = "hello world";
console.log(obj); // { a: "hello", b: 33 };
console.log(newObj); // { a: "hello world", b: 33};
console.log(obj==newObj); // false
console.log(obj===newObj); // false
2.$.extend(true,{},obj)使用递归思路可以实现深拷贝,要求第一个参数必须为true:
var obj = { a: {a: "hello"}, b: 33 };
var newObj = $.extend(true, {}, obj);
newObj.a.a = "hello world";
console.log(obj); // { a: "hello", b: 33 };
console.log(newObj); // { a: "hello world", b: 33 };
console.log(obj==newObj); // false
console.log(obj===newObj); // false
3. lodash中的_.clone(obj, true)等价于_.cloneDeep(obj) 两个方法,lodash花了大量的代码来实现 ES6 引入的大量新的标准对象,并针对存在环的对象的处理也是非常出色的,因此对于深拷贝来说lodash和其他库相比最友好:
var obj = { a: {a: "hello"}, b: 33 };
var newObj = _.cloneDeep(obj);
newObj.a.a = "hello world";
console.log(obj); // { a: "hello", b: 33 };
console.log(newObj); // { a: "hello world", b: 33 };
console.log(obj==newObj); // false
console.log(obj===newObj); // false
4. 自己实现一个简单的深拷贝deepClone():
function deepClone(obj){
if(typeof obj !== "object") return;
let newObj = obj instanceof Array ? [] : {};
for(let key in obj){
if(obj.hasOwnProperty(key)){
newObj[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
}
}
return newObj;
}
let obj = {a: 11, b: function(){}, c: {d: 22}};
deepClone(obj); // {a: 11, b: f(), c: {d: 22}};
对于深拷贝来说最常用的就是这些方法,当然还有其他的一些库,比如deepCopy等,此外数组常用contact和slice来实现深拷贝,不同的方法有其最好的适用环境,还是那句话:"离开场景谈性能就是耍流氓",下面用数据具体分析一下一维数据结构和二维数据结构在不同方法下的性能对比。
4.深拷贝不同方法的性能对比
深拷贝一维数据结构用时对比:
var obj = [];
for (var i = 0; i < 100; i++) {
obj[i] = Math.random();
}
console.time("assign");
var newObj = Object.assign({}, obj);
console.timeEnd("assign");
console.time("JSON.parse(JSON.stringify())");
var newObj = JSON.parse(JSON.stringify(obj));
console.timeEnd("JSON.parse(JSON.stringify())");
console.time("$.extend");
var newObj = $.extend(true, {}, obj);
console.timeEnd("$.extend");
console.time("Loadsh.cloneDeep");
var newObj = _.cloneDeep(obj);
console.timeEnd("Loadsh.cloneDeep");
经过多次实验分析发现,一维数据结构的深拷贝方法性能最佳的为Object.assign();
深拷贝二维数据结构用时对比:
var obj = [];
for (var i = 0; i < 100; i++) {
obj[i] = {};
for (var j = 0; j < 100; j++) {
obj[i][j] = Math.random();
}
}
console.time("JSON.parse(JSON.stringify())");
var newObj = JSON.parse(JSON.stringify(obj));
console.timeEnd("JSON.parse(JSON.stringify())");
console.time("$.extend");
var newObj = $.extend(true, {}, obj);
console.timeEnd("$.extend");
console.time("Loadsh.cloneDeep");
var newObj = _.cloneDeep(obj);
console.timeEnd("Loadsh.cloneDeep");
经过多次实验分析发现,二维数据结构的深拷贝方法性能最佳的为JSON.parse(JSON.stringify());
5.总结
一维数据结构的深拷贝方法建议使用:Object.assign();
二维数据结构及以上的深拷贝方法建议使用:JSON.parse(JSON.stringify());
特别复杂的数据结构的深拷贝方法建议使用:Loadsh.cloneDeep();
读懂javascript深拷贝与浅拷贝的更多相关文章
- javascript 深拷贝与浅拷贝
javascript 深拷贝与浅拷贝 深拷贝与浅拷贝 赋值和深/浅拷贝的区别 浅拷贝的实现方式 1.Object.assign() 2.函数库lodash的_.clone方法 3.展开运算符... 4 ...
- javaScript深拷贝和浅拷贝简单梳理
在了解深拷贝和浅拷贝之前,我们先梳理一下: JavaScript中,分为基本数据类型(原始值)和复杂类型(对象),同时它们各自的数据类型细分下又有好几种数据类型 基本数据类型 数字Number 字符串 ...
- JavaScript深拷贝与浅拷贝的理解
个人是这么理解深拷贝和浅拷贝的:就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力. 一起看看我举的浅拷贝栗子: let ...
- JavaScript深拷贝与浅拷贝的区别和实现方式
如何区分深拷贝和浅拷贝呢,简单来说对象B拷贝了对象A,如果对象A和对象B共用一个对象,对象B改变对象A跟着改变这就是浅拷贝:但如果对象B拷贝了对象A,但是对象A和对象B是分开的,那么就是深拷贝 基本数 ...
- javaScript 深拷贝、浅拷贝
在 JS 中有一些基本类型像是Number.String.Boolean,而对象就是像这样的东西{ name: 'Larry', skill: 'Node.js' },对象跟基本类型最大的不同就在于他 ...
- JavaScript深拷贝和浅拷贝
1. 基本类型 和 对象类型 他们最大的区别就是在于他们的传值方式. 基本类型是传值 对象类型就是传引用. 这里复制一份obj叫做obj2, 这里修改了obj2的b为100 同时也修改了obj1.b. ...
- JS JavaScript深拷贝、浅拷贝
浅拷贝:浅拷贝只是复制了内存地址,如果原地址中的对象改变了,浅拷贝出来的对象也会相应改变. 深拷贝:开辟了一块新的内存存放地址和地址指向的对象,原地址的任何对象改变了,深拷贝出来的对象不变. 浅拷贝数 ...
- 小白神器 - 两篇博客读懂JavaScript (一基础篇)
JavaScript是一门编程语言,浏览器内置了JavaScript语言的解释器,所以在浏览器上按照JavaScript语言的规则编写相应代码之,浏览器可以解释并做出相应的处理. 一. 编写格式 1, ...
- 十分钟读懂JavaScript原型和原型链
原型(prototype)这个词来自拉丁文的词proto,意谓“最初的”,意义是形式或模型.在JavaScript中,原型的探索也有很多有趣的地方,接下来跟随我的脚步去看看吧. 原型对象释义 每一个构 ...
随机推荐
- RabbitMQ系列教程之七:RabbitMQ的 C# 客户端 API 的简介
今天这篇博文是我翻译的RabbitMQ的最后一篇文章了,介绍一下RabbitMQ的C#开发的接口.好了,言归正传吧. Net/C# 客户端 API简介1.主要的命名空间,接口和类 定义核心的API的 ...
- RxSwift 系列(六) -- Mathematical and Aggregate Operators
前言 本篇文章将要学习RxSwift中数学和集合操作符,在RxSwift中包括了: toArray reduce concat toArray 将一个Observable序列转化为一个数组,并转换为一 ...
- ARM开发(1) 基于STM32的LED跑马灯
一 跑马灯原理: 1.1 本实验实现2个led的跑马灯效果,即2个led交替闪烁. 1.2 实验思路:根据电路图原理,给led相关引脚赋予高低电平,实现电路的导通,使led灯发光. 1.3 开发 ...
- (转)@ContextConfiguration注解说明
场景:学习spring实战中相关的单元测试 1 正常使用 @ContextConfiguration Spring整合JUnit4测试时,使用注解引入多个配置文件 1.1 单个文件 @ContextC ...
- OpenFlow协议1.0及1.3版本分析
OpenFlow是SDN控制器和交换之间交流的协议,在SDN领域有着十分重要的地位. OpenFlow协议发展到现在已经经过了1.0.1.3.1.4等版本.其中1.0和1.3版本使用的是最为广泛的. ...
- ETL作业自动化调度软件TASKCTL4.5安装程序是选项解释
下文仅针对TASKCTL4.5有效,在执行安装程序后,会看到安装程序有4个选项,其中,1和2是需要了解的. 1, 典型安装: 这个选项是单机部署,在第一次使用TASKCTL的时候,我用的是这种安装方式 ...
- Git的一些知识
Git Git的特点: Git存储的是文件快照, 即整个文件内容, 并保存指向快照的索引 分布式 原理 这个之前面试实习的时候被问到过, 搞懂基本原理还是很重要的 Git的目录结构在执行git ini ...
- CentOS IP DNS设置
1.CentOS 修改DNS 修改对应网卡的DNS的配置文件 # vi /etc/resolv.conf 修改以下内容 nameserver 8.8.8.8 #google域名服务器 nameserv ...
- mysql5.7.18的安装与主从复制
CentOS6.7安装mysql5.7.18 1. 解压到/usr/local目录 # tar -zxvf mysql-5.7.18-linux-glibc2.5-i686.tar.gz -C /u ...
- 为什么是Spring Boot
原文:https://dzone.com/articles/why-springboot 作者:Siva Prasad Reddy Katamreddy 译者:Oopsguy 本文介绍将各种Sprin ...