前言
在開發過程中,偶爾會遇到這種場景,拿到一個資料後,你打算對它進行處理,但是你又希望拷貝一份副本出來,方便資料對比和以後恢復資料。
那麼這就涉及到了 JS 中對資料的深淺拷貝問題,所謂深淺拷貝,淺拷貝的意思就是,你只是複製了物件資料的引用,並沒有把記憶體裡的值另外複製一份,那麼深拷貝就是把值完整地複製一份新的值。
在之前的文章《JS專題之資料型別和型別檢測》中我有講過,JS 中的資料型別分為兩種,基本資料型別和引用資料型別,基本資料型別是儲存在棧的資料結構中的,是按值訪問,所以不存在深淺拷貝問題。
而比如物件,陣列,函式,正則,時間物件這些都是引用資料型別,是儲存在堆中的。所以,引用資料型別的複製,是記憶體地址的傳遞,並沒有拷貝出一份新的資料。
那麼深拷貝,淺拷貝的區別是什麼呢?先給結論:
操作拷貝之後的資料不會影響到原資料的值拷貝,就是深拷貝,反正,有影響則為淺拷貝。
一、應用場景
日常開發中,JS 拷貝大多會在 資料儲存,資料比對,資料同步 時出現,所以,當你在這些場景的時候,要記得裡面隱藏有一個資料深淺拷貝的問題。
二、淺拷貝
我們來看一下淺拷貝:
function clone(origin) {
var result = {};
for (var prop in origin) {
if (origin.hasOwnProperty(prop)) {
result[prop] = origin[prop];
}
}
return result;
}
var jay = {
name: "jayChou",
age: 40,
family: {
wife: "Quinlivan"
}
}
var otherJay = clone(jay);
otherJay.age = 18;
otherJay.family.wife = "otherGirl";
console.log(jay);
//
// {
// name: "jayChou",
// age: 40, // 沒被改變
// family: {
// wife: "otherGirl" // 同時被改變,說明是同一個引用
// }
// }
console.log(otherJay);
//
// {
// name: "jayChou",
// age: 18,
// family: {
// wife: "otherGirl" // 被改變了
// }
// }
複製程式碼
我們發現,首先,淺拷貝不是直接賦值,淺拷貝新建了一個物件,然後將源物件的屬性都一一複製過來,複製的是值,而不是引用。
我們知道,物件都是按地址引用進行訪問的,淺拷貝的複製只複製了第一層的屬性,並沒有遞迴將所有的值複製過來,所以,操作拷貝資料,對原資料產生了影響,故而為淺拷貝。
進而,那些可以直接返回原陣列的方法就可以簡單實現陣列和物件淺拷貝。
// 1、 陣列淺拷貝 - slice
function shallowCopy1(origin) {
return origin.slice();
}
// 2、 陣列淺拷貝 - concat
function shallowCopy2(origin){
return origin.concat();
}
// 3、 陣列淺拷貝 - 遍歷
function shallowCopy3(origin){
var result = [];
for(var i = 0; i < origin.length; i++) {
result.push(origin[i]);
}
return result;
}
// 4、 物件淺拷貝 - Object.assign
function shallowCopy4(origin) {
return Object.assign({},origin)
}
// 5、 物件淺拷貝 - 擴充套件運算子
// 擴充套件運算子(...)用於取出引數物件的所有可遍歷屬性,拷貝到當前物件之中
function shallowCopy5(origin) {
return {
...origin
}
}
複製程式碼
Object.assign 的拷貝,假如源物件的屬性值是一個指向物件的引用,它也只拷貝那個引用值。MDN 有相應的例項和解釋。
二、深拷貝
深拷貝就完整複製資料的值(而非引用),目的在於避免拷貝後資料對原資料產生影響。
目前深拷貝的實現方法主要有遞迴複製,JSON 正反序列化:
// 1. 深拷貝 - JSON 正反序列化
// 缺點就是無法拷貝 undefined、function、symbol 這類特殊的屬性值。
function deepClone1(origin) {
return JSON.parse(JSON.stringify(arr));
}
// 2. 深拷貝 - 遞迴;
function deepClone2(origin) {
const result = origin.constructor === Array ? [] : {};
for (let keys in origin) {
// 不遍歷原型鏈上的屬性
if (origin.hasOwnProperty(keys)) {
if (origin[keys] && typeof origin[keys] === "object") {
// 如果值是物件,就遞迴一下, 區分是一般物件還是陣列物件
result[keys] = origin[keys].constructor === Array ? [] : {};
// 如果是引用資料型別,會遞迴呼叫
result[keys] = deepClone(origin[keys]);
} else {
// 如果不是,就直接賦值
result[keys] = origin[keys];
}
}
}
return result;
}
複製程式碼
JS 的深拷貝的應用,需要根據你的使用場景進行使用,首先是有無必要深拷貝,其次是資料型別,是否直接使用 JSON 的 API 其實就可以。
JS 深淺拷貝在日常開發中使用頻率還是較高的,其中考察的知識點,主要在於:
1、是否遇到過深淺拷貝的問題,裡面有什麼坑
2、是否瞭解 JS 的資料型別,資料在計算機中的儲存機制
3、是否瞭解陣列、物件的一些常用的 API
4、jquery、lodash、underscore 的相關工具函式使用
總結
深淺拷貝主要考察了開發者對 JS 資料型別的瞭解,陣列,物件常用方法的特點和應用,遞迴函式的封裝。
春節快樂!
寫於大年三十,不寫文章渾身不舒服~ 歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。
掘金專欄 JavaScript 系列文章
- JavaScript之變數及作用域
- JavaScript之宣告提升
- JavaScript之執行上下文
- JavaScript之變數物件
- JavaScript之原型與原型鏈
- JavaScript之作用域鏈
- JavaScript之閉包
- JavaScript之this
- JavaScript之arguments
- JavaScript之按值傳遞
- JavaScript之例題中徹底理解this
- JavaScript專題之模擬實現call和apply
- JavaScript專題之模擬實現bind
- JavaScript專題之模擬實現new
- JS專題之事件模型
- JS專題之事件迴圈
- JS專題之去抖函式
- JS專題之節流函式
- JS專題之函式柯里化
- JS專題之陣列去重