在面试中经常会问到js的深拷贝和浅拷贝,也常常让我们手写,下面我们彻底搞懂js的深拷贝与浅拷贝。

在js中 Array 和 Object  这种引用类型的值,当把一个变量赋值给另一个变量时,这个值得副本其实是一个指针,这是两个变量的指针指向的是同一片推内存,当我们改变其中一个值的时候,另一个值也会受到影响。

那么这就分为两种情况,浅拷贝和深拷贝

浅拷贝:拷贝对象的引用

// 对象
var obj1 = {
a: 1,
b: 2
};
var obj2 = obj1; obj2.a = 3; console.log(obj1); // {a: 3, b; 2}
console.log(obj2); // {a: 3, b: 2} // 数组
var arr1 = [1, 2, 3];
var arr2 = arr1; arr2.push(4); console.log(arr1); // [1, 2, 3, 4]
console.log(arr2); // [1, 2, 3 ,4]

可以看出,拷贝的是一个对象的引用,常用的方法有 jQuery 的extend({}, obj) 或者 Array.prototype.slice() 和 Array.prototype.concat() 都会返回一个数组或者对象的浅拷贝

var arr1 = ['a', {a: 1}];
var arr2 = arr1.slice(); console.log(arr1 === arr2); // false arr2[0] = 'b';
console.log(arr1); // ['a', {a: 1}] 基本类型的值不会相互影响 arr2[1].a = 2;
console.log(arr1); // ['a', {a: 2}] 引用类型的值还是无法拷贝

为了加深印象,我们可以手动实现一个slice 或者jq 的extend({}, obj)

function shallowClone(source) {
if (!source || typeof source !== 'object') {
throw new Error('error');
}
var resultObj = source.constructor === Array ? [] : {};
for (var keys in source) {
if (source.hasOwnProperty(keys)) {
resultObj[keys] = source[keys];
}
}
return resultObj;
} var obj1 = {a: 1, b: [1, 2]}
var obj2 = shallowClone(obj1); obj2.a = 2;
console.log(obj1); // {a: 1, b: [1, 2]} obj2.b.push(3)
console.log(obj1); // {a: 1, b: [1, 2, 3]}

深拷贝:拷贝对象的实例

深拷贝就是拷贝出一个新的实例,新的实例与原来的实例互不影响,实现深拷贝的方法有几种:

1、使用jq 的第三个参数来递归调用 $.extend(true, obj, ...),或者是lodash等第三方库函数实现

2、自己实现一个深拷贝,自己实现也有两种方法,一种是使用递归的方式拷贝,另一种是JSON.parse和JSON.stringfy

// 递归实现深拷贝
function deepClone(source) {
if (!source || typeof source !== 'object') {
throw new Error('error');
}
var resultObj = source.constructor === Array ? [] : {};
for (var keys in source) {
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
resultObj[keys] = source[keys].constructor === Array ? [] : {};
resultObj[keys] = deepClone(source[keys]);
}
else {
resultObj[keys] = source[keys];
}
}
}
return resultObj;
} var a1 = {
a: 1,
b: [1, 2],
c: {
a: 1,
b: 2
}
}; var a2 = deepClone(a1);
a2.b.push(3) console.log(a1); // {a: 1, b: [1, 2], c: {a: 1, b: 2}}
console.log(a2); // {a: 1, b: [1, 2, 3], c: {a: 1, b: 2}}

循环引用问题

上面的问题看似解决了所有的问题,但是有一种情况会有问题,就是循环引用。

1. 父级引用

父级引用是指,当某个属性的值正好是这个对象本身,如果我们用上面的方法进行深拷贝,就会在子元素 -> 父元素 -> 子元素 ...之间死循环,最后导致栈溢出。比如下面的代码:

var a1 = {
a: 1,
b: 2
};
a1.c = a1;
var result = deepClone(a1); // Uncaught RangeError: Maximum call stack size exceeded

解决办法:就是判断一个对象的字段是否引入了这个对象或者这个对象的任意父级,那么就需要修改上面的函数:

function deepClone2(source, parent = null) {
if (!source || typeof source !== 'object') {
throw new Error('error');
}
var resultObj = source.constructor === Array ? [] : {};
var _parent = parent;
while (_parent) {
if (_parent.originParent === source) {
return _parent.currentParent;
}
_parent = _parent.parent;
}
for (var keys in source) {
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
resultObj[keys] = source[keys].constructor === Array ? [] : {};
resultObj[keys] = deepClone2(source[keys], {
originParent: source,
currentParent: resultObj,
parent: parent
});
}
else {
resultObj[keys] = source[keys];
}
}
}
return resultObj;
} var a1 = {
a: 1,
b: 2
};
a1.c = a1;
var result = deepClone2(a1);
console.log(a1) // {a: 1, b: 2, c: {…}}

2. 同级引用

假设有如下代码

var obj = {
a: {
name: 'a'
},
b: {
name: 'b'
},
c: {}
}; obj.c.d = obj.a;
console.log(obj.c.d === obj.a); // true

如果我们调用上面的deepClone2函数

var copy = deepClone2(obj);
console.log(copy.a); // {name: "a"}
console.log(copy.c.d); // {name: "a"}
console.log(copy.a === copy.c.d); // false

从上面可以看出,虽然 copy.a 与 copy.c.d是的值是相等的,但二者引用的并不是同一个对象。

这种情况是因为 obj.a 并不在obj.c.d 的对象链上,所以 deepClone2 函数就无法检测到 obj.c.d 对 obj.a 也是一种引用关系,所以 deepClone2 函数就将 obj.a 深拷贝的结果赋值给了copy.c.d。

