为什么会有深浅拷贝?


js有基本类型引用类型
基本类型 string number boolean undefined null symbol
引用类型 object function array
涉及到栈内存堆内存

基本类型创建的变量和值都保存在栈内存中,name 和 val,当复制时,栈内存会新开辟一个内存,它们 之间就不会相互影响。

引用类型的变量在栈内存中(name), val指向的是一个堆地址,值保存在堆内存中,当复制时,在栈内存创建一个新的变量,但是val依然指向那个堆地址,值发生改变时堆内存里边的值发生了改变,两个变量的值都发生了改变。

拷贝:
基本数据类型, slice(), concat(), Object.assign(), es6的扩展运算符"..."等都是浅拷贝,它们都只是在第一层是深拷贝,但是当嵌套了一个数组的时候也就是第二层或更多层发生改变数据依然会跟着变化。

实现一层深拷贝:
示例1、Object.assign()

 var obj1 = {
    a: 1,
    b: 2,
    c: ['a','b','c']
}
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
console.log(obj1.c); // ["a", 5, "c"]
console.log(obj2.c); // ["a", 5, "c"]

示例2、slice实现

// 对有多层属性的数组对象使用slice
var a = [1,[1,2],3,4];
var b = a.slice();
b[1][0] = 2;
alert(a); // 1,2,2,3,4
alert(b); // 1,2,2,3,4

示例3、concat()方法

 var a=[1,2,[3,4]]
 var c=[];
 var b=c.concat(a);
 b[0]=5;
 b[2][0]=6;
 console.log(b[0]);//5
 console.log(a[0])//1
 console.log(b[2][0]);//6
 console.log(a[2][0])//6

示例4、es6的扩展运算符"..."

 var a=[1,2,[3,4]]
 var b=[...a];
 b[0]=5;
 b[2][0]=6
 console.log(b[0]);//5
 console.log(a[0])//1
 console.log(b[2][0]);//6
 console.log(a[2][0])//6

示例5、Object.create()

function deepCopy(obj) {
  var copy = Object.create(Object.getPrototypeOf(obj));
  var propNames = Object.getOwnPropertyNames(obj);

  propNames.forEach(function(name) {
    var desc = Object.getOwnPropertyDescriptor(obj, name);
    Object.defineProperty(copy, name, desc);
  });

  return copy;
}

var obj1 = { a: 1, b: {bc: 50, dc: 100, be: {bea: 1}} };
var obj2 = deepCopy(obj1);
obj2.a = 20;
obj2.b.bc = 60;
console.log(obj1.a)//1
console.log(obj2.a)//20
console.log(obj1.b.bc)//60
console.log(obj2.b.bc)//60

深拷贝:

1. JSON.stringify 和 JSON.parse  2. 递归嵌套拷贝3. lodash.cloneDeep

示例1、JSON.stringify和JSON.parse粗暴深拷贝(抛弃对象的constructor)

JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象;
function deepCopy(obj1){
    let _obj = JSON.stringify(obj1);
    let obj2 = JSON.parse(_obj);
    return obj2;
}
var a = [1, [1, 2], 3, 4];
var b = deepCopy(a);
b[1][0] = 2;
alert(a); // 1,1,2,3,4
alert(b); // 1,2,2,3,4

注意点:
缺陷:它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;
这种方法能正确处理的对象只有 Number, String, Boolean, Array, 扁平对象,
也就是说,只有可以转成JSON格式的对象才可以这样用,像function没办法转成JSON;
let obj1 = {
   fun:function(){
      alert(123);
   }
}
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(typeof obj1.fun); // function
console.log(typeof obj2.fun); // undefined


示例2、递归拷贝,解决循环引用问题

实现原理:
使用递归的方式实现数组、对象的深拷贝。
先判断各个字段类型,然后用递归解决嵌套数据。
判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝。   
进行深拷贝的不能为空,并且是对象或者是数组。
function deepClone(obj){
  let objClone =  Array.isArray(obj) ? [] : {};
  if (obj && typeof obj === 'object') {
    for(let key in obj){
      if (obj[key] && typeof obj[key] === 'object'){ // 判断对象的这条属性是否为对象
        objClone[key] = deepClone(obj[key]); // 若是对象进行嵌套调用
      }else{
        objClone[key] = obj[key]
      }
    }
  }
  return objClone; //返回深度克隆后的对象
}
let arrayA = [{a: 1, b: 2, aa: [{ab: 1, ac: [{ac: 1}]}]}];
let arrayB = deepClone(arrayA);
arrayB[0]['aa'][0].ab = 2;
console.log(arrayA); // [{a: 1, b: 2, aa: [{ab: 1, ac: [{ac: 1}]}]}]
console.log(arrayB); // [{a: 1, b: 2, aa: [{ab: 2, ac: [{ac: 1}]}]}]

示例3、ES插件lodash

import lodash from 'lodash'

var objects = [1,{ 'a': 1 }, { 'b': 2 }]; 
var deep = lodash.cloneDeep(objects);
deep[0] = 2;
deep[1].a = 2;
console.log(objects[0]);//1
console.log(deep[0]);//2
console.log(objects[1].a);//1
console.log(objects[1].a);//2

参考文章:js实现深拷贝的几种方法:https://www.jianshu.com/p/3dbe82d2826a

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