App下載

JavaScript如何實(shí)現(xiàn)深拷貝

猿友 2020-08-03 16:05:50 瀏覽數(shù) (10497)
反饋

JavaScript 開發(fā)工作中,我們經(jīng)常會(huì)碰到需要進(jìn)行深拷貝的情況,而且在面試中也經(jīng)常會(huì)問到這個(gè)問題,那么什么是淺拷貝,什么是深拷貝?

什么是淺拷貝

關(guān)于淺拷貝的概念,我在網(wǎng)上看到一種說法,直接上代碼。

var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var person1 = person;       //他們認(rèn)為這是淺拷貝

但是我個(gè)人認(rèn)為,上面這個(gè)根本不涉及拷貝,只是一個(gè)簡單的引用賦值。以我的理解,淺拷貝應(yīng)該是不考慮對(duì)象的引用類型的屬性,只對(duì)當(dāng)前對(duì)象的所有成員進(jìn)行拷貝,代碼如下:

function copy(obj){
    var objCopy = {};
    for(var key in obj){
        objCopy[key] = obj[key];
    }
    return objCopy;
}


var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}};
var personCopy = copy(person);

上面這段代碼中,person對(duì)象擁有兩個(gè)基本類型的屬性nameage,一個(gè)引用類型的屬性car,當(dāng)使用如上方法進(jìn)行拷貝的時(shí)候,nameage屬性會(huì)被正常的拷貝,但是car屬性,只會(huì)進(jìn)行引用的拷貝,這樣會(huì)導(dǎo)致拷貝出來的對(duì)象personCopyperson會(huì)共用一個(gè)car對(duì)象。這樣就是所謂的淺拷貝。

什么是深拷貝

深拷貝的就是在拷貝的時(shí)候,需要將當(dāng)前要拷貝的對(duì)象內(nèi)的所有引用類型的屬性進(jìn)行完整的拷貝,也就是說拷貝出來的對(duì)象和原對(duì)象之間沒有任何數(shù)據(jù)是共享的,所有的東西都是自己獨(dú)占的一份。

如何實(shí)現(xiàn)深拷貝

實(shí)現(xiàn)深拷貝需要考慮如下幾個(gè)因素:

  • 傳入的對(duì)象是使用對(duì)象字面量{}創(chuàng)建的對(duì)象還是由構(gòu)造函數(shù)生成的對(duì)象

  • 如果對(duì)象是由構(gòu)造函數(shù)創(chuàng)建出來的,那么是否要拷貝原型鏈上的屬性

  • 如果要拷貝原型鏈上的屬性,那么如果原型鏈上存在多個(gè)同名的屬性,保留哪個(gè)

  • 處理循環(huán)引用的問題

第三方庫實(shí)現(xiàn)深拷貝

jQuery的$.extend()

我們可以通過$.extend()方法來完成深復(fù)制。值得慶幸的是,我們?cè)?code>jQuery中可以通過添加一個(gè)參數(shù)來實(shí)現(xiàn)遞歸extend。調(diào)用$.extend(true, {}, ...)就可以實(shí)現(xiàn)深復(fù)制,參考下面的例子:

var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};


var y = $.extend({}, x),          //shallow copy
    z = $.extend(true, {}, x);    //deep copy


y.b.f === x.b.f       // true
z.b.f === x.b.f       // false

但是jQuery的這個(gè)$.extend()方法,有弊端,什么弊端呢?我們看下面的例子:

var objA = {};
var objB = {};


objA.b = objB;
objB.a = objA;


$.extend(true,{},a);


//這個(gè)時(shí)候就出現(xiàn)異常了
//Uncaught RangeError: Maximum call stack size exceeded(…)

也就是說,jQuery中的$.extend()并沒有處理循環(huán)引用的問題。

使用JSON對(duì)象實(shí)現(xiàn)深拷貝

使用JSON全局對(duì)象的parsestringify方法來實(shí)現(xiàn)深復(fù)制也算是一個(gè)簡單討巧的方法。

function jsonClone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
var clone = jsonClone({ a:1 });

然而使用這種方法會(huì)有一些隱藏的坑,它能正確處理的對(duì)象只有 Number, String, Boolean, Array, 扁平對(duì)象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)。

自己造輪子

下面我們給出一個(gè)簡單的解決方案,當(dāng)然這個(gè)方案是參考別人的方式來實(shí)現(xiàn)的。希望對(duì)大家有用。

var clone = (function() {
    //這個(gè)方法用來獲取對(duì)象的類型 返回值為字符串類型 "Object RegExp Date Array..."
    var classof = function(o) {
        if (o === null) {
            return "null";
        }
        if (o === undefined) {
            return "undefined";
        }
        // 這里的Object.prototype.toString很可能用的就是Object.prototype.constructor.name
        // 這里使用Object.prototype.toString來生成類型字符串
        var className = Object.prototype.toString.call(o).slice(8, -1);
        return className;
    };


    //這里這個(gè)變量我們用來存儲(chǔ)已經(jīng)保存過的屬性,目的在于處理循環(huán)引用的問題
    var references = null;


    //遇到不同類型的對(duì)象的處理方式
    var handlers = {
        //正則表達(dá)式的處理
        'RegExp': function(reg) {
            var flags = '';
            flags += reg.global ? 'g' : '';
            flags += reg.multiline ? 'm' : '';
            flags += reg.ignoreCase ? 'i' : '';
            return new RegExp(reg.source, flags);
        },
        //時(shí)間對(duì)象處理
        'Date': function(date) {
            return new Date(+date);
        },
        //數(shù)組處理 第二個(gè)參數(shù)為是否做淺拷貝
        'Array': function(arr, shallow) {
            var newArr = [],
            i;
            for (i = 0; i < arr.length; i++) {
                if (shallow) {
                    newArr[i] = arr[i];
                } else {
                    //這里我們通過reference數(shù)組來處理循環(huán)引用問題
                    if (references.indexOf(arr[i]) !== -1) {
                        continue;
                    }
                    var handler = handlers[classof(arr[i])];
                    if (handler) {
                        references.push(arr[i]);
                        newArr[i] = handler(arr[i], false);
                    } else {
                        newArr[i] = arr[i];
                    }
                }
            }
            return newArr;
        },
        //正常對(duì)象的處理 第二個(gè)參數(shù)為是否做淺拷貝
        'Object': function(obj, shallow) {
            var newObj = {}, prop, handler;
            for (prop in obj) {
                //關(guān)于原型中屬性的處理太過復(fù)雜,我們這里暫時(shí)不做處理
                //所以只對(duì)對(duì)象本身的屬性做拷貝
                if (obj.hasOwnProperty(prop)) {
                    if (shallow) {
                        newObj[prop] = obj[prop];
                    } else {
                        //這里還是處理循環(huán)引用的問題
                        if (references.indexOf(obj[prop]) !== -1) {
                            continue;
                        }


                        handler = handlers[classof(obj[prop])];
                        //如果沒有對(duì)應(yīng)的處理方式,那么就直接復(fù)制
                        if (handler) {
                            references.push(obj[prop]);
                            newObj[prop] = handler(obj[prop], false);
                        } else {
                            newObj[prop] = obj[prop];
                        }
                    }
                }
            }
            return newObj;
        }
    };


    return function(obj, shallow) {
        //首先重置我們用來處理循環(huán)引用的這個(gè)變量
        references = [];
        //我們默認(rèn)處理為淺拷貝
        shallow = shallow === undefined ? true : false;
        var handler = handlers[classof(obj)];
        return handler ? handler(obj, shallow) : obj;
    };
}());


(function() {
    //下面是一些測試代碼
    var date = new Date();
    var reg = /hello word/gi;
    var obj = {
        prop: 'this ia a string',
        arr: [1, 2, 3],
        o: {
            wow: 'aha'
        }
    };
    var refer1 = {
        arr: [1, 2, 3]
    };
    var refer2 = {
        refer: refer1
    };
    refer1.refer = refer2;


    var cloneDate = clone(date, false);
    var cloneReg = clone(reg, false);
    var cloneObj = clone(obj, false);
    alert((date !== cloneDate) && (date.valueOf() === cloneDate.valueOf()));
    alert((cloneReg !== reg) && (reg.toString() === cloneReg.toString()));
    alert((obj !== cloneObj) && (obj.arr !== cloneObj.arr) && (obj.o !== cloneObj.o) && (JSON.stringify(obj) === JSON.stringify(cloneObj)));


    clone(refer2, false);
    alert("I'm not dead yet!");
    // Output:
    // true
    // true
    // true
    // I'm not dead yet!
}());

以上就是關(guān)于JavaScript拷貝的一些知識(shí)了,希望對(duì)大家有所幫助,對(duì)JavaScript有興趣的同學(xué)可以看一下教程

JavaScript教程:http://www.o2fo.com/javascript/

JavaScript微課:http://www.o2fo.com/minicourse/play/jscourse

2 人點(diǎn)贊