最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一文读懂深拷贝和浅拷贝

    正文概述 掘金(枭阳前端小哥)   2021-01-16   680

    浅拷贝的原理

    对于浅拷贝的定义,我们可以大致这么理解:

    1. 对于基本数据类型,浅拷贝就是将基本类型的值赋给新对象。
    2. 对于引用数据类型,浅拷贝就是将其内存中的引用赋值给新对象。

    浅拷贝的API

    在JavaScript中,有提供许多关于浅拷贝的API:

    1.“=” 赋值

    最基础的赋值方法,只是将对象的引用赋值。

    2. Object.assign

    Object.assgin是ES6的新函数。Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

    但是需要注意的是:

    • 它不会拷贝对象的继承属性
    • 它不会拷贝对象的不可枚举属性
    • 它可以拷贝Symbol类型的属性
    • 它可以处理一层的深度拷贝。
    let target = {};
    let source = { a: { b: 2 }, c: 2, sym: Symbol(1) };
    Object.defineProperty(source, 'innumerable' ,{
        value: '不可枚举属性',
        enumerable: false
    });
    Object.assign(target, source);
    console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
    source.a.b = 10;
    source.c = 10;
    console.log(source);             // { a: { b: 10 }, c: 10, sym: Symbol(1), innumerable: '不可枚举属性' }
    console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
    

    3. 扩展运算符

    我们也可以使用JS中的扩展运算符,在构造对象的同时完成浅拷贝功能。

    使用扩展运算符同样也存在Object.assign一样的缺陷。

    let source = { a: { b: 2 }, c: 2, sym: Symbol(1) };
    Object.defineProperty(source, 'innumerable' ,{
        value: '不可枚举属性',
        enumerable: false
    });
    let target = {...source};
    console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
    source.a.b = 10;
    source.c = 10;
    console.log(source);             // { a: { b: 10 }, c: 10, sym: Symbol(1), innumerable: '不可枚举属性' }
    console.log(target);             // { a: { b: 10 }, c: 2, sym: Symbol(1) }
    

    4. 对于数组来说,我们可以使用slice和concat实现拷贝的功能

    • slice

    slice通过控制两个参数来决定原数组截取的开始和结束位置,是不会影响和改变原数组的。

    let arr = [1, 2, {val: 4}];
    let newArr = arr.slice();
    newArr[2].val = 1000;
    console.log(arr);            // [ 1, 2, { val: 1000 } ]
    

    从上面的例子看出,slice也只能是处理一层的深度拷贝。如果存在对象的嵌套,它也无能为力。

    • concat

    concat表示将多个数组连接起来,从而实现浅拷贝的功能。但注意的是,当修改原数组中的元素属性时,会影响拷贝之后的数组。

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

    浅拷贝的手动实现

    对于浅拷贝方法的实现,大致有以下两点思路:

    1. 对于基础数据类型来说,就是一个最基本的拷贝
    2. 对于引用数据类型来说,需要开辟一个新的存储,并且拷贝一层对象属性。
    const shallowCopy = target => {
      if (typeof target === 'object' && target !== null) {
      	const cloneTarget = Array.isArray(target) ? [] : {};
        for (let key in target) {
          if (target.hasOwnProperty(key)) {
            cloneTarget[key] = target[key];
          }
        }
        return cloneTarget;
      } else {
      	return target;
      }
    };
    

    深拷贝的原理

    深拷贝应该满足以下几点:

    1. 将一个对象从内存中完整地拷贝出来,并从堆内存中开辟一个全新的空间存放。
    2. 两个对象之间相互独立不受影响,完全实现了内存上的分离。

    深拷贝的实现

    1. 使用JSON.stringify序列化

    用JSON.stringify把对象转成字符串,再用JSON.parse将字符串转成新的对象。

    var obj1 = { body: { a: 10 } };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    obj2.body.a = 20;
    console.log(obj1);                     // { body: { a: 10 } } <-- 沒被改到
    console.log(obj2);                     // { body: { a: 20 } }
    console.log(obj1 === obj2);            // false
    console.log(obj1.body === obj2.body);  // false
    

    但是使用这个方法有以下几点不足:

    1. 拷贝的对象中的值如果有函数、undefined、symbol这几种类型时,经过JSON.stringify序列化之后的字符串中会消失。
    2. 拷贝的对象中的值为Date时,会变成字符串。
    3. 无法拷贝不可枚举的属性。
    4. 无法拷贝对象的原型链。
    5. 拷贝的对象中的值为RegExp时,会变成空对象。
    6. 拷贝的对象中的值为NaN、Infinity或者-Infinity时,会变成null
    7. 无法拷贝对象的循环应用,即对象成环(obj[key] = obj);
    function Obj() {
      this.func = function() { alert(1); };
      this.obj = { a: 1 };
      this.arr = [1, 2, 3];
      this.reg = /123/;
      this.und = undefined;
      this.date = new Date(0);
      this.NaN = NaN;
      this.Infinity = Infinity;
      this.sym = Symbol(1);
    }
    let obj1 = new Obj();
    Object.defineProperty(obj1,'innumerable',{ 
      enumerable: false,
      value: 'innumerable'
    });
    console.log('obj1', obj1);
    let str = JSON.stringify(obj1);
    let obj2 = JSON.parse(str);
    console.log('obj2', obj2);
    

    打印出来的结果如下: 一文读懂深拷贝和浅拷贝

    2. 递归方式实现深拷贝

    const deepClone = (origin) => {
      let target = null; 
      if (typeof origin === "object" && origin !== null) {
        target = Array.isArray(origin) ? [] : {};
        for (let key in origin) {
          target[key] = deepClone(origin[key]);
        }
      } else {
        target = origin;
      }
      return target;
    }
    

    再次拿上面的例子做实验:

    function Obj() {
      this.func = function() { alert(1); };
      this.obj = { a: 1 };
      this.arr = [1, 2, 3];
      this.reg = /123/;
      this.und = undefined;
      this.date = new Date(0);
      this.NaN = NaN;
      this.Infinity = Infinity;
      this.sym = Symbol(1);
    }
    let obj1 = new Obj();
    Object.defineProperty(obj1,'innumerable',{ 
      enumerable: false,
      value: 'innumerable'
    });
    console.log('obj1', obj1);
    let obj2 = deepClone(obj1);
    console.log('obj2', obj2);
    

    一文读懂深拷贝和浅拷贝 根据结果,我们可以得出以下几点:

    • 这个深拷贝方法不可拷贝不可枚举的属性
    • 这个方法对于Date、RegExp不能够正确的拷贝
    • 无法拷贝对象的循环应用,即对象成环(obj[key] = obj);

    3.改进版深度拷贝

    要更好的实现一个深拷贝的方法,那么必须要了解以下四点:

    1. 对于不可枚举的属性,我们采用Reflect.ownKeys方法。
    2. 当参数为Date、RegExp类型时,则直接生成一个新的实例返回。
    3. 利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性。顺便结合Object.create创建对象,并继承传入原对象的原型链。
    4. 利用WeakMap类型作为哈希表,作为检测循环引用。如果存在循环,则引用直接返回WeakMap存储的值。
    const isComplexDataType = obj => (typeof obj === "object" || typeof obj === "function") && obj !== null;
    const deepClone = function(origin, hash = new WeakMap()) {
      if (origin.constructor === Date) return new Date(origin);
      if (origin.constructor === RegExp) return new RegExp(origin);
      // 如果循环引用就用WeakMap解决
      if (hash.has(origin)) return hash.get(origin);
      // 遍历传入参数所有键的特性
      let allDesc = Object.getOwnPropertyDescriptors(origin);
      // 继承原型链
      let cloneObj = Object.create(Object.getPrototypeOf(origin), allDesc);
      hash.set(origin, cloneObj);
      for (let key of Reflect.ownKeys(origin)) {
        cloneObj[key] = (isComplexDataType(origin[key]) && typeof origin[key] !== "function" ? deepClone(origin[key], hash) : origin[key];
      }
      return cloneObj;
    }
    

    起源地下载网 » 一文读懂深拷贝和浅拷贝

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元