JavaScript中对象的拷贝

217 阅读3分钟

浅拷贝

利用for循环遍历原始对象

将原始对象中的每个属性拷贝到目标对象上,但是可能存在性能问题

function shallowCopy(sourceObj) {
    let objCopy = {};
    let key;
    
    for (key in sourceObj) {
        if (sourceObj.hasOwnProperty(key)) {
            objCopy[key] = sourceObj[key];
        }
    }
    return objCopy;
}

const sourceObj = {
    a: 3,
    b: 5,
    c: {
        x: 7,
        y: 9
    }
}

console.log(shallowCopy(sourceObj));

使用Object.assign()方法

let obj = {
    a: 1,
    b: 2
}

let objCopy = Object.assign({}, obj);
console.log(objCopy);

使用ES6的扩展运算符...

let obj = {
    one: 1,
    two: 2
}

let newObj = {...obj};

浅拷贝存在的问题

  • 拷贝对象的原型链不会继承原始对象的原型链
  • 原始对象的属性描述符(数据属性,访问器属性)不会被拷贝
  • 只拷贝了可枚举的属性
  • 原始对象中内嵌的对象与拷贝后对象中的内嵌对象共享内存地址

深拷贝

使用JSON.parse(JSON.stringify(object))

它能正确处理的对象只有 Number, String, Boolean, Array 扁平对象,即那些能够被JSON直接表示的数据结构,比如Date对象就不适用此方法。

let obj = {
    a: 1,
    b: {
        c: 2
    }
}

let newObj = JSON.parse(JSON.stringify(obj));

obj.b.c = 20;
console.log(obj); // {a: 1, b: {c: 20}}
console.log(newObj); // {a: 1, b: {c: 2}}

缺点:

  • 无法拷贝用户定义的对象方法(函数),但是Object.assign()方法可以
  • JSON.parse(JSON.stringify(object))方法无法拷贝环对象(环对象指的是对象本身的属性之间相互引用),但是Object.assign()方法却可以浅拷贝循环引用的对象

使用第三方库

如Underscore的_.clone(),jQuery的$.clone()浅拷贝 / $.extend(true, {}, ...)可以实现深复制,lodash的_.clone() / _.cloneDeep(),其中lodash的兼容性更好一些

利用HTML5的history API

利用history.pushState()history.replaceState()两个方法都会为它们的第一个参数创建一个结构化的克隆,不过要注意的是因为这两个方法是同步的并且对浏览器历史记录的操作性能并不是很高,所以重复调用这两个方法可能会导致浏览器没有响应,影响用户体验

const structuredClone = obj => {
    const oldState = history.state;
    history.replaceState(obj, null);
    const cloneObj = history.state;
    history.replaceState(oldState, null);
    return cloneObj;
}

利用Notification API

当利用Notification构造函数创建通知时,构造函数也会为传入的数据创建一个结构化的克隆

const structuredClone = obj => {
    const n = new Notification("", {data: obj, slient: true});
    n.onshow = n.close.bind(n);
    return n.data;
}

使用Node.JS

Node.js自8.0.0版本之后提供了一个兼容结构化克隆的序列化API,但是这个API还在开发阶段,所以兼容性并不是很好

const v8 = require('v8');
const buf = v8.serialize({a: 'foo', b: new Date()});
const cloned = v8.deserialize(buf);
cloned.b.getMonth();

原生方法

stackoverflow上原生Javascript实现的一个简单的对象克隆函数:


function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");}

总结

深拷贝和浅拷贝区别:浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级深拷贝必须用到递归。从兼容性方面考虑,使用lodash的cloneDeep()是最佳的选择。

参考链接:

Copying Objects in JavaScript

javascript中浅拷贝和深拷贝的区别

深入剖析 JavaScript 的深复制

COPYING OBJECTS IN JAVASCRIPT