说起深浅拷贝其实就是数据内存,堆栈问题。
基本数据类型值指保存在栈内存中的简单数据段
var a=1
操作的是变量实际保存的值。a = 2;
基本类型变量的复制:从一个变量向一个变量复制时,会在栈中创建一个新值,然后把值复制到为新变量分配的位置上。var b= a;
栈内存
引用数据类型:引用数据类型值指保存在堆内存中的对象。也就是,变量中保存的实际上的只是一个指针,这个指针指向内存中的另一个位置,该位置保存着对象。访问方式是按引用访问。
vara = new Object();
当操作时,需要先从栈中读取内存地址,然后再延指针找到保存在堆内存中的值再操作
a.name= 'xz';
引用类型变量的复制:复制的是存储在栈中的指针,将指针复制到栈中未新变量分配的空间中,而这个指针副本和原指针指向存储在堆中的同一个对象;复制操作结束后,两个变量实际上将引用同一个对象。因此,在使用时,改变其中的一个变量的值,将影响另一个变量。
var b= a;所以深浅拷贝。就是对堆内存(复杂数据类型)而言。
浅拷贝就是只拷贝相同的指针,跟第六张图一样。js内有些方法也是现实了浅拷贝,例如对象的Object.assign()第一个参数是我们最终复制的目标对象,后面的所有参数是我们的即将复制的源对象,支持对象或数组,一般调用的方式为
var newObj = Object.assign({}, originObj);
另外
[].slice()
方法可以视为数组对象的浅拷贝。深拷贝则是指针也发生了改变,为什么js内部不提供统一的方法进行深拷贝?
- 1,如何定义深拷贝?
- 2,怎么处理拷贝后的原型?
- 3,原生BOM/DOM怎么拷贝?
- 4,函数对象是新创建还是引用?太多极端情况无法统一。
2,内部循环引用怎么处理。是遍历过的对象都进行缓存,每次进行对比,然后再造一个循环引用来?这样带来的内存消耗可接受吗。
var obj = {}; obj.b = obj; //深拷贝obj对象时,就会循环的遍历b属性,直到栈溢出。
经典题
var a = { name: '前端' } var b = a; a = null; //释放引用 console.log(a) //null console.log(b) //{ name: '前端' }
var a = {n: 1}; var b = a; a = {n: 2}; a.x = a console.log(a.x)//{n:2} console.log(b.x)//undefined
var a = {n: 1}; var b = a; a.x=a={n:2}//等价于b.x = a = {n: 2}; //先是预编译ab同时指向的对象增加x属性, //该对象也就是后来都没变的b指向的对象, //这里a= {n: 2};改变了a的指向。 console.log(a.x)//undefined console.log(b.x)//{n:2}
对象实现深拷贝的方案
通过JSON对象来回转换。JSON.parse(JSON.stringify())
缺点:- 1)拷贝的对象的值中如果有函数、undefined、symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
- 2)无法拷贝不可枚举的属性,无法拷贝对象的原型链
- 3)拷贝Date引用类型会变成字符串
- 4)拷贝RegExp引用类型会变成空对象
- 5)对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
- 6)无法拷贝对象的循环应用(即obj[key] = obj)所以这个一般只适合纯数据对象
通过{}赋值创建,开辟新的引用地址,在将需要拷贝的键值放在新的堆内存里面。实现深拷贝
var obj1 = { a: { b: 1 }, c: 1 }; var obj2 = {}; obj2.a = {} obj2.c = obj1.c obj2.a.b = obj1.a.b; console.log(obj1); //{a:{b:1},c:1}; console.log(obj2); //{a:{b:1},c:1}; obj1.a.b = 2; console.log(obj1); //{a:{b:2},c:1}; console.log(obj2); //{a:{b:1},c:1};
通过插件,或第三方库。
- lodash
jQuery
var oldJson = { Name: 'quber', List: [1, 2, 3, 4], Obj: [ { name: 'qubernet', fun: function () { return 1; } }, { name: 'qubernet1', fun: function () { return 2; } } ] }; var newJson = $.extend(true, {}, oldJson); console.log(JSON.stringify(newJson));
封装深拷贝方法
function deepClone (target) { if (typeof target !== 'object') { return target } //对于Array,Date,RegExp,Error,Function引用类型正确拷贝 let copy = Array.isArray(target) ? [] : {} for (let key in target) { console.log(key) copy[key] = deepClone(target[key]) } return copy } var target = { a: 1, b: { c: [2] }, d:function () { console.log(123) } } var copyTarget = deepClone(target) target.a = 0 target.b.c = [2,3] target.d = function(){console.log(456)} console.log(target, copyTarget) target.d() copyTarget.d()
首先这个deepClone函数并不能复制不可枚举的属性以及Symbol类型
对象循环引用成环了的情况//上面封装对于循环引用(对象内部存在引用关系)则会造成无线循环爆栈问题 var target = { a: { b: 1, }, }; target.c=target let clonedTarget = deepClone(target); target.a.b = 2; console.log(target, clonedTarget); //所以在递归的时候需要判断
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。