淺拷貝 and 深拷貝
A primitive (primitive value, primitive data type) is data that is not an object and has no methods. In JavaScript, there are 6 primitive data types: string, number, boolean, null, undefined, symbol (new in ECMAScript 2015).
All primitives are immutable (cannot be changed).
所以有六種原始類型:string, number, boolean, null, undefined, symbol。而這些原始類型不是一個對象,沒有方法,不能改變。“不能改變”是指原始類型的value是immutable的,而variable是mutable的.所以說,對於原始類型的變量,為其賦值,本質上就是讓變量指向新的內存.
而Object的賦值,則大不相同,涉及到深拷貝和淺拷貝。
淺拷貝:淺拷貝是拷貝引用,拷貝後的引用都是指向同一個對象的實例,彼此之間的操作會互相影響,淺拷貝只拷貝一層對象的屬性;
深拷貝:在堆中重新分配內存,並且把源對象所有屬性都遞歸進行新建拷貝,以保證深拷貝的對象的引用圖不包含任何原有對象或對象圖上的任何對象,拷貝後的對象與原來的對象是完全隔離,互不影響。
需要註意的是,如果對象比較大,層級也比較多,深拷貝會帶來性能上的問題。在遇到需要采用深拷貝的場景時,可以考慮有沒有其他替代的方案。在實際的應用場景中,也是淺拷貝更為常用。
1. 淺拷貝
淺拷貝分兩種情況,直接拷貝源對象的引用和源對象拷貝實例,但其屬性(類型為Object,Array的屬性)拷貝引用。
1.1 拷貝原對象的引用
var a = {c:1};
var b = a;
console.log(a === b); // true。
a.c = 2;
console.log(b.c); // 2
1.2 源對象拷貝實例,其屬性對象拷貝引用。
這種情況,外層源對象是拷貝實例,如果其屬性元素為復雜雜數據類型時,內層元素拷貝引用。
對源對象直接操作,不影響另外一個對象,但是對其屬性操作時候,會改變另外一個對象的屬性的值。
常用方法為:Array.prototype.slice(), Array.prototype.concat(), jQury的$.extend({},obj),例:
1.2.1 slice(0)
var a = [{c:1}, {d:2}];
var b = a.slice(0);
console.log(a === b); // false,說明外層數組拷貝的是實例
a[0].c = 3;
console.log(b[0].c); // 3,說明其元素拷貝的是引用
1.2.2 concat([])
var arr1 = [1,2,3];
var arr2=arr1.concat([]);
arr2[1] = 66;
console.log(arr1); //[1, 2, 3]
console.log(arr2); //[1, 66, 3]
1.2.3 $.extend({},obj)
var a={name:"mary",arr:[1,2,3],data:[{id:0},{id:1}]},
g=$.extend({}, a);
g.name=‘tom‘;
g.arr[1]=22;
g.data[1].id=33;
console.log(JSON.stringify(a));
//{"name":"mary","arr":[1,22,3],"data":[{"id":0},{"id":33}]}
console.log(JSON.stringify(g));
//{"name":"tom","arr":[1,22,3],"data":[{"id":0},{"id":33}]}
2. 深拷貝
深拷貝後,兩個對象,包括其內部的元素互不幹擾。常見方法有JSON.parse(),JSON.stringify(),jQury的$.extend(true,{},obj),lodash的_.cloneDeep和_.clone(value, true)。
2.1 JSON.parse(),JSON.stringify()
b = JSON.parse( JSON.stringify(a) );
局限性:
無法復制函數;
原型鏈沒了,對象就是object,所屬的類沒了。
2.2 $.extend(true,{},obj)
var a={name:"mary",arr:[1,2,3],data:[{id:0},{id:1}]},
g=$.extend(true,{}, a);
g.name=‘tom‘;
g.arr[1]=22;
g.data[1].id=33;
console.log(JSON.stringify(a));
//{"name":"mary","arr":[1,2,3],"data":[{"id":0},{"id":1}]}
console.log(JSON.stringify(g));
//{"name":"tom","arr":[1,22,3],"data":[{"id":0},{"id":33}]}
2.3 _.cloneDeep
var a={name:"mary",arr:[1,2,3],data:[{id:0},{id:1}]},
g=_.cloneDeep(a);
g.name=‘tom‘;
g.arr[1]=22;
g.data[1].id=33;
console.log(JSON.stringify(a));
//{"name":"mary","arr":[1,2,3],"data":[{"id":0},{"id":1}]}
console.log(JSON.stringify(g));
//{"name":"tom","arr":[1,22,3],"data":[{"id":0},{"id":33}]}
2.4 自定義方案一:
var isType = function(type){
return function(obj){
//簡單類型檢測Object.prototype.toString
return Object.prototype.toString.call(obj) === ‘[object ‘+ type +‘]‘;
}
}
var is = {
isArray : isType(‘Array‘),
isObject : isType(‘Object‘),
}
var copyObj = function(result, source) {
for (var key in source){
var copy = source[key];
if(is.isArray(copy)){
//Array deep copy
result[key] = copyObj(result[key] || [], copy);
}else if(is.isObject(copy)){
//Object deep copy
result[key] = copyObj(result[key] || {}, copy);
}else{
result[key] = copy;
}
}
return result;
}
var o1 = {
number: 1,
string: "I am a string",
object: {
test1: "Old value"
},
arr: [
"a string",
{
test2: "Try changing me"
}
]
};
var o2 = copyObj({},o1);
o2.object.test1 = "new Value";
console.log(JSON.stringify(o1));
//{"number":1,"string":"I am a string","object":{"test1":"Old value"},"arr":["a string",{"test2":"Try changing me"}]}
console.log(JSON.stringify(o2));
//{"number":1,"string":"I am a string","object":{"test1":"new Value"},"arr":["a string",{"test2":"Try changing me"}]}
o2.arr[1].subArr = [1,2,3];
console.log(JSON.stringify(o1));
//{"number":1,"string":"I am a string","object":{"test1":"Old value"},"arr":["a string",{"test2":"Try changing me"}]}
console.log(JSON.stringify(o2));
//{"number":1,"string":"I am a string","object":{"test1":"new Value"},"arr":["a string",{"test2":"Try changing me","subArr":[1,2,3]}]}
2.5 自定義方案二:
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;
};
參考:
https://developer.mozilla.org/en-US/docs/Glossary/Primitive
http://yuanhehe.cn/2016/11/03/%E7%90%86%E8%A7%A3JS%E7%9A%84%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D/
https://yq.aliyun.com/articles/35053
淺拷貝 and 深拷貝