JS对象的深拷贝和浅拷贝的总结
对象拷贝首推简单有效的方法:JSON.stringfy()和JSON.parse()即可搞定。但是这种简单粗暴的方法有其局限性。当值为undefined、function、symbol 会在转换过程中被忽略...所以,对象值有这三种的话用这种方法会导致属性丢失。
利用window.JSON的方法做深拷贝存在2个缺点:
如果你的对象里有函数,函数无法被拷贝下来
无法拷贝copyObj对象原型链上的属性和方法
弄懂深拷贝的相关问题需要的前置知识:
理解JS里的引用类型和值类型的区别,知道Obj存储的只是引用
对原型链有基本了解
基本类型:undefined、null、Boolean、number、string。变量直接按指存放在栈区内,可以直接访问,所以我们平时把字符串、数字的值赋值给新变量,相当于把值完全复制过去,新变量的改变不会影响旧变量。
引用类型:存放在堆区的对象,变量在栈区中保存的是一个指针地址。
理解对象引用、深拷贝、浅拷贝的区别
对象引用:
对象引用容易理解,直接赋值,修改复制后的数组,原对象会随之改变。
在JS中,一般的=号传递的都是对象/数组的引用。
//对象引用 var boy = { age:18 } var girl = boy; console.log(boy === girl);//true girl.age = 20; console.log(boy.age);//20
理解:使用“=”进行赋值,girl和boy指向了同一内容地址,修改一个,另一个也会修改。
浅拷贝和深拷贝的理解:
只有对象里嵌套对象的情况下,才会根据需求讨论,我们要深拷贝还是浅拷贝。
比如下面这种对象
var obj1 = { name: 'ziwei', arr : [1,2,3] } // 调用objectCopy()拷贝函数后,obj2拷贝obj1所有的属性。但是obj2.arr和obj1.arr是不同的引用,并且指向同一个内存空间。 var obj2 = objectCopy ( obj1 , {}) console.log( obj1 !== obj2 ) // true 无论哪种拷贝,obj1和obj2一定都是2个不同的对象(内存空间不同) console.log( obj2.arr === obj1.arr ) // true 他们2个对象里arr的引用,指向【相同的】内存空间
2个obj经过拷贝后,虽然他们属性相同,也的确是不同的对象,但他们内部的obj都是指向同一个内存空间,这种我们叫浅拷贝。
调用deepCopy()拷贝函数后,obj2拷贝obj1所有的属性,而且obj2.arr和obj1.arr是指向不同的内存空间。
2个obj2除了拷贝了一样的属性,没有任何其他关联。
var obj2 = deepCopy( obj1 , {}) console.log( obj1 !== obj2 ) // true 无论哪种拷贝,obj1和obj2一定都是2个不同的对象(内存空间不同) console.log( obj2.arr === obj1.arr ) // false 他们2个对象里arr的引用,指向【不同的】内存空间
因此这种 2个obj经过拷贝后,除了拷贝下来相同的属性之外,没有任何其他关联的2个对象,这种我们叫深拷贝。
1)对象遍历拷贝(浅拷贝)
var obj = { name: '拷贝', class: '类别' } function copy (obj) { let newObj = {}; for (let item in obj ){ newObj[item] = obj } return newObj; } var copyObj = copy(obj); copyObj.name = '拷贝'; console.log(obj); // {name: "拷贝", job: "类别"} console.log(copyObj); // {name: "拷贝", job: Object}
no,这不是我想要的。
2)ES6的Object.assign 对象拷贝(浅拷贝)
var obj = { name: '拷贝', class: '我是es6方法' } var copyObj = Object.assign({}, obj); copyObj.name = '拷贝'; console.log(obj); // {name: "拷贝", class: "我是es6方法"} console.log(copyObj); // {name: "拷贝", class: "我是es6方法"}
Object.assign:用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回合并后的target
用法: Object.assign(target, source1, source2); 所以 copyObj = Object.assign({}, obj); 这段代码将会把obj中的一级属性都拷贝到 {}中,然后将其返回赋给copyObj
no,有缺陷。
3)ES6扩展运算符(...)(浅拷贝)
var obj = { name: '拷贝', class: '我是es6方法' } var copyObj = { ...obj } console.log(obj); // {name: "拷贝", class: "我是es6方法"} console.log(copyObj); // {name: "拷贝", class: "我是es6方法"}
扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
no,不完美。
4)JSON.parse()与JSON.stringify()(深拷贝-JSON对象)
//纯数据json对象的深度克隆 function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); }
理解:通过对象符串化和字符串对象化进行对象的拷贝。此方法只使用与纯JSON对象的深拷贝
5)对象深拷贝函数方法封装(深拷贝)
var clone = function (obj) { if(obj === null) return null if(typeof obj !== 'object') return obj; if(obj.constructor===Date) return new Date(obj); if(obj.constructor === RegExp) return new RegExp(obj); var newObj = new obj.constructor (); //保持继承链 for (var key in obj) { if (obj.hasOwnProperty(key)) { //不遍历其原型链上的属性 var val = obj[key]; newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合 } } return newObj; };
理解深拷贝实现:
(1)、用 new
obj.constructor ()构造函数新建一个空的对象,可以保持原形链的继承;
(2)、用obj.hasOwnProperty(key)来判断属性是否来自原型链上,因为for..in..也会遍历其原型链上的可枚举属性。
(3)、函数用到递归算法,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,需要使用 arguments.callee。
深拷贝注意点:
1. 不仅拷贝第一层级,还能够拷贝数组或对象所有层级的各项值
2. 不是单独针对数组或对象,而是能够通用于数组,对象和其他复杂的JSON形式的对象
3. 检验深拷贝成功的方法就是:改变任意一个新对象/数组中的属性/元素,都不改变原对象/数组
深拷贝和浅拷贝图解
存在大量深拷贝需求的代码——immutable提供的解决方案
实际上,即使我们知道了如何在各种情况下进行深拷贝,我们也仍然面临一些问题: 深拷贝实际上是很消耗性能的。所以,当你的项目里有大量深拷贝需求的时候,性能就可能形成了一个制约的瓶颈了。
immutable的作用:
通过immutable引入的一套API,实现:
1.在改变新的数组(对象)的时候,不改变原数组(对象)
2.在大量深拷贝操作中显著地减少性能消耗
先睹为快:
const { Map } = require('immutable') const map1 = Map({ a: 1, b: 2, c: 3 }) const map2 = map1.set('b', 50) map1.get('b') // 2 map2.get('b') // 50
深拷贝和浅拷贝的实现方式
浅拷贝的实现:
浅拷贝比较简单,就是用for in 循环赋值。
深拷贝的实现:
深拷贝,就是遍历那个被拷贝的对象
判断对象里每一项的数据类型
如果不是对象类型,就直接赋值,如果是对象类型,就再次调用deepCopy,递归的去赋值。