解决方案:父级的引用是一种引用,非父级的引用也是一种引用,那么只要记录下对象A中的所有对象,并与新创建的对象一一对应即可

function deepClone3(obj) {
// hash表,记录所有的对象的引用关系
let map = new WeakMap();
function dp(obj) {
let result = null;
let keys = Object.keys(obj);
let key = null,
temp = null,
existobj = null; existobj = map.get(obj);
//如果这个对象已经被记录则直接返回
if(existobj) {
return existobj;
} result = {}
map.set(obj, result); for(let i =0,len=keys.length;i<len;i++) {
key = keys[i];
temp = obj[key];
if(temp && typeof temp === 'object') {
result[key] = dp(temp);
}else {
result[key] = temp;
}
}
return result;
}
return dp(obj);
} var obj = {
a: {
name: 'a'
},
b: {
name: 'b'
},
c: {}
}; // 子级引用
var copy = deepClone3(obj);
console.log(copy.a); // {name: "a"}
console.log(copy.c.d); // {name: "a"}
console.log(copy.a === copy.c.d); // true // 父级引用
var a1 = {
a: 1,
b: 2
};
a1.c = a1;
var result = deepClone3(a1);
console.log(a1) // {a: 1, b: 2, c: {…}}

js 中的深拷贝与浅拷贝的更多相关文章

  1. js中的深拷贝与浅拷贝

    对象的深拷贝于浅拷贝 对于基本类型,浅拷贝过程就是对值的复制,这个过程会开辟出一个新的内存空间,将值复制到新的内存空间.而对于引用类型来书,浅拷贝过程就是对指针的复制,这个过程并没有开辟新的堆内存空间 ...

  2. js 中的 深拷贝与浅拷贝

    js在平时的项目中,赋值操作是最多的:比如说: var person1 = { name:"张三", age:18, sex:"male", height:18 ...

  3. js中的深拷贝和浅拷贝2

    所谓 深浅拷贝: 对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝. 而如果是在堆中重新分配内存,拥有不同的地址,但是值是一 ...

  4. JS中的深拷贝和浅拷贝

    浅拷贝 浅拷贝是拷贝第一层的拷贝 使用Object.assign解决这个问题. let a = { age: 1 } let b = Object.assign({}, a) a.age = 2 co ...

  5. JS对象复制(深拷贝、浅拷贝)

    如何在 JS 中复制对象 在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法. 在开始之前,有一些基础知识值得一提:Javas ...

  6. javascript中的深拷贝与浅拷贝

    javascript中的深拷贝与浅拷贝 基础概念 在了解深拷贝与浅拷贝的时候需要先了解一些基础知识 核心知识点之 堆与栈 栈(stack)为自动分配的内存空间,它由系统自动释放: 堆(heap)则是动 ...

  7. 浅谈Java中的深拷贝和浅拷贝(转载)

    浅谈Java中的深拷贝和浅拷贝(转载) 原文链接: http://blog.csdn.net/tounaobun/article/details/8491392 假如说你想复制一个简单变量.很简单: ...

  8. C语言中的深拷贝和浅拷贝

    //C语言中的深拷贝和浅拷贝 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #inc ...

  9. 浅谈Java中的深拷贝和浅拷贝

    转载: 浅谈Java中的深拷贝和浅拷贝 假如说你想复制一个简单变量.很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(bool ...

随机推荐

  1. JavaScript深入之词法作用域和动态作用域(转载)

    作用域 作用域是指程序源代码中定义变量的区域. 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限. JavaScript 采用词法作用域(lexical scoping),也就是静态作 ...

  2. elasticsearch 深入 —— Search Type检索类型

    在此我们再给出那个查询的代码: $ curl -XGET localhost:9200/startswith/test/_search?pretty -d '{ "query": ...

  3. 记一个日志冲突——管中窥豹[java混乱的日志体系]

    D:\Java\jdk1.8.0_211\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Communit ...

  4. pychrome激活

    http://blog.csdn.net/fx677588/article/details/58164902

  5. hive中为分区表增加字段需要注意默认不会修改已有分区的字段,导致查询时新增字段为null

    若向hive表添加字段,通常会使用下面这种语句 alter table default.testparquet add columns(c8 string); 但是对于分区表来说, 1. 若新建的分区 ...

  6. ROS基础学习总结

    最近一周因为工程需要,把ROS的一些基础学习了一下,现在做一下总结. 学习资源: #创客智造整理的wiki上的ROS入门教程(中文)https://www.ncnynl.com/category/ro ...

  7. SQL Server 数据库使用备份还原造成的孤立用户和对象名‘xxx’无效的错误的解决办法

    SQL Server 数据库使用备份还原造成的孤立用户和对象名‘xxx’无效的错误的解决办法 在使用数据库的过程中,经常会遇到数据库迁移或者数据迁移的问题,或者有突然的数据库损坏,这时需要从数据库的备 ...

  8. logstash配置文件详解

     logstash pipeline 包含两个必须的元素:input和output,和一个可选元素:filter. 从input读取事件源,(经过filter解析和处理之后),从output输出到目标 ...

  9. 关于exe文件传递参数方法

    段代码手工折叠 {$REGION 'Designer Managed Code'} ............ {$ENDREGION} 昨天同事问到,delphi里exe文件如何传递参数? 因为手头装 ...

  10. SSH弱小算法支持问题

    SSH弱小算法支持问题:SSH的配置文件中加密算法没有指定(没有配置加密算法),则会默认支持所有加密算法,包括arcfour,arcfour128,arcfour256等弱加密算法.解决方法:1.修改 ...