zoukankan      html  css  js  c++  java
  • module1-02-JS深浅拷贝

    JS深浅拷贝---代码基本功能则是(下)

    • 在JS编程中经常需要对数据进行复制,对于什么时候用深拷贝,什么时候用钱开呗是值得我们思考的一个问题

    • 学习好这一内容有利于

      • 提高手撕JS代码能力

      • 提高对边界特殊情况的深入思考

    • 提出两个问题让我们思考以下

      • ① 拷贝一个很多嵌套的对象怎么实现?

      • ② 在面试官眼中,写成什么样的深拷贝代码才合格

    一、浅拷贝

    • 直接用=操作符来获取一个对象,如果目标是基本数据类型,则复制的就是该对象的值;如果复制的是引用数据类型,则复制的是该对象的地址,目标对象跟复制出来的对象任一改变其中的值,则两边都会改变。

    • 了解完浅拷贝的定义之后,接下来列举以下用以浅拷贝的方法

    1.1 Object.assign

    • 语法: Object.assign(target, ...sources)

      • 其中target是目标对象,存储复制之后的值的对象

      • sources表示可以为多个的待被拷贝对象

    (1)用Object.assign()来完成一次浅拷贝

    let target = { b: 1};
    let source = { a: { b: 1 } };

    Object.assign(target, source);
    console.log(target); // { a: {b: 1}, b: 1 };
    • 如果期间修改了值

    const target = {};
    const source = { a: { b: 1 } };

    Object.assign(target, source); // { a: { b: 5 } };

    console.log(target); // { a: { b: 5 } };
    source.a.b = 5;
    console.log(target); // { a: { b: 5 } };

    (2)优缺点

    • 不会拷贝对象的继承属性

    • 不会拷贝对象的不可枚举属性

    • 可以拷贝Symbol类型的属性

    (3)验证其特性

    let obj1 = { a: { b: 1 }, [Symbol(1)]: sym}; 
    Object.defineProperty(obj1, 'innumerable' ,{
       value: '不可枚举属性',
       enumerable: false
    });
    let obj2 = {};
    Object.assign(obj2,obj1)
    obj1.a.b = 2;
    console.log('obj1',obj1);
    console.log('obj2',obj2);
    • 得出来的结果

    1.2 扩展运算符方式

    • 其结果跟assign相似

    1.3 concat拷贝数组

    • concat也是浅拷贝

    1.4 slice拷贝数组

    • slice也是浅拷贝

    1.5 自己实现的一个浅拷贝

    const shallowClone = (target) => {
    if (typeof target !== 'object' || target === null) return target;
    const cloneTarget = Array.isArray(target) ? [] : {};
    for (const key in target) {
    if (target.hasOwnProperty(key)) {
    cloneTarget[key] = target[key]
    }
    }
    return cloneTarget
    }

    二、深拷贝

    • 浅拷贝只是创建了一个新的对象,复制了原有对象的基本类型的值

    • 深拷贝需要在堆内存中完全开闭了一块内存地址,并将原有的对象完全复制过来存放,且新旧对象的修改并不会改变双方对象,二者实现真正的分离

    • 接下来讲述以下深拷贝的各种方法

    2.1 JSON.stringify

    • 是目前开发过程中最简单的深拷贝方法

    let obj1 = { a:1, b:[1,2,3] }
    let str = JSON.stringify(obj1)
    let obj2 = JSON.parse(str)
    console.log(obj2);   //{a:1,b:[1,2,3]}
    obj1.a = 2
    obj1.b.push(4);
    console.log(obj1);   //{a:2,b:[1,2,3,4]}
    console.log(obj2);   //{a:1,b:[1,2,3]}

    该方法存在的问题

    • ① 拷贝的对象的值中如果有函数、undefined、symbol这几种类型,经过JSON.stringify序列化之后的字符串中这个键值对会消失

    • ② 拷贝Date引用类型会变成字符串

    • 无法拷贝不可枚举的属性

    • 无法拷贝对象的原型链

    • ⑤ 拷贝RegExp引用类型会变成空对象

    • ⑥ 对象中含有NaN、Infinity、以及-Inifinity,JSON序列化的结果会变成null

    • 无法拷贝对象的循环应用即对象成环(obj[key] = obj)

    验证存在的问题

    function Obj() { 
     this.func = function () { alert(1) };
     this.obj = {a:1};
     this.arr = [1,2,3];
     this.und = undefined;
     this.reg = /123/;
     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.2 基础手写版

    • 实现一个deepClone封装的例子,通过for in遍历传值,如果还是引用类型则再次调用该函数来递归,如果是基础类型就直接返回

    let obj1 = {
     a:{
       b:1
    }
    }
    function deepClone(obj) {
     let cloneObj = {}
     for(let key in obj) {                 //遍历
       if(typeof obj[key] ==='object') {
         cloneObj[key] = deepClone(obj[key])  //是对象就再次调用该函数递归
      } else {
         cloneObj[key] = obj[key]  //基本类型的话直接复制值
      }
    }
     return cloneObj
    }
    let obj2 = deepClone(obj1);
    obj1.a.b = 2;
    console.log(obj2);   // {a:{b:1}}
    • 这样能基本实现普通需求的深拷贝,但是不够严谨,主要在于以下几点

      • ① 这个深拷贝函数并不能复制不可枚举属性以及Symbol类型

      • ② 这种方法只是针对普通的引用类型的值做递归复制

      • ③ 对象的属性里面成环,即循环引用没有解决

    2.3 严谨手写版

    • 改进的方法

      • 针对能够遍历对象的不可枚举属性以及Symbol类型我们可以使用Reflect.ownKeys方法

      • ② 当参数为Date、RegExp类型,则直接生成一个新的实例返回

      • ③ 利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性,顺便结合Object的create方法创建一个新的对象,并继承传入原对象的原型链

      • ④ 利用WeakMap类型作为Hash表,因为WeakMap是弱引用类型,可以有效防止内存泄漏,作为检测循环引用很有帮助,如果存在循环,则引用直接返回WeakMap存储的值

    • 最终代码

    const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
    const deepClone = function (obj, hash = new WeakMap()) {
     if (obj.constructor === Date)
     return new Date(obj)       // 日期对象直接返回一个新的日期对象
     if (obj.constructor === RegExp)
     return new RegExp(obj)     //正则对象直接返回一个新的正则对象
     //如果循环引用了就用 weakMap 来解决
     if (hash.has(obj)) return hash.get(obj)
     let allDesc = Object.getOwnPropertyDescriptors(obj)
     //遍历传入参数所有键的特性
     let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
     //继承原型链
     hash.set(obj, cloneObj)
     for (let key of Reflect.ownKeys(obj)) {
       cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
    }
     return cloneObj
    }
    // 下面是验证代码
    let obj = {
     num: 0,
     str: '',
     boolean: true,
     unf: undefined,
     nul: null,
     obj: { name: '我是一个对象', id: 1 },
     arr: [0, 1, 2],
     func: function () { console.log('我是一个函数') },
     date: new Date(0),
     reg: new RegExp('/我是一个正则/ig'),
    [Symbol('1')]: 1,
    };
    Object.defineProperty(obj, 'innumerable', {
     enumerable: false, value: '不可枚举属性' }
    );
    obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
    obj.loop = obj    // 设置loop成循环引用的属性
    let cloneObj = deepClone(obj)
    cloneObj.arr.push(4)
    console.log('obj', obj)
    console.log('cloneObj', cloneObj)
    • 得出来的结果

    2.4 Q&A

    • 为什么new Date(obj) / new RegExp(obj) 就可以返回一个新的一样的日期 / 正则对象

      • 因为其表现形式是字符串,根据构造函数所需要传入的参数就是字符串,所以有以上结果

    • 为什么要使用WeakMap来实现内环循环引用

      • ea6文档

      • 因为weakMap是弱引用类型能防止内存泄漏,作为检测循环引用很有帮助,如果存在循环,则引用直接返回WeakMap存储的值

    • Object.create的作用是什么

      • MDN文档

      • 第一个参数是原型对象

      • 第二个参数是参数的描述属性

      • 返回一个浅拷贝的新对象

      • 这里面包含用到的 Reflect.getPrototypeOf() Object.getOwnPropertyDescriptors()

        • 分别是获取其 原型对象 和 参数的描述属性

  • 相关阅读:
    Fix the Package System is Broken error in Ubuntu
    linux源镜像网站
    VS2010快捷键大全
    自定义函数与存储过程的比较
    vbcr,vblf和 vbcrlf之间的区别?
    GridView序号问题
    Outlook2007设置备份账号、联系人和个性签名的方法
    c#.net4.0利用odac连接oracle取数
    无法打开登录所请求的数据库DbName 。登录失败。 用户 'IIS APPPOOL\DefaultAppPool' 登录失败。 的解决方案
    C#开发和调用Web Service
  • 原文地址:https://www.cnblogs.com/lezaizhu/p/14422790.html
Copyright © 2011-2022 走看看