在 JS 中有一些基本類型像是Number、String、Boolean,而對象就是像這樣的東西{ name: 'Larry', skill: 'Node.js' },對象跟基本類型最大的不同就在于他們的傳值方式。
基本類型是按值傳遞,像是這樣:在修改a時并不會改到b
vara =25;
varb =a;
b=18;
console.log(a);//25
console.log(b);//18
但對象就不同,對象傳的是按引用傳值:
varobj1 = { a:10, b:20, c:30};
varobj2 =obj1;
obj2.b=100;
console.log(obj1);
//{ a: 10, b: 100, c: 30 } <-- b 被改到了
console.log(obj2);
//{ a: 10, b: 100, c: 30 }
復制一份obj1叫做obj2,然后把obj2.b改成100,但卻不小心改到obj1.b,因為他們根本是同一個對象,這就是所謂的淺拷貝。
要避免這樣的錯誤發生就要寫成這樣:
varobj1 = { a:10, b:20, c:30};
varobj2 ={ a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b=100;
console.log(obj1);
//{ a: 10, b: 20, c: 30 } <-- b 沒被改到
console.log(obj2);
//{ a: 10, b: 100, c: 30 }
這樣就是深拷貝,不會改到原本的obj1。
淺拷貝(Shallow Copy) VS 深拷貝(Deep Copy)
淺拷貝只復制指向某個對象的指針,而不復制對象本身,新舊對象還是共享同一塊內存。但深拷貝會另外創造一個一模一樣的對象,新對象跟原對象不共享內存,修改新對象不會改到原對象。
淺拷貝的實現方式
也就是簡單地復制而已
1、簡單地復制語句
function simpleClone(initalObj) {
varobj ={};
for(variininitalObj) {
obj[i]=initalObj[i];
}
returnobj;
}
varobj ={
a:"hello",
b:{
a:"world",
b:21
},
c:["Bob","Tom","Jenny"],
d:function() {
alert("hello world");
}
}
varcloneObj =simpleClone(obj);
console.log(cloneObj.b);
console.log(cloneObj.c);
console.log(cloneObj.d);
cloneObj.b.a="changed";
cloneObj.c= [1,2,3];
cloneObj.d= function() { alert("changed"); };
console.log(obj.b);
console.log(obj.c);
console.log(obj.d);
結果為:
2、Object.assign()
Object.assign是ES6的新函數。Object.assign()方法可以把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,然后返回目標對象。但是Object.assign()進行的是淺拷貝,拷貝的是對象的屬性的引用,而不是對象本身。
Object.assign(target, ...sources)
參數:
target:目標對象。
sources:任意多個源對象。
返回值:目標對象會被返回。
varobj = { a: {a:"hello", b:21} };
varinitalObj =Object.assign({}, obj);
initalObj.a.a="changed";
console.log(obj.a.a);//"changed"
兼容性:
需要注意的是:
Object.assign()可以處理一層的深度拷貝,如下:
varobj1 = { a:10, b:20, c:30};
varobj2 =Object.assign({}, obj1);
obj2.b=100;
console.log(obj1);
//{ a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
//{ a: 10, b: 100, c: 30 }
深拷貝的實現方式
要完全復制又不能修改到原對象,這時候就要用?Deep Copy,這里會介紹幾種Deep Copy?的方式。
1、手動復制
把一個對象的屬性復制給另一個對象的屬性
varobj1 = { a:10, b:20, c:30};
varobj2 ={ a: obj1.a, b: obj1.b, c: obj1.c };
obj2.b=100;
console.log(obj1);
//{ a: 10, b: 20, c: 30 } <-- 沒被改到
console.log(obj2);
//{ a: 10, b: 100, c: 30 }
但這樣很麻煩,要一個一個自己復制;而且這樣的本質也不能算是 Deep Copy,因為對象里面也可能回事對象,如像下面這個狀況:
varobj1 = { body: { a:10} };
varobj2 ={ body: obj1.body };
obj2.body.a=20;
console.log(obj1);
//{ body: { a: 20 } } <-- 被改到了
console.log(obj2);
//{ body: { a: 20 } }
console.log(obj1 ===obj2);
//false
console.log(obj1.body ===obj2.body);
//true
雖然obj1跟obj2是不同對象,但他們會共享同一個obj1.body,所以修改obj2.body.a時也會修改到舊的。
2、對象只有一層的話可以使用上面的:Object.assign()函數
Object.assign({}, obj1)的意思是先建立一個空對象{},接著把obj1中所有的屬性復制過去,所以obj2會長得跟obj1一樣,這時候再修改obj2.b也不會影響obj1。
因為Object.assign跟我們手動復制的效果相同,所以一樣只能處理深度只有一層的對象,沒辦法做到真正的?Deep Copy。不過如果要復制的對象只有一層的話可以考慮使用它。
3、轉成?JSON?再轉回來
用JSON.stringify把對象轉成字符串,再用JSON.parse把字符串轉成新的對象。
varobj1 = { body: { a:10} };
varobj2 =JSON.parse(JSON.stringify(obj1));
obj2.body.a=20;
console.log(obj1);
//{ body: { a: 10 } } <-- 沒被改到
console.log(obj2);
//{ body: { a: 20 } }
console.log(obj1 ===obj2);
//false
console.log(obj1.body ===obj2.body);
//false
這樣做是真正的Deep Copy,這種方法簡單易用。
但是這種方法也有不少壞處,譬如它會拋棄對象的constructor。也就是深拷貝之后,不管這個對象原來的構造函數是什么,在深拷貝之后都會變成Object。
這種方法能正確處理的對象只有Number, String, Boolean, Array, 扁平對象,即那些能夠被 json 直接表示的數據結構。RegExp對象是無法通過這種方式深拷貝。
也就是說,只有可以轉成JSON格式的對象才可以這樣用,像function沒辦法轉成JSON。
varobj1 = { fun: function(){ console.log(123) } };
varobj2 =JSON.parse(JSON.stringify(obj1));
console.log(typeofobj1.fun);
//'function'
console.log(typeofobj2.fun);
//'undefined' <-- 沒復制
要復制的function會直接消失,所以這個方法只能用在單純只有數據的對象。
4、遞歸拷貝
function deepClone(initalObj, finalObj) {
varobj = finalObj ||{};
for(variininitalObj) {
if(typeofinitalObj[i] ==='object') {
obj[i]= (initalObj[i].constructor === Array) ?[] : {};
arguments.callee(initalObj[i], obj[i]);
}else{
obj[i]=initalObj[i];
}
}
returnobj;
}
varstr ={};
varobj = { a: {a:"hello", b:21} };
deepClone(obj, str);
console.log(str.a);
上述代碼確實可以實現深拷貝。但是當遇到兩個互相引用的對象,會出現死循環的情況。
為了避免相互引用的對象導致死循環的情況,則應該在遍歷的時候判斷是否相互引用對象,如果是則退出循環。
改進版代碼如下:
function deepClone(initalObj, finalObj) {
varobj = finalObj ||{};
for(variininitalObj) {
varprop = initalObj[i];//避免相互引用對象導致死循環,如initalObj.a = initalObj的情況
if(prop ===obj) {
continue;
}
if(typeofprop ==='object') {
obj[i]= (prop.constructor === Array) ?[] : {};
arguments.callee(prop, obj[i]);
}else{
obj[i]=prop;
}
}
returnobj;
}
varstr ={};
varobj = { a: {a:"hello", b:21} };
deepClone(obj, str);
console.log(str.a);
5、使用Object.create()方法
直接使用var newObj = Object.create(oldObj),可以達到深拷貝的效果。
function deepClone(initalObj, finalObj) {
varobj = finalObj ||{};
for(variininitalObj) {
varprop = initalObj[i];//避免相互引用對象導致死循環,如initalObj.a = initalObj的情況
if(prop ===obj) {
continue;
}
if(typeofprop ==='object') {
obj[i]= (prop.constructor === Array) ?[] : Object.create(prop);
}else{
obj[i]=prop;
}
}
returnobj;
}
6、jquery
jquery?有提供一個$.extend可以用來做?Deep Copy。
var$ = require('jquery');
varobj1 ={
a:1,
b: { f: { g:1} },
c: [1,2,3]
};
varobj2 = $.extend(true, {}, obj1);
console.log(obj1.b.f===obj2.b.f);
//false
7、lodash
另外一個很熱門的函數庫lodash,也有提供_.cloneDeep用來做?Deep Copy。
var_ = require('lodash');
varobj1 ={
a:1,
b: { f: { g:1} },
c: [1,2,3]
};
varobj2 =_.cloneDeep(obj1);
console.log(obj1.b.f===obj2.b.f);
//false
這個性能還不錯,使用起來也很簡單。
最后,又不正之處歡迎之處
原文地址:http://www.cnblogs.com/Chen-XiaoJun/p/6217373.html