likes
comments
collection
share

详谈深浅拷贝

作者站长头像
站长
· 阅读数 21

我们在开发过程中深浅拷贝可谓是经常需要使用,那么实现深浅拷贝的方法具体有哪些呢?他们各自的优势与劣势又是怎么样的呢?

浅拷贝

Object.assign

作为ES6新增的方法,它的作用就是合并对象,有点类似于数组的concat方法。但是Object.assign是有自己的特点的:

  • 它改变源对象,并返回新对象
  • 它只可以将目标对象的可枚举属性进行复制合并
  • 它不会合并继承属性
  • 可以复制Symbol类型的属性

所以我们看以下这段代码:

let obj1 = {
    name:'zs',
    love:['play','girl'],
    [Symbol(1)]:'Symbol'
}
Object.defineProperty(obj1,'noMerge',{
    value:'nonono',
    enumerable:false
})
let obj2 = Object.assign({},obj1)
console.log(obj1);
console.log(obj2);

我们来看结果:

详谈深浅拷贝

同时如果我们修改了obj2中的love,给它新增一个'eat',那么obj1和obj2将变成:

{
    name:'zs',
    love:['play','girl','eat'],
    ...
}

扩展运算符

展运算符(...)是ES6的语法,用于取出参数对象的所有可遍历属性,然后拷贝到当前对象之中。

  • 包括Symbol类型
  • 不包括继承来的属性
  • 包括可枚举属性
let obj1 = {
    name:'zs',
    love:['play','girl'],
    [Symbol(1)]:'Symbol'
}
Object.defineProperty(obj1,'noMerge',{
    value:'nonono',
    enumerable:false
})
let obj2 = {...obj1}
console.log(obj2)

现在来看结果:

详谈深浅拷贝

如果我们修改了obj2的love属性:

obj2.love.push('30')
console.log(obj1);

详谈深浅拷贝

数组的concat

数组的concat和对象的assign有点类似:

let arr = [1,2,{name:'zs'}]
let arr2 = [].concat(arr);
arr2[2].name='ls';
console.log(arr);

详谈深浅拷贝

数组的slice

数组的slice也是浅拷贝的:

let arr = [{name:'zs'},{name:'ls'},{name:'ww'}]
arr2 = arr.slice(1);
arr2[0].name='erer';
console.log(arr);

详谈深浅拷贝

深拷贝

JSON序列化

作为最简单的深拷贝方法,但是它有一些需要注意的地方:

  • 如果拷贝对象的值有函数、Symbol、undefined这几种类型,经过JSON.stringify序列化后的字符串中这个键值对会消失
  • 拷贝Date引用类型为变为字符串
  • 无法拷贝不可枚举的属性
  • 无法拷贝对象的原型链
  • 拷贝正则引用类型会变为空对象
  • 对象中含有NAN、infinity以及-infinity,JSON序列化的结果会变成null
  • 无法拷贝对象的循环引用

比如:

function Obj() {
    this.func = function(){console.log(1)};
    this.obj={a:1};
    this.arr=[1,2];
    this.und=undefined;
    this.nul=null;
    this.Nan=NaN;
    this.sym=Symbol(1);
    this.date = new Date();
    this.infi = Infinity;
    this.reg = /123/;
}
let obj1 = new Obj();
Object.defineProperty(obj1,'ennu',{
    enumerable:false,
    value:'ennu'
})
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1);
console.log(obj2);

详谈深浅拷贝

详谈深浅拷贝

递归实现(基本版)

function deepCopy(origin){
    if((typeof origin === 'object' || typeof origin === 'function' ) && origin!==null) {
        let target = Array.isArray(origin)?[]:{};
        for(let [key,value] of Object.entries(origin)){
            if(value instanceof Object) {
                target[key] = deepCopy(value)
            } else {
                target[key] = value;
            }
        }
        return target;
    }
}

虽然这种能实现深拷贝,且能应付日常开发中的大部分需求,但是它仍有一些地方需要注意:

  • Symbol类型的不能拷贝
  • 不能解决循环引用的问题
  • 不可枚举的属性不能拷贝(这个看需求,一般都不需要拷贝继承上面的属性)
  • 不能拷贝正则、日期等对象
let a = {
    name:'11',
    arr:[1,2,3,4],
    [Symbol(1)]:1,
    date:new Date(),
    reg:/123/
}
let b = deepCopy(a)

详谈深浅拷贝

改进版的递归实现深拷贝

function isComplexDataType(origin) {
    return ((typeof origin === 'object' || typeof origin === 'function') && origin!==null)
}

function deepCopy(origin,hash=new WeakMap()){
    if(origin.constructor === Date) {
        return new Date(origin)
    }
    if(origin.constructor === RegExp) {
        return new RegExp(origin)
    }
    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(key of Reflect.ownKeys(origin)) {
        cloneObj[key]=(isComplexDataType(origin[key])&&typeof origin[key]!=='function') ?
        deepCopy(origin[key],hash):origin[key]
    }
    return cloneObj;
}

let a = {
    name:'11',
    arr:[1,2,3,4],
    [Symbol(1)]:1,
    date:new Date(),
    reg:/123/
}
let b = deepCopy(a)

详谈深浅拷贝