JS-浅拷贝-深拷贝

119 阅读5分钟

浅拷贝

①Object.assign()

  • Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
  •   var obj = { a: {a: "kobe", b: 39} };
      var initalObj = Object.assign({}, obj)
    

②Array.prototype.concat()

  •   let arr = [1, 3, {username: 'kobe'}];
      let arr2=arr.concat();  
    

③Array.prototype.slice()

  •   let arr = [1, 3, {username: 'kobe'}];
      let arr2=arr.slice(); 
    

②③补充:

  • 如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也发生改变
  • 对于字符串数字布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组

深拷贝-JSON.parse(JSON.stringify())

  •  let arr = [1, 3, { username: ' kobe'}];
     let arr4 = JSON.parse(JSON.stringify(arr)); 
    
  • 原理

    • JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数
  • const s1 = Symbol();
    const s2 = Symbol();
    
    const obj = {
        name: "zzy",
        age: 22,
        friends: {
            name: "kobe",
        },
        foo: function () {
            console.log("foo");
        },
        // Symbol既可以作为key,也可以作为value
        [s1]: "s1",
        [s2]: s2,
    };
    
    // 不可以循环引用 无法通过JSON进行深拷贝,会报错
    // 不能将循环结构转成JSON
    // obj.inner = obj;
    
    const info = JSON.parse(JSON.stringify(obj));
    // 深拷贝 创建出全新的对象
    console.log(info === obj); // false
    // 不会影响info对象的引用
    obj.friends.name = "james";
    console.log(info.friends.name); // kobe
    // 不会对函数进行处理
    console.log(info.foo); // undefined
    // 不会对Symbol进行处理
    console.log(obj[s1]); // s1
    console.log(info[s1]); // undefined
    

自定义深拷贝

image.png

基本实现

  • 
    function isObject(value) {
        const valueType = typeof value
        return (value !== null) && (valueType === 'object' || valueType === 'function')
    }
    
    function deepClone(originValue) {
        // 判断传入的originValue是否是一个对象类型
        if (!isObject(originValue)) {
            return originValue
        }
        const newObj = {}
        for(key in originValue) {
            newObj[key] = deepClone(originValue[key])
        }
        return newObj
    }
    
    const obj = {
        name: 'zzy',
        age: 22,
        friends: {
            name: 'kobe'
        }
    }
    
    const newObj = deepClone(obj)
    console.log(newObj === obj); // false
    
    obj.friends.name = 'james'
    console.log(newObj.friends.name); // kobe 
    

其他类型

  • function isObject(value) {
        const valueType = typeof value;
        return value !== null && (valueType === "object" || valueType === "function");
    }
    
    function deepClone(originValue) {
        // 判断是否是一个Set类型
        if (originValue instanceof Set) {
            return new Set([...originValue]);
        }
    
        // 判断是否是一个Map类型
        if (originValue instanceof Map) {
            return new Map([...originValue]);
        }
    
        // 如果是Symbol的value,创建一个新的Symbol
        if (typeof originValue === "symbol") {
            return Symbol(originValue.description);
        }
    
        // 如果是一个函数类型,直接使用同一个函数
        if (typeof originValue === "function") {
            return originValue;
        }
    
        // 判断传入的originValue是否是一个对象类型
        if (!isObject(originValue)) {
            return originValue;
        }
        // 判断传入的originValue是否是一个数组类型
        const newObj = Array.isArray(originValue) ? [] : {};
        for (key in originValue) {
            newObj[key] = deepClone(originValue[key]);
        }
    
        // 对Symbol的key进行处理
        const symbolKeys = Object.getOwnPropertySymbols(originValue);
        for (const sKey of symbolKeys) {
            // 克隆出来的对象的Symbol类型的key也可以不一样,没啥必要
            // const newSKey = Symbol(sKey.description)
            // newObj[newSKey] = deepClone(originValue[sKey])
            newObj[sKey] = deepClone(originValue[sKey]);
        }
    
        return newObj;
    }
    
    let s1 = Symbol("aa");
    let s2 = Symbol("bb");
    
    const obj = {
        name: "zzy",
        age: 22,
        friends: {
            name: "kobe",
        },
        // 数组类型
        hobbies: ["eat", "run", "ring"],
        // 函数类型
        foo: function () {
            console.log("foo!");
        },
        // Symbol类型作为key和value
        [s1]: "s1",
        s2: s2,
        // Set/Map类型
        set: new Set(["a", "b", "c"]),
        map: new Map([
            ["aaa", "bbb"],
            ["ccc", "ddd"],
        ]),
    };
    
    const newObj = deepClone(obj);
    console.log(newObj === obj); // false
    
    obj.friends.name = "james";
    console.log(newObj.friends.name); // kobe
    
    console.log(newObj); // { '0': 'eat', '1': 'run', '2': 'ring' } =》[ 'eat', 'run', 'ring' ]
    
    console.log(obj.s2 == newObj.s2); // false
    

循环引用

  • function isObject(value) {
        const valueType = typeof value;
        return value !== null && (valueType === "object" || valueType === "function");
    }
    
    function deepClone(originValue, map = new WeakMap()) {
        // 判断是否是一个Set类型
        if (originValue instanceof Set) {
            return new Set([...originValue]);
        }
    
        // 判断是否是一个Map类型
        if (originValue instanceof Map) {
            return new Map([...originValue]);
        }
    
        // 如果是Symbol的value,创建一个新的Symbol
        if (typeof originValue === "symbol") {
            return Symbol(originValue.description);
        }
    
        // 如果是一个函数类型,直接使用同一个函数
        if (typeof originValue === "function") {
            return originValue;
        }
    
        // 判断传入的originValue是否是一个对象类型
        if (!isObject(originValue)) {
            return originValue;
        }
    
        if(map.has(originValue)) {
            return map.get(originValue)
        }
    
        // 判断传入的originValue是否是一个数组类型
        const newObj = Array.isArray(originValue) ? [] : {};
        map.set(originValue, newObj)
        for (key in originValue) {
            newObj[key] = deepClone(originValue[key], map);
        }
    
        // 对Symbol的key进行处理
        const symbolKeys = Object.getOwnPropertySymbols(originValue);
        for (const sKey of symbolKeys) {
            // 克隆出来的对象的Symbol类型的key也可以不一样,没啥必要
            // const newSKey = Symbol(sKey.description)
            // newObj[newSKey] = deepClone(originValue[sKey])
            newObj[sKey] = deepClone(originValue[sKey], map);
        }
    
        return newObj;
    }
    
    let s1 = Symbol("aa");
    let s2 = Symbol("bb");
    
    const obj = {
        name: "zzy",
        age: 22,
        friends: {
            name: "kobe",
        },
        // 数组类型
        hobbies: ["eat", "run", "ring"],
        // 函数类型
        foo: function () {
            console.log("foo!");
        },
        // Symbol类型作为key和value
        [s1]: "s1",
        s2: s2,
        // Set/Map类型
        set: new Set(["a", "b", "c"]),
        map: new Map([
            ["aaa", "bbb"],
            ["ccc", "ddd"],
        ]),
    };
    
    obj.info = obj
    
    const newObj = deepClone(obj);
    console.log(newObj === obj); // false
    
    obj.friends.name = "james";
    console.log(newObj.friends.name); // kobe
    
    console.log(newObj); // { '0': 'eat', '1': 'run', '2': 'ring' } =》[ 'eat', 'run', 'ring' ]
    
    console.log(obj.s2 == newObj.s2); // false
    

递归-深拷贝

  • 递归方法实现深度克隆原理:

    • 遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
  •   //定义检测数据类型的功能函数
          function checkedType(target) {
            return Object.prototype.toString.call(target).slice(8, -1)
          }
          //实现深度克隆---对象/数组
          function clone(target) {
            //判断拷贝的数据类型
            //初始化变量result 成为最终克隆的数据
            let result, targetType = checkedType(target)
            if (targetType === 'object') {
              result = {}
            } else if (targetType === 'Array') {
              result = []
            } else {
              return target
            }
            //遍历目标数据
            for (let i in target) {
              //获取遍历数据结构的每一项值。
              let value = target[i]
              //判断目标结构里的每一值是否存在对象/数组
              if (checkedType(value) === 'Object' ||
                checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
                //继续遍历获取到value值
                result[i] = clone(value)
              } else { //获取到value值是基本的数据类型或者是函数。
                result[i] = value;
              }
            }
            return result
          }
    

函数库-lodash

  • 该函数库也有提供_.cloneDeep用来做 Deep Copy

  •   var _ = require('lodash');
      var obj1 = {
          a: 1,
          b: { f: { g: 1 } },
          c: [1, 2, 3]
      };
      var obj2 = _.cloneDeep(obj1);