1

浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
深拷贝:会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

浅拷贝的实现方式

直接赋值一个变量

  let obj = {username: 'kobe', age: 39, sex: {option1: '男', option2: '女'}};
  let obj1 = obj;
  obj1.sex.option1 = '不男不女'; // 修改复制的对象会影响原对象
  console.log(obj1, obj);

alt text

Object.assign()

let obj = {
    username: 'kobe'
    };
let obj2 = Object.assign(obj);
obj.username = 'wade';
console.log(obj2);//{username: "wade"}

Array.prototype.concat()

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

修改新对象会改到原对象:

alt text

Array.prototype.slice()

let arr = [1, 3, {
    username: ' kobe'
    }];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);

同样修改新对象会改到原对象:

alt text

关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。详细规则请看MDN对应函数讲解。

深拷贝的实现方式

只有值类型数据的深拷贝

针对只有值的数据对象,下面一行代码足以!

JSON.parse(JSON.stringify(obj))

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法只能处理只有值类型数据的拷贝。

不严谨的简单的深拷贝

function clone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
             //通过hasOwnProperty方法来进行筛选,所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。
            if (typeof source[i] === 'object') {
                target[i] = clone(source[i]); // 注意这里
            } else {
                target[i] = source[i];
            }
        }
    }

    return target;
}

问题存在:

  • 没有对参数做检验
  • 判断是否对象的逻辑不够严谨
  • 没有考虑数组的兼容

进阶深拷贝

function isObj(obj)
{
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}

function deepCopy(obj)
{
    let tempObj = Array.isArray(obj) ? [] :{};
    for(let key in obj)
    {
        tempObj[key] = isObj(obj[key]) ? deepCopy(obj[key]) : obj[key];
    }
    return tempObj;
}

问题存在:

  • __拷贝环__,也就是对 对象循环引用 的拷贝出现问题

对象环知识点补充

方法一 用try catch的捕获异常的方法来判断,代码简洁

function cycleDetector (obj) {
    console.log(arguments)
  // 请添加代码
    let result = false;
    try {
        JSON.stringify(obj);
    } catch (e) {
        result = true;
    } finally {
        return result;
    }
}

方法二 时间更快,但是它执行递归,逻辑较第一种更复杂,空间也需要更大

function cycleDetector2(obj) {
    let hasCircle = false,
        cache = [];

    (function(obj) {
        Object.keys(obj).forEach(key => {
            const value = obj[key]
            if (typeof value == 'object' && value !== null) {
                const index = cache.indexOf(value)
                if (index !== -1) {
                    hasCircle = true
                    return
                } else {
                    cache.push(value)
                    arguments.callee(value)
                    // (注:箭头函数没有arguments对象,此时的arguments指向该匿名函数的参数)
                }
            }
        })
    })(obj)

    return hasCircle
}

针对环的深拷贝

可以使用一个WeakMap结构存储已经被拷贝的对象,每一次进行拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回,将deepCopy函数改造成如下:

function isObj(obj)
{
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj, hash = new WeakMap()) {    
    if(hash.has(obj)) return hash.get(obj)    
    let cloneObj = Array.isArray(obj) ? [] : {}
    hash.set(obj, cloneObj)    
    for (let key in obj) {
        cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
    }   
    return cloneObj
}

问题存在:

  • 没有考虑对new Date(),正则,函数类型的对象的拷贝

结合环,针对date,reg,箭头函数类型的深拷贝


const obj = {    arr: [111, 222],    obj: {key: '对象'},    a: () => {console.log('函数')},    date: new Date(),    reg: /正则/ig}

function isObj(obj)
{
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
function deepCopy(obj, hash = new WeakMap()) {  
      let cloneObj;   
      let Constructor = obj.constructor; 
      switch(Constructor){       
        case RegExp:
            cloneObj = new Constructor(obj)         
            break;
        case Date:
            cloneObj = new Constructor(obj.getTime())           
            break;
        case Function:
            cloneObj = eval(obj.toString());
            break;
        default:           
         if(hash.has(obj)) return hash.get(obj)
            cloneObj = new Constructor()
            hash.set(obj, cloneObj)
      }   

      for (let key in obj) {
            cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
      }    
      return cloneObj;
}

const cloneObj = deepCopy(obj);
console.log(cloneObj);

前端技术精髓
1.8k 声望760 粉丝