1. 程式人生 > >javascript 陣列、物件深度克隆

javascript 陣列、物件深度克隆

最近專案過程中針對陣列及物件的賦值發現以下問題

情況一:

 var A={age:12,name:'anne'},B=A;
    B.grade='五年級';
    console.log('A:',A,'B:',B);  
    //A:{age:12,name:'anne',grade:'五年級'} B: {age:12,name:'anne',grade:'五年級'}

情況二:

    var A={age:12,name:'anne'},
    B={}; //初始化物件B
    B=A;
    B.grade='五年級';
    console.log('A:',A,'B:',B);  
    //A:{age:12,name:'anne',grade:'五年級'} B: {age:12,name:'anne',grade:'五年級'}
 

以上兩種我們會發現原本的目的是想改變物件B,但實際情況是物件A也跟著被改變,且初始化物件B也是無效;

javascript中有兩種型別,一種叫做基本資料型別,一種就是引用型別

基本資料型別有 String Number Boolean undefined null

引用資料型別有 Object Array 等 (Function Regexp)

因為物件是引用型別,用物件儲存的是物件的引用地址,而把物件的實際內容單獨存放,所以物件的賦值是預設引用物件地址,修改的時候改變的是同一個地址的物件,所有互相干擾,如果你想要複製賦值,則必須要重新分配物件

我們給出以下幾種寫法:

  1. 使用Object.assign()
    該方法用於將所有可列舉屬性的值從一個或多個源物件複製到目標物件。它將返回目標物件。
 var A={age:12,name:'anne'}, B=Object.assign({},A); //將一個空物件和A複製到同一個目標物件
    
    B.grade='五年級';
    
    console.log( 'A:', A, 'B:' ,B );  
    //A:{age:12,name:'anne'} B: {age:12,name:'anne',grade:'五年級'}
  1. JSON物件序列化方法
    這個方法明顯是簡單得多,但是有個弊端,就是不能複製函式
var obj = {a:1,b:2}  
var newObj = JSON.parse(JSON.stringify(obj));   
newObj.c=3;
console.log(obj)  //{a:1,b:2} 
console.log(newObj)  //{a:1,b:2,c:3}
  1. 封裝的JSON物件序列化(相容處理,不能複製函式)
var cloneObj = function(obj){
      var str, newobj = obj.constructor === Array ? [] : {};
    if(typeof obj !== 'object'){
         return;
      } else if(window.JSON){
          str = JSON.stringify(obj), //序列化物件
          newobj = JSON.parse(str); //還原
     } else {
         for(var i in obj){
            newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i]; 
         }
     }
     return newobj;
};
  1. 完整的深度克隆物件
function deepClone(obj){
    var newObj,oClass=isClass(obj);
         //確定newObj的型別
    if(oClass==="Object"){
        newObj={};
    }else if(oClass==="Array"){
        newObj=[];
    }else{
        return obj;
    }
    for(key in obj){
        var _key=obj[key];
        if(isClass(_key)=="Object"){
            newObj[key]=
            (_key);//遞迴呼叫
        }else if(isClass(_key)=="Array"){
            newObj[key]=arguments.callee(_key);
        }else{
            newObj[key]=obj[key];
        }
    }
    return newObj;
}
function isClass(obj){
    if(obj===null) return "Null";
    if(obj===undefined) return "Undefined";
    return Object.prototype.toString.call(obj).slice(8,-1);
}


/*直接測試帶函式的物件克隆*/
var obj = {a:1,b:2,c:()=>{console.log("this's a function")}} ;
var newObj=deepClone(obj);
newObj.d=function(){
	console.log('The second function ');
}
newObj.d;     // f(){console.log('The second function ');}
obj.d             // undefined

/*陣列克隆*/
var arr = ['a','b'];
var newArr=deepClone(arr);
newArr[2]='c';
console.log('arr=>',arr,'newArr=>',newArr)     //arr=> ['a','b'] newArr=> ['a','b','c']

從上面的程式碼可以看到,深度克隆的物件可以完全脫離原物件,我們對新物件的任何修改都不會反映到原物件中,這樣深度克隆就實現了。

這裡要注意一點的就是:deepClone這個函式中的newObj一定要判斷型別?newObj直接是物件,後面會導致明明傳進去的是一個數組,結果複製完了以後,變成了一個物件了。
  如下例:

function deepClone(obj){
    var newObj={},oClass=isClass(obj);
    for(key in obj){
        var _key=obj[key];
        if(isClass(_key)=="Object"){
            newObj[key]=arguments.callee(_key);//遞迴呼叫
        }else if(isClass(_key)=="Array"){
            newObj[key]=arguments.callee(_key);
        }else{
            newObj[key]=obj[key];
        }
    }
    return newObj;
}
function isClass(obj){
    if(obj===null) return "Null";
    if(obj===undefined) return "Undefined";
    return Object.prototype.toString.call(obj).slice(8,-1);
}

var arr = ['a','b'];
var newArr=deepClone(arr); 
console.log(newArr) //{0:'a',1:'b'}