JavaScript中物件的拷貝

smallbone發表於2018-12-13

淺拷貝

利用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

相關文章