深浅拷贝知识在我们的日常开发中还算是用的比较多, 但是之前的状态一直都是只曾听闻, 未曾使用(其实用了只是自己没有意识到), 所以今天来跟大家聊一聊 JS 的深浅拷贝;
首先我们来了解一下 JavaScript 的数据类型, 在 ES5 版本的 JS 中我们的 JavaScript 一共有 6 种数据类型, 分别是:
Number(数值型),String(字符串),Boolean(布尔型),Object(对象, object 和 array 都属于 Object 类型),null,undefined
我们日常使用的 JavaScript 深浅拷贝主要是面向 Object 引用类型进行拷贝;
我们知道了 JS 的深浅拷贝面对的执行操作对象, 然后我们再来看一下深浅拷贝的概念:
拷贝顾名思义就是复制, 内存中一共分为栈内存和堆内存两大区域, 所谓深浅拷贝主要是对 JavaScript 引用类型数据进行拷贝一份, 浅拷贝就是引用类型数据相互赋值之后, 例 obj1=obj2; 如果后面的操作中修改 obj1 或者 obj2, 这个时候数据是会进行相应的变化的, 因为在内存中引用类型数据是存储在堆内存中, 堆内存中存放的是引用类型的值, 同时会有一个指针地址指向栈内存, 两个引用类型数据地址一样, 如果其中一个发生变化另外一个都会有影响; 而深拷贝则不会, 深拷贝是会在堆内存中重新开辟一块空间进行存放;
基本类型复制:
- var a = 1;
- var b = a;// 复制
- console.log(b)//1
- a = 2;// 改变 a 的值
- console.log(b)//1
- console.log(a) //2
因为 a,b 都是属于基本类型, 基本类型的复制是不会影响对方的, 因为基本类型是每一次创建变量都会在栈内存中开辟一块内存, 用来存放值, 所以基本类型进行复制是不会对另外一个变量有影响的;
引用类型复制:
引用类型的复制我们分为数组的复制和对象的复制两个方面来进行讲解:
JS 的浅拷贝:
- var arr1 = ['red','green'];
- var arr2 = arr1;// 复制
- console.log(arr2)//['red','green'];
- arr1.push('black') ;// 改变 color1 的值
- console.log(arr2)//['red','green','black']
- console.log(arr1) //["red", "green", "black"]
上面的案例是 JavaScript 数组的浅拷贝, 通过上面的知识我们可以看知道数组是引用类型数据, 引用类型数据复制是会进行相互影响的, 我们看到 arr1.push('black')添加了一个新的子项, 因为上面 var arr2=arr 这行代码是将两个引用类型数据的地址指针指向了同一块堆内存区域, 所以不管是 arr1 还是 arr2 修改, 任何一个一个改动两个数组都是会互相产生影响的; 上面的那种直接赋值方式的复制就是我们常说的引用类型的浅拷贝;
关于深拷贝很多同学都误以为 JS 的原生方法 concat,slice 是属于深拷贝, 其实不是的; JS 的原生方法 concat,slice 都是仅适用于一维数组, 一旦到了二维数组或者多维数组中就会出现问题, 就出现拷贝的不够彻底导致还是会发生数据的相互牵引问题;
- slice:
- var arr1 = ['red','green'];
- var arr2 = arr1.slice(0);// 复制
- console.log(arr2)//['red','green'];
- arr1.push('black') ;// 改变 color1 的值
- console.log(arr2)//["red", "green"]
- console.log(arr1)//["red", "green", "black"]
JS 原生的方法 slice 会返回一个新的数组, 上述代码乍一看会以为是深拷贝, 因为 arr2 和 arr1 相互复制和牵引, 而当 arr1 调用了 push 方法添加了新数组子项的时候, arr2 没有发生变化; 是的, 这是符合深拷贝的特性, 但是拷贝的不够彻底, 所以还不能算是真正意义上的深拷贝, 所以 slice 只能被称为浅拷贝; slice 方法只适用于一维数组的拷贝, 在二维数组中就会破绽百出;
下面我们再来看一下二维数组的例子:
- var arr1=[1,2,3,['1','2','3']];
- var arr2=arr1.slice(0);
- arr1[3][0]=0;
- console.log(arr1);//[1,2,3,['0','2','3']]
- console.log(arr2);//[1,2,3,['0','2','3']]
上述代码是一个二维数组, 当我们在 arr1[3][0]里面进行更改 arr1 的值的时候, 我们发现 arr1,arr2 两个数组的值都发生了变化; 所以事实证明 slice 不是深拷贝;
- concat:
- var arr1 = ['red','green'];
- var arr2 = arr1.concat();// 复制
- console.log(arr2)//['red','green'];
- arr1.push('black') ;// 改变 color1 的值
- console.log(arr2)//["red", "green"]
- console.log(arr1)//["red", "green", "black"]
- var arr1=[1,2,3,['1','2','3']];
- var arr2=arr1.concat();
- arr1[3][0]=0;
- console.log(arr1);//[1,2,3,['0','2','3']]
- console.log(arr2);//[1,2,3,['0','2','3']]
concat 方法在一维数组中是不会影响源数组的数据的, 而在二维数组中 concat 的表现和 slice 是一样的;
JS 的深拷贝:
JS 数组中实现深拷贝的方法都多种, 比如 JSON.parse(JSON.stringify())和递归以及 jQuery 库的 extend 方法 (只是 extend 方法需要依赖 jQuery 库, 所以我们尽量的使用原生的方式来实现) 都是可以实现数组和对象的深拷贝的;
- var arr1 = ['red','green'];
- var arr2 = JSON.parse(JSON.stringify(arr1));// 复制
- console.log(arr2)//['red','green'];
- arr1.push('black') ;// 改变 color1 的值
- console.log(arr2)//["red", "green"]
- console.log(arr1)//["red", "green", "black"]
上述代码中我们可以清晰的看到 JSON.parse(JSON.stringify())是真正意义上实现了深拷贝;
递归实现深拷贝:
- function deepClone(obj){
- // 判断参数是不是一个对象
- let objClone = obj instanceof Object?[]:{};
- if(obj && typeof obj==="object"){
- for(key in obj){
- if(obj.hasOwnProperty(key)){
- // 判断 ojb 子元素是否为对象, 如果是, 递归复制
- if(obj[key]&&typeof obj[key] ==="object"){
- objClone[key] = deepClone(obj[key]);
- }else{
- // 如果不是, 简单复制
- objClone[key] = obj[key];
- }
- }
- }
- }
- return objClone;
- }
- var a ={
- x:1,
- y:2
- };
- b=deepClone(a);
- a.x=3
- console.log(a);
- console.log(b);
输出效果如下:
总结:
1: 深拷贝只是从源数据中拷贝一份出来进行操作, 而不是改变源数据; 改变源数据的那是浅拷贝;
2: 原生 JS 方法 slice,concat 都不是真正意义上的深拷贝, 都仅只适用于一维数组, 拷贝的属性不够彻底;
3: 实现 JS 深拷贝我们可以通过 JSON.parse(JSON.stringify()), 递归以及 jQuery 库的 extend 方法来实现;
来源: https://www.cnblogs.com/dengyao-blogs/p/11466598.html