JavaScript-手寫優秀的拷貝

面試中,你們常常會遇到,面試官讓你講述什麼是深拷貝,什麼是淺拷貝,如何實現深拷貝,如何實現淺拷貝。這都是一下面試中常常遇到的問題。咱們若是不經能說出,還能寫出,那你就很叼了。 面試

1、什麼是淺拷貝?

建立一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。若是屬性是基本類型,拷貝的就是基本類型的值,若是屬性是引用類型,拷貝的就是引用類型的指針。若是原對象改變了這個指針,拷貝對象也會受影響。瀏覽器

將 B 對象拷貝到 A 對象中,但不包括 B 裏面的子對象。淺拷貝只複製一層對象的屬性,並不包括對象裏面的爲引用類型的數據。 bash

2、如何實現淺拷貝

2.1 Object.assign();

Object.assign();在不少的文章中,有說起到Object.assign()是一個深拷貝,你們請注意一下,它是一個淺拷貝。這裏稍微扯遠一點,在面試中,若是讓你實現一個Object.assign();咱們該怎麼作了。代碼以下。異步

  • 判斷瀏覽器是否支持Object.assign,不支持咱們在Object上綁定一個。
  • 目標值需不能爲空,不然進行報錯。避免存在值不是對象,咱們手動進行一次轉換。
  • 循環拷貝對象,對每個拷貝對象的屬性進行拷貝。
  • 返回目標值
  • Object.assign屬性是不可枚舉的,因此設置Object.assignMy enumerable爲false。
// 判斷是否支持assign屬性
if (Object.assign == null) {
    Object.defineProperty(Object, 'assignMy', {
        value: function(target) {
            if (target == null) {
                throw new TypeError('xxxx');
            }
            var newObj = Object(target);
            for (var index = 1; index < arguments.length; index++) {
                var nextSource = arguments[index];
                if (nextSource != null) {
                    for (var source in nextSource) {
                       if(Object.prototype.hasOwnProperty.call(nextSource, source)) {
                            newObj[source] = nextSource[source];
                        }
                    }
                }
            }
            return newObj;
        },
        writable: true,
        configurable: true,
        enumerable: false
    });
}
複製代碼

2.2 擴展運算符(...)

2.3 Array.prototype.slice() && Array.prototype.concat()

function list() {
    return Array.prototype.slice.call(arguments);
}
var m = {a: 1, b: 2, c: { d: 3 }};
var list1 = list(1, 2, 3, m); 
m.c.d = 5;
console.log(list1);
複製代碼

3、什麼是深拷貝

將一個對象衝內存中徹底的拷貝出來,分配一個新的內存區域存放新的對象,且修改對象不會影響到原來的對象。函數

將 B 對象拷貝到 A 對象中,包括 B 裏面的子對象。它不只將原對象的各個屬性逐個複製出去,並且將原對象各個屬性所包含的對象也依次採用深複製的方法遞歸複製到新對象上。post

4、如何實現深拷貝

4.1 Json.parse(JSON.stringify(xxxx))

  • 優勢:簡單、已使用。
  • 缺點:會忽略undefined、symbol。不能序列化函數。不能解決循環引用的問題。

4.2 消息通道messageChannel

function clone(obj) {
    return new Promise(resolve => {
          const {port1, port2} = new MessageChannel();
          port2.onmessage = ev => resolve(ev.data);
          port1.postMessage(obj); 
    });
}
複製代碼
  • 優勢:能解決undefined和循環應用的問題。
  • 缺點:是一個異步函數。

4.3 遞歸函數

function deepClone(target) {
    function isObj(o) {
        return (typeof o === 'object' || typeof o === 'function') && o !== null;
    }
    if (isObj(target)) {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = deepClone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
}
var target1 = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8],
	field5: function() {},
	field6: Symbol('field6')
};
console.time('clone');
var obj = deepClone(target1);
console.log(obj);
console.timeEnd('clone');
複製代碼

若是存在循環引用

  • 優勢:能解決undefined、Symbol和函數的問題。
  • 缺點:不能循環引用。

4.4優化遞歸函數

function deepClone(target, map = new Map()) {
    function isObj(o) {
        return (typeof o === 'object' || typeof o === 'function') && o !== null;
    }
    if (isObj(target)) {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        // 這裏還可使用while來提升性能
        for (const key in target) {
            cloneTarget[key] = deepClone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
}
var target1 = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
target1.target1 = target1;
console.time('clone');
var obj = deepClone(target1);
console.log(obj);
console.timeEnd('clone');
複製代碼

  • 優勢:能解決undefined、Symbol、函數的問題和循環引用問題。
  • 缺點:存在必定的性能問題。
相關文章
相關標籤/搜索