js 深浅拷贝的区别,以及实现深浅拷贝的几种方式和优缺点

866 阅读3分钟

1. Js 深浅拷贝的区别

要明白的这个问题还要从js 的数据类型说起

1.1 javascript 的的数据类型和它们在程序中存储的位置

javascript 有两种数据类型

  1. 基本数据类型:它是简单的数据段,包含新增的一共7种 String、Number、Boolean、Null、Undefined、Symbol BigInt
  2. 引用类型: 对象,数组,函数 ,日期对象,正则对象

1.2 javascript 的变量存储方式

栈内存(stack) : 自动分配内存空间,系统自动释放。

堆内存(heap): 由程序员去分配内存,大小不定,也不会自动释放,里面存放的是引用内存的地址

1.3 javascript 基本数据类型与引用数据类型的区别是存的值,还是地址

基本数据类型: 用的值传递

 let a = 10;
    let b = a
    a++
    console.log(b, a); // 10 11

引用数据类型: 地址的传递

    let a = ['a', 'b', 'c']; // 定义一个数组a并赋值 
    let b = a;   // 数组是引用类型,采用地址传递,将a的地址赋值给b
    b.push('d'); // 给b数组增加一个'd'元素
    console.log(a) // ['a', 'b', 'c', 'd']
    console.log(b) // ['a', 'b', 'c', 'd']

分析:由于a和b都是引用类型,它们采用的地址的传递,把a的地址给了b,那么变量a和b,就必然指向同一个堆内存地址。当向b里面添加一个d时,就等于像a和b同时指向的那个堆内存地址添加个d所以a的值也跟着变化。

如何解决上面问题:

要解决上面问题就要用深拷贝和浅拷贝了,js基本数据类型不存在深浅拷贝到概念,深浅拷贝主要是针对于引用类型而言的

1.4 深浅拷贝的概念

浅拷贝: 指拷贝对象的时候,只对第一层键值对进行独立的复制,如果对象里还是对象,就只能复制嵌套对象的地址。

深拷贝: 指的是复制对象的时候完全拷贝一份对象,就算嵌套了对象也相互分离,深拷贝处理后的对象修改对象属性的时候只会影响这个对象本身,不会影响另一个。

2 实现数组浅拷贝的几种方法

【1】Array.concat()


 let arr = [1,2,3];
 let newArr = arr.concat();
 console.log(newArr);
 newArr.push(4)
 console.log(arr,newArr); //[1,2,3]; [1,2,3,4]

【2】Array.slice()


 let arr = [1,2,3];
 let newArr = arr.slice();
 newArr.push(4);
 console.log(arr,newArr); // [ 1, 2, 3 ] [ 1, 2, 3, 4 ]

【3】使用扩展运算符


let arr = [1,2,3];
 let newArr =[...arr];
 newArr.push(4);
 console.log(arr,newArr); // [ 1, 2, 3 ] [ 1, 2, 3, 4 ]

【4】Array.from 实现浅拷贝


let arr = [1,2,3];
let newArr =Array.from(arr);
 console.log("eeee",newArr);
 newArr.push(4);
 console.log(arr,newArr); // [ 1, 2, 3 ] [ 1, 2, 3, 4 ]

[5] Object.assign(); 实现浅拷贝


            let arr=[1,2,3,4];
            let newArr = Object.assign([],arr);
            newArr.push(5)
            console.log(arr,newArr);[ 1, 2, 3,4 ] [ 1, 2, 3, 4,5 ]

3 . js 实现深拷贝常用的方式

1. JSON.parse(JSON.stringify(obj)); // 深拷贝


let obj = {
  name:'wkm',
  age:12,
  n:null,
  u:undefined,
  sy:Symbol('xx'),
  child:{
    ele:'body',
    x:100
  },
  arr:[10,20,30],
  reg:/^\d+$/,
  fn:function(){
    console.log(this.name);
  },
  time:new Date(),
  err:new Error()
  
};
​
let result = JSON.parse(JSON.stringify(obj));
console.log(result);
/* 
{
  name: 'wkm',
  age: 12,
  n: null,
  child: { ele: 'body', x: 100 },
  arr: [ 10, 20, 30 ],
  reg: {},
  time: '2021-12-06T09:40:55.949Z',
  err: {}
}
*/

JSON.parse(JSON.stringify(obj)) 实现深拷贝的原理及特点

原理:把对象转成基本数据类型后,就等于和之前的堆内存把联系断了,在转成对象开辟了新的堆内存空间

特点

  1. 如果对象里有bigint类型会报错,对于bigint 类型无法转换
  2. 对于undefined,symbol,function 等类型会丢失对应的属性
  3. 对于error regexp 这些类型,属性值会变空
  4. 对于属性值是date 等类型的,属性值变成字符串,就算再次转换属性值也还是字符串

2. 手写完成深浅拷贝,深拷贝时解决循环的引用

用法clone(deep,copy): deep 布尔值:true 表示深拷贝,false,表示浅拷贝 copy :需要拷贝的数组和对象,

用的工具函数:toType 检测数据类型,isArrayLike:是否是数组还是类数组 isPlainObject 是否是纯对象 each

工具函数集合

// 数组和对象的深浅克隆
  const clone = function clone() {
    let target = arguments[0],
      deep = false,
      type,
      isArray,
      isObject,
      ctor,
      result,
      treated = arguments[arguments.length - 1];
    if (typeof target === "boolean") {
      if (arguments.length === 1) return target;
      deep = target;
      target = arguments[1];
    }
    // 判断第一次没有传treated 所以他是个空数组
    if (!Array.isArray(treated) || !treated.treated) {
      treated = [];
      treated.treated = true;
    }
    if (treated.includes(target)) return target;
    treated.push(target);
    type = toType(target);
    isArray = isArrayLike(target);
    isObject = isPlainObject(target);
    if (target == null) return target;
    ctor = target.constructor;
    if (/^(regexp|date|error)$/i.test(type)) {
      if (type === "error") target = target.message;
      return new ctor(target);
    }
    // 函数为啥要这样克隆?可以自己调试下
    if (/^(function|generatorfunction)$/i.test(type)) {
      return function proxy(...params) {
        return target.call(this, ...params);
      };
    }
    if (!isArray && !isObject) return target;
    result = isArray ? [] : {};
    each(target, function (copy, name) {
      if (deep) {
        // treated 解决循环引用他是第二层拷贝是传递的记录有没有出现过的数组标识
        result[name] = clone(deep, copy, treated);
        return;
      }
      result[name] = copy;
    });
    return result;
  };
​