指標巢狀指標 拷貝_淺拷貝與深拷貝
技術標籤:指標巢狀指標 拷貝淺拷貝和深拷貝的區別深拷貝和淺拷貝的區別
在 JavaScript 中有兩中變數型別資料, 基本型別和引用型別. 對值型別的複製操作會對變數值進行拷貝, 兩者互不相干. 而引用型別只會對儲存變數的指標地址進行拷貝, 導致兩個變數指向同一個地址, 也就是同一份資料, 修改其中一個會直接影響另外一個.
而我們要談的淺拷貝與深拷貝就是專指引用型別.
淺拷貝
淺拷貝是最簡單易理解的, 只對引用型別進行一級拷貝.
- 自行實現
function shallowClone(source) { var target = {}; for(var i in source) { if (source.hasOwnProperty(i)) { target[i] = source[i]; } } return target; }
- Object.assign
let obj = {
a: 1,
b: 2,
};
let objCopy = Object.assign({}, obj);
console.log(objCopy);
// Result - { a: 1, b: 2 }
上面的程式碼會將所有的可列舉的自身屬性複製到目標物件.
- 擴充套件操作符 (...)
var sourceObject = { a: 1, b: function() { return a; } }; var targetObject = { ...sourceObject }; console.log(targetObject.b === sourceObject.b); // true
淺拷貝只會對一級進行拷貝, 對於巢狀的引用型別依然是同一個指標地址.
深拷貝
深拷貝會對任意的巢狀層級進行拷貝, 保證所有的引用型別全部是新物件, 不會和原物件有任何的關係, 彼此的修改不會互相影響.
- 奇淫巧技 JSON.parse(JSON.stringify(object))
let obj = { a: 1, b: { c: 2, }, } let newObj = JSON.parse(JSON.stringify(obj)); obj.b.c = 20; console.log(obj); // { a: 1, b: { c: 20 } } console.log(newObj); // { a: 1, b: { c: 2 }
上面的方法雖然簡單但是有很多的問題.
第一個: 不能拷貝函式
let obj = {
name: 'neo',
sayName: function exec() {
return this.name;
},
}
let method = JSON.parse(JSON.stringify(obj));
console.log(method); // JSON.parse(JSON.stringify(obj))
/*
{
name: "neo"
}
*/
第二個: 迴圈引用
迴圈引用是說物件的物件引用了他們自身. 這個是深拷貝中一個老生常談的問題.
// 迴圈引用
let obj = {
a: 'a',
b: {
c: 'c',
d: 'd',
},
}
obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
上面程式碼執行的結果是:
所以說, JSON.parse(JSON.stringify(obj)) 沒辦法解決迴圈引用的問題.
第三個: 值為 undefined 的屬性會被忽略
var obj = {
a: 'lendel',
b: undefined
};
var newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// 結果
/*
{ a: 'lendel' }
*/
第四個: Infinity 值會被置為 null
var obj = {
a: 'lendel',
i: Infinity
};
var newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// 結果
/*
{ a: 'lendel', i: null }
*/
第五個: 一些物件會被轉為字串
這種方法會將一些型別的物件轉變字串, 例如 Date, Set, Map......
var obj = {
a: 'lendel',
d: new Date()
};
var newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// 結果
/*
{ a: 'lendel', d: '2019-04-01T02:53:37.720Z' }
*/
一個簡單的深拷貝方法
function cloneObject(obj) {
var clone = {};
for(var i in obj) {
if(obj[i] != null && typeof(obj[i])=="object")
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
這是一段非常簡單的深拷貝函式, 因為簡單,所以問題也多. 引數的校驗並不完備, 利用遞迴時當層次過深的時候容易造成棧溢位, 還有迴圈引用.
上面的程式碼體現了深拷貝的基本思想,但實際使用中建議使用 lodash 等現成的庫
對於棧溢位的問題, 我們可以將遞迴改用迴圈來解決:
function loopClone(x) {
const root = {};
// 棧
const loopList = [
{
parent: root,
key: undefined,
data: x
}
];
while (loopList.length) {
// 深度優先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化賦值目標,key為undefined則拷貝到父元素,否則拷貝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次迴圈
loopList.push({
parent: res,
key: k,
data: data[k]
});
} else {
res[k] = data[k];
}
}
}
}
return roo
程式碼來源: https:// yanhaijing.com/javascri pt/2018/10/10/clone-deep/
結語:
寫的時候我一直在想一個問題, 我究竟需不需要自己寫一個深拷貝函式或類似的. 對於學習來說這是件無關緊要的事, 但是想要在產品中使用自己寫的其實是挺蠢的事. 並不是說我們實現不出來, 而是投入時間的價效比實在太低. 要完成一個工業級別的函式需要大量的測試, 要覆蓋到每一種情況,這是件得不償失的事. 並且是在網上有那麼多經過上千場景驗證過的現成框架的情況下.所以, 要在合適的情況下選擇合適的工具.
文章參考: https:// flaviocopes.com/how-to- clone-javascript-object/ https:// yanhaijing.com/javascri pt/2018/10/10/clone-deep/