深拷貝、淺拷貝的操作目标都是對象,對象的作為引用類型,它的資料存放在堆記憶體中,而資料指針存放在棧記憶體中,當通路引用資料時,會先從棧記憶體中擷取指針,通過指針在堆記憶體中找到所需資料。
深拷貝
1. JSON實作深拷貝
JSON 是基于 JavaScript 的文法,用來序列化對象、數組、數值、字元串、布爾值和 null,,但它不是 JavaScript 的子集。它擁有 JSON.parse() 和 JSON.stringify() 兩個方法,分别用來解析 JSON 字元串和序列化資料。
通過 JOSN.stringify(obj)将資料序列化為字元串,然後重新解析該字元串,配置設定堆記憶體和棧記憶體來分别存儲資料和指針,進而實作深拷貝。
注意,該方法不能複制 function、正則和 symbol類型,同時在循環引用時也會報錯
比方說,一個對象的不同屬性或者子對象等等的屬性指向同一個引用類型的資料,在 JSON 解析後将不再指向同一個位址了,而是按照實際重新配置設定了不同的引用位址
var sameObj = {'a': 1};
var data = {'b': sameObj, 'c':{'d': 1, 'e':sameObj}};
data['b'] === data['c']['e']; // true, 因為指向的是同一個引用類型對象
var nowData = JSON.parse(JSON.stringify(data));
nowData ['b'] === nowData ['c']['e']; // false, 序列化時這兩個資料重新配置設定了不同的位址了
2. 遞歸實作深拷貝
建立一個對象,然後周遊目标對象的所有結點,将對應節點的資料在新對象上重新配置設定位址,尤其是類型為對象的資料,保證每一個節點上都是重新配置設定過的位址,進而實作深拷貝
function checkType(data){ // 用于判斷資料類型
return Object.prototype.toString.call(data).slice(8, -1);
}
function deepCopy(data){
var originObjArr = []; // 用于存放原始節點的資料
var resultObjArr = []; // 用于存放建立節點的資料
function _deepCopy(node){
var result;
// 擷取該節點在存放原始節點數組裡的位置
var index = originObjArr.indexOf(node);
if(checkType(node) == 'Object'){
// 如果在用于存放原始節點的數組中可以直接用建立節點來傳回
if(index > -1){
return resultObjArr[index];
}else{
result = {};
// 如果是該節點不在存放原始節點的數組裡就push進去
// 并把建立節點也push進存放建立節點的數組
originObjArr.push(node);
resultObjArr.push(result);
// 遞歸調用
for(var i in node){
// 排除從原型鍊上周遊到的屬性
if(node.hasOwnProperty(i)) {
result[i] = _deepCopy(node[i])
}
}
return result;
}
}else{
if(checkType(node) == 'Array'){
if(index > -1){
return resultObjArr[index];
}else {
result = [];
originObjArr.push(node);
resultObjArr.push(result);
for (var i = 0; i < node.length; i++) {
result[i] = _deepCopy(node[i]);
}
return result;
}
}else{
// 既不是數組也不是對象就直接傳回該類型資料
return node;
}
}
}
return _deepCopy(data);
}
這個方法對于除數組和對象的其它引用類型沒有處理,另外原型鍊上的屬性也沒有擷取
淺拷貝
淺拷貝是拷貝目标對象裡面的資料,但是不拷貝目标對象裡面的子對象
function checkType(data){ // 用于判斷資料類型
return Object.prototype.toString.call(data).slice(8, -1);
}
function shallowCopy(data){
var result;
if(checkType(data) == "Object"){
result = {};
for(var i in data){
// 排除從原型鍊上周遊到的屬性
if(data.hasOwnProperty(i)) {
result[i] = data[i];
}
}
return result;
}else{
if(checkType(data) == 'Array'){
result = [];
for (var i = 0; i < data.length; i++) {
result[i] = data[i];
}
return result;
}else{
return data;
}
}
}
es6 提供了新方法 Object.assign(),用于将所有可枚舉屬性的值從一個或多個源對象複制到目标對象。需要注意的是,該方法IE不支援,需要添加相容Polyfill。
// target 目标對象。
// sources 源對象
Object.assign(target, ...sources)
關于數組淺拷貝可以用些數組的原生方法
1. slice 方法
slice() 方法傳回一個新的數組對象,這一對象是一個由 begin 和 end 決定的原數組的淺拷貝(包括 begin,不包括end)。原始數組不會被改變。
2. concat 方法
concat() 方法用于合并兩個或多個數組。此方法不會更改現有數組,而是傳回一個新數組。
其它的方法這裡就不一一贅述了
