javascript的浅克隆与深克隆

70 阅读2分钟

浅克隆表示只克隆引用对象的第一层,第二层后的对象依然使用原先对象的堆栈。比如 Object.assign , ... , slice

function a(){
    const obj = {
        foo : {
            bar : 'bar'
        }
    }

    const obj2 = Object.assign({} , obj)

    obj2.foo.bar = '1'

    console.log(obj);
}

a()  // { foo: { bar: '1' } }

function b(){
    const arr = [
        {
            bar : 'bar'
        }
    ]

    const arr2 = [...arr]
    arr2[0].bar = '1'
    console.log(arr);
}

b()  // [ { bar: '1' } ]

function c(){
    const arr = [
        {
            bar : 'bar'
        }
    ]

    const arr2 = arr.slice(0)
    arr2[0].bar = '1'
    console.log(arr);
}

c()  // [ { bar: '1' } ]

深克隆表示不使用任何原先的堆栈,比如 JSON.parse(JSON.stringify()) ,它将对象转换为字符串再解析为一个新的对象:

function a(){
    const obj = {
        foo : {
            bar : 'bar'
        }
    }

    const obj2 = JSON.parse(JSON.stringify(obj))

    obj2.foo.bar = '1'

    console.log(obj);
}

a()  // { foo: { bar: 'bar' } }

手写一个深克隆方法是一个经典的面试题,主要考察递归算法:

function deepClone(val) {

    if (typeof val !== 'object') {
        return val
    }

    const res = {}

    for (let key in val) {
        res[key] = deepClone(val[key])
    }
    return res;
}

const obj = {
    foo: {
        bar: 'bar'
    }
}

const obj2 = deepClone(obj)

obj2.foo.bar = '1'

console.log(obj2); // { foo: { bar: '1' } }
console.log(obj); // { foo: { bar: 'bar' } }

上面这个例子是有问题的,它涉及到这个考题的另一个考点:原型链。还是刚才这个例子,如果我们要深克隆的 obj 对象的原型链上存在一个对象 {a : '1'} ,当克隆完成后,原型链上的对象会被转换为普通对象:

const obj = {
    foo: {
        bar: 'bar'
    }
}

Object.setPrototypeOf(obj, { a: '1' })

const obj2 = deepClone(obj)

obj2.foo.bar = '1'

console.log(obj2); // { foo: { bar: '1' }, a: '1' }
console.log(obj); // { foo: { bar: 'bar' } }

之所以会出现这种情况是因为 for in 方法不会区分是否为原型链上的元素,原型链上的 {a : '1'} 同样具有一个迭代器,它会被 for in 方法处理。

为了不处理原型链上的元素,我们需要对被 for in 遍历的元素加上一些判断,Object.prototype.hasOwnProperty() 方法帮助我们判断某个属性是否是该对象自身持有的属性,而非从原型链上继承来的属性:

function deepClone(val) {

    if (typeof val !== 'object') {
        return val
    }

    const res = {}

    for (let key in val) {
        if(val.hasOwnProperty(key)){
            res[key] = deepClone(val[key])
        }
    }
    return res;
}

现在我们的实例可以正常通过了:

function deepClone(val) {

    if (typeof val !== 'object') {
        return val
    }

    const res = {}

    for (let key in val) {
        if(val.hasOwnProperty(key)){
            res[key] = deepClone(val[key])
        }
    }
    return res;
}

const obj = {
    foo: {
        bar: 'bar'
    }
}

Object.setPrototypeOf(obj, { a: '1' })

const obj2 = deepClone(obj)

obj2.foo.bar = '1'

console.log(obj2); // { foo: { bar: '1' }}
console.log(obj); // { foo: { bar: 'bar' } }

参考:

JS深克隆浅克隆