js深淺複製

深淺複製對比


由於JavaScript存儲對象都是存地址的,因此淺複製會致使 obj 和obj1 指向同一塊內存地址。個人理解是,這有點相似數據雙向綁定,改變了其中一方的內容,都是在原來的內存基礎上作修改會致使拷貝對象和源對象都發生改變,而深複製通常都是開闢一塊新的內存地址,將原對象的各個屬性逐個複製出去。對拷貝對象和源對象各自的操做不影響另外一方javascript

代碼層面實現深淺複製


//數組拷貝

//淺複製,雙向改變,指向同一片內存空間
let arr = [1, 2, 3];
let arr1 = arr;
arr1[1] = 'test';
console.log('shallow copy: ' + arr + " " + arr1);   //shallow copy: 1,test,3 1,test,3

//深複製,開闢新的內存區

//方法一:slice,原理:slice返回一個新數組
let deepArr = [1, 2, 3];
let deepArr1 = deepArr.slice(0);
deepArr1[1] = 'test';
console.log('deep copy: ' + deepArr + " " + deepArr1);   //deep copy: 1,2,3 1,test,3

//方法二:concat,原理:concat返回一個新數組
let deepArr2 = [1, 2, 3];
let deepArr3 = deepArr2.concat();
deepArr3[1] = 'test';
console.log('deep copy: ' + deepArr2 + " " + deepArr3);   //deep copy: 1,2,3 1,test,3

//知乎看到的深複製方法,這個函數能夠深拷貝 對象和數組,很遺憾,對象裏的函數會丟失
deepCloneObj = obj => {
    let str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object') {
        return;
    }else if(window.JSON) {
        /*好處是很是簡單易用,可是壞處也顯而易見,會丟失不少東西,這會拋棄對象的constructor,
        也就是深複製以後,不管這個對象本來的構造函數是什麼,在深複製以後都會變成Object。
        另外諸如RegExp對象是沒法經過這種方式深複製的。
        */
        str = JSON.stringify(obj);
        newobj = JSON.parse(str);
        //console.log(JSON.parse(JSON.stringify(/[0-9]/)));
    }else {
        for(let i in obj) {
            newobj[i] = typeof obj[i] === 'object' ? deepCloneObj(obj[i]) : obj[i];
        }
    }
    return newobj;
}

let deepArr4 = {
    a: 1,
    b: 'test',
    c: [1, 2, 3],
    d: {
        'a': 'd:a',
        'b': 'd:b'
    }
}
deepArr5 = deepCloneObj(deepArr4);
deepArr5['a'] = 'testa';
console.log('deep copy: ' + JSON.stringify(deepArr4) + " " + JSON.stringify(deepArr5));
/*deep copy: {"a":1,"b":"test","c":[1,2,3],"d":{"a":"d:a","b":"d:b"}} 
{"a":"testa","b":"test","c":[1,2,3],"d":{"a":"d:a","b":"d:b"}}
*/

第三方庫實現深淺複製

1.jQuery.extend
第一個參數能夠是布爾值,用來設置是否深度拷貝的:java

jQuery.extend(true, { a : { a : "a" } }, { a : { b : "b" } } );
jQuery.extend( { a : { a : "a" } }, { a : { b : "b" } } );

下面是源碼,能夠看看jquery

jQuery.extend = jQuery.fn.extend = function() {
    var src, copyIsArray, copy, name, options, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
        deep = target;

        // skip the boolean and the target
        target = arguments[ i ] || {};
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
        target = {};
    }

    // extend jQuery itself if only one argument is passed
    if ( i === length ) {
        target = this;
        i--;
    }

    for ( ; i < length; i++ ) {
        // Only deal with non-null/undefined values
        if ( (options = arguments[ i ]) != null ) {
            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );

                // Don't bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
};

2.lodash —— _.clone() / _.cloneDeep()
在lodash中關於複製的方法有兩個,分別是_.clone()和_.cloneDeep()。其中_.clone(obj, true)等價於_.cloneDeep(obj)。使用上,lodash和jquery並無太大的區別,但看了源碼會發現, jQuery 不過60多行。可 lodash 中與深複製相關的代碼卻有上百行.jQuery 沒法正確深複製 JSON 對象之外的對象,lodash 花了大量的代碼來實現 ES6 引入的大量新的標準對象。更厲害的是,lodash 針對存在環的對象的處理也是很是出色的。所以相較而言,lodash 在深複製上的行爲反饋比jquery好不少,是更擁抱將來的一個第三方庫。數組

總結

綜上所述,數組的深拷貝比較簡單,方法沒有什麼爭議,對象的深拷貝,比較好的方法是用lodash的方法實現,或者遞歸實現,比較簡單的深複製可使用JSON.parse(JSON.stringify(obj))實現函數

參考資料

知乎:javascript中的深拷貝和淺拷貝

深刻剖析 JavaScript 的深複製oop

相關文章
相關標籤/搜索