在javascript中對資料的clone分為指派、深拷貝和淺拷貝。
在經過克隆之後,我們希望clone的對象操作之後與資料源互不幹擾。
在分析深拷貝和淺拷貝之前還有一個概念要清楚,就是不同資料類型的clone方式和效果是不同的。
javascript中資料類型
類型 | 内容 |
---|---|
基本資料類型 | string、number、boolean、null、undefined |
引用類型 | array、object、 |
- 基本資料類型儲存在
中,在進行淺拷貝操作後,會在記憶體中開辟一塊新的空間,然後把賦過來的值儲存于這塊新開辟的空間中,存放在棧中的資料互不影響棧
- 引用資料類型儲存在
中,在進行淺拷貝操作後,也會開辟一塊新的空間,不同的是新的空間儲存的是指派對象現在的位址,由于資料對位址的引用是相同的,是以會關聯變化堆
- 是以深拷貝和淺拷貝是針對與引用類型讨論的
- 淺複制隻複制一層對象的屬性,而深複制則遞歸複制了所有層級。
指派、深拷貝、淺拷貝差別
- 指派 : 兩個對象經過拷貝之後,有相同的屬性,對象也相同
var obj = ;
var newobj = obj;
obj === newobj //true
- 淺拷貝 : 經過拷貝之後雖然有相同屬性,但是對象卻不同,其内部引用類型資料指向相同記憶體位址
var arr = [,,];
var newarr = arr.slice();
arr === newobj; //false
arr.push();
newarr // [1,2,3,12]
- 深拷貝 : 經過拷貝之後雖然有相同的屬性,但是對象卻不同,而且其内部引用類型位址也不相同
var arr = [,,];
var newarr = JSON.parse(JSON.stringify(arr));
arr === newobj; //false
arr.push();
newarr // [1,2,3]
指派
- 如果隻是想要對資料的複制,那麼直接指派就可以了,這是對資料進行clone的一種方式:
var data = {
a : "hello",
b : ,
c : [,,],
d : function(){
return this.a;
}
}
var shallowcopy = data;
console.log(shallowcopy); //{a: "hello", b: 12, c: Array(3), d: ƒ};
- 但是這樣會存在一個問題!
data.a += "world";
shallowcopy.a; //"hello"
data.c.push(12);
shallowcopy.c // [, , , ]
- ① 當data.a變化時,shallowcopy.a不會跟着變化,這是我們想要的結果
- ② 當data.c變化時, shallowcopy.cc也會跟着關聯變化,和我們的需求不太一樣。
是以有了淺拷貝的需求,使得拷貝出來的資料更新時和源資料互不幹擾。
淺拷貝
方式一: splice()
- slice() 方法傳回一個從開始到結束(不包括結束)選擇的數組的一部分淺拷貝到一個新數組對象。原始數組不會被修改。
var arr = [,,];
var arr1 = arr.slice();
arr.push();
arr //[1,2,3,12]
arr1 //[1,2,3]
- 由于slice()是數組的方法,是以這種方式隻能對引用類型中的數組使用
方式二:Object.assign()
- 方法用于将所有可枚舉屬性的值從一個或多個源對象複制到目标對象。它将傳回目标對象。
- Object.assign(target, …sources)
var arr = [,,,];
var arr2 =Object.assign([],arr);
arr.a.push()
arr2 // Object { a: Array [1, 2, 3, 4, 12] }
// 這種淺拷貝方式,可操作所有引用類型的對象。且新對象與原對象互不幹擾。但是新的問題出現了:
var obj = {
a : [,,,],
b : [,,,]
}
var newobj = Object.assign({},obj);
obj.a.push()
newobj.a // [1, 2, 3, 4, 12]
//當對象多嵌套一層數組的時候,對資料進行修改,還是會互相影響,這種情況下隻有深拷貝才能解決這種問題了。
深拷貝
方式一: JSON.parse(JSON.stringify(oldobj))
var obj = {
a : ,
b : {c : }
}
var newobj = JSON.parse(JSON.stringify(obj));
obj.a = ;
obj.b.c = ;
newobj = { a: , b: { c: }}; //互不幹擾
- 然而這種使用JSON序列化的方法有使用限制
- 對象裡有函數,函數無法被拷貝下來
- 對象原型鍊上的屬性和方法無法被拷貝下來
var obj = {
a: ,
b: function(){
return this.a
}
};
var newobj = JSON.parse(JSON.stringify(obj));
newobj // {a: 0} -- 函數未被拷貝
方式二: 函數方式遞歸淺拷貝
- 先看函數方式的淺拷貝方法:
function shallowCopy(source, target = {}) {
var key;
for (key in source) {
if (source.hasOwnProperty(key)) { // 意思就是__proto__上面的屬性,我不拷貝
target[key] = source[key];
}
}
return target;
}
var obj = { a: "hello", b: [,,]};
var newobj = shallowCopy(obj,{});
obj.b.push();
console.log(obj !== newobj); //true 不管深拷貝還是淺拷貝obj都是兩個不同對象,他們所占記憶體位址不同
console.log(obj.b == newobj.b); //true 由于是淺拷貝,是以引用類型所占記憶體位址是相同的
console.log(newobj.b); //[1,2,3,12] // 互相幹擾
- 先看函數方式的深拷貝方法:
- 周遊被拷貝對象 (for in)
- 判斷被拷貝對象的資料類型
- 如果不是對象類型則直接指派,如果是對象類型則再次調用deepcopy() ③
function deepCopy(source, target) {
var target = target || {};
for (var key in source) {
if (source.hasOwnProperty(key)) { //判斷對象自身屬性中是否具有指定的屬性,過濾__proto__原型上屬性
if (typeof source[key] === 'object') { // ③
target[key] = (source[key].constructor === Array) ? [] : {}; //判斷target[key] 類型是[] / {}
deepCopy(source[key], target[key]);
} else {
target[key] = source[key];
}
}
}
return target;
}
var obj = {
a: "hello",
b: [, , ]
};
var newobj = deepCopy(obj, {});
obj.b.push();
console.log(newobj); // Object {a: "hello", b: Array(3)} 互不幹擾
jquery - $.extend() 方式進行深、淺拷貝
- $.extend( [deep ], target, object1 [, objectN ] )
- jQuery.extend() 函數用于将一個或多個對象的内容合并到目标對象。
第一個參數為是否深度拷貝,預設是淺拷貝
第二個參數是拷貝目标
第三個參數是拷貝的源目标
$(function () {
var object1 = {
apple: ,
banana: {
weight: ,
price:
},
cherry:
};
var newobj = {}
$.extend(true,newobj, object1);
object1.banana.price = ;
console.log(object1.banana);
console.log(newobj.banana);
})
大概總結了
0、 js中值類型和引用類型的存儲方式
1、 指派、深拷貝、淺拷貝的差別
2、 深拷貝的3種方式
3、 淺拷貝的3種方式
以上。