關於Javascript對象拷貝實現及疑問執行個體

來源:互聯網
上載者:User

在實際編程過程,有時候我們會遇到一種情況:當你有一個對象A,在某一個時刻,A已經儲存了對應的屬性值,而且這些值本身是有效,這個時候可能需要一個和A完全相同的對象B,並且當B裡面的屬性值發生變化的時候,A中的屬性值不受影響,可以理解為A和B獨立,但是B的初始化不是按照我們平時建立該對象的時候的初始化操作,B的初始化資料完全來自A。

一、淺拷貝

當我們需要將一個對象拷貝至另一個對象時,我們一般會這麼實現


function shadowCopy(source,target){    var target=target||{};    for(var i in source)    {        target[i]=source[i];    }    return target;}var a={name:'Lily',age:19};var b=shadowCopy(a);//b={name:'Lily',age:19}


淺拷貝的問題是,如果父物件的屬性等於數組或另一個對象,實際上子物件獲得的只是一個記憶體位址,而不是真正拷貝,父物件的數組或對象屬性發生變化時,子物件對應屬性也發生變化

function shadowCopy(source,target){    var target=target||{};    for(var i in source)    {        target[i]=source[i];    }    return target;}var a={name:'Lily',Hobbies:['Music','Sport']};var b=shadowCopy(a);//b={name:'Lily',Hobbies:['Music','Sport']}a.Hobbies.push('Read');//b={name:'Lily',Hobbies:['Music','Sport','Read']}


二、深拷貝

為瞭解決上述問題,需要對對象的數組和對象屬性進行深拷貝。它的實現並不難,只要遞迴調用"淺拷貝"就行了

function deepCopy(source,target){    var target=target||{};    for(var i in source)    {        if(typeof source[i] === 'object'){            target[i] = (source[i].constructor === Array ) ? [] : {} ;            deepCopy(source[i],target[i]);        }else{            target[i]=source[i];        }    }    return target;}             var a={name:'Lily',Hobbies:['Music','Sport']};var b=deepCopy(a);//b={name:'Lily',Hobbies:['Music','Sport']}a.Hobbies.push('Read');//b={name:'Lily',Hobbies:['Music','Sport','Read']},b={name:'Lily',Hobbies:['Music','Sport']}


上述代碼中有一個問題,當待拷貝對象中存在自引用時,程式會陷入無限迴圈

var a={name:'lily'};
a.obj=a;
deepCopy(a);

在Chome Console運行時,如下提示

RangeError: Maximum call stack size exceeded

為瞭解決自引用問題,拷貝時加入判斷邏輯

function deepCopy(source,target){    var target=target||{};    for(var i in source)    {                //防止自引用        if(source[i] === source )            continue;        if(typeof source[i] === 'object'){            target[i] = (source[i].constructor === Array ) ? [] : {} ;            deepCopy(source[i],target[i]);        }else{            target[i]=source[i];        }    }    return target;}             var a={name:'lily'};a.obj=a;var b=deepCopy(a);//b={name:'lily'}


三、JQuery拷貝實現

網上有很多對JQuery extend方法的分析,有不瞭解的可以去搜尋閱讀

貼一處被分析的源碼

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;        target = arguments[1] || {};        // skip the boolean and the target        i = 2;    }    // 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 ( length === i ) {        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;};


四、JQuery實現疑問

在閱讀上述JQuery代碼時,有個地方有疑問,疑問代碼如下

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


注意到,在extend方法中,為了防止無限迴圈,這裡有一個邏輯,在target對象等於copy對象時,調過這次複製操作。其中,copy對象為options對象的屬性對象。

問題是,這裡為什麼要拿target對象與copy對象比較呢?難道不應該是比較copy對象和options對象嗎?

帶著這個疑問,在一個已經引入了JQuery庫的頁面Console中執行下

var a={name:'lily'};
a.obj=a;
var b={};
$.extend(true,b,a);
RangeError: Maximum call stack size exceeded

可以看到,當a對象中存在自引用屬性時,extend方法並不能防止無限迴圈的發生

那麼判斷target === copy能起到什麼作用呢?

var a={name:'lily'};
var b={age:19};
a.obj=b;
$.extend(true,b,a);
//此時b={age: 19, name: "lily"}

去掉判斷target === copy會陷入無限迴圈嗎?實際上是不會的

var a={name:'lili'};
var b={age:19};
a.obj=b;
deepCopy(true,b,a);
//b=Object {age: 19, name: "lili", obj: Object}
//其中Object為b

這裡的deepCopy是我將JQuery的extend方法,去掉上述判斷邏輯,自己實現了一份

function deepCopy() {    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;        target = arguments[1] || {};        // skip the boolean and the target        i = 2;    }    // 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 ( length === i ) {        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 ( options === copy ) {                    continue;                }                */                // Recurse if we're merging plain objects or arrays                if ( deep && copy && ( isPlainObject(copy) || (copyIsArray =isArray(copy)) ) ) {                    if ( copyIsArray ) {                        copyIsArray = false;                        clone = src && isArray(src) ? src : [];                    } else {                        clone = src && isPlainObject(src) ? src : {};                    }                    // Never move original objects, clone them                    target[ name ] =deepCopy( deep, clone, copy );                // Don't bring in undefined values                } else if ( copy !== undefined ) {                    target[ name ] = copy;                }            }        }    }    // Return the modified object    return target;};var isString=function(obj){    return Object.prototype.toString.call(obj) === '[object String]';};var isArray=function(obj){    return Object.prototype.toString.call(obj) === '[object Array]';};var isPlainObject=function(obj){    return Object.prototype.toString.call(obj) === '[object Object]';}var a={name:'lili'};var b={age:19};a.obj=b;deepCopy(true,b,a);


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.