JS深淺拷貝及其實現

基本數據類型和引用數據類型
JS數據分為基本數據類型和引用數據類型。基本數據類型的變量存儲在棧中,引用數據類型則存儲在堆中,引用數據類型的存儲地址則保存在棧中。
下面來看一個小例子🌰
// 基本數據類型 let intType = 1; console.log('初始intType:' + intType); let copyIntType = intType; copyIntType = 2; console.log('更改後intType:' + intType); console.log('更改後copyIntType:' + intType); let object = { a: 1 }; // 引用數據類型 let copyObject = object console.log('初始object:'); console.log(object); copyObject.a = 2; console.log('更改後的object:'); console.log(object); console.log('更改後的copyObject:'); console.log(copyObject);
結果:
基本數據類型在複製的時候會創建一個值的副本,並將該副本賦值給新變量。引用類型在複製的時候其實複製的是指針。
深淺拷貝
- 淺拷貝:僅僅是複製了引用,彼此之間的操作會互相影響
- 深拷貝:在堆中重新分配內存,不同的地址,相同的值,互不影響
淺拷貝
創建一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內存地址 ,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。實現方式如下
實現
- 遍歷賦值實現
- ES6擴展運算符
- ES6方法Object.assign()
- 數組方法(只適用於類數組對象)
Array.from(arrayLike)
Array.prototype.concat()
Array.prototype.slice()
遍歷賦值實現
var obj = { a:1, arr: [2,3] }; //淺拷貝實現 for (var prop in obj){ if(obj.hasOwnProperty(prop)){ shallowObj[prop] = obj[prop]; } }
ES6擴展運算符
var obj = { a:1, arr: [2,3] }; var obj1 = {...obj}
ES6方法Object.assign()
var obj = { a:1, arr: [2,3] }; var obj1 = Object.assign({}, obj);
數組方法(僅適用於類數組對象)
Array.from(arrayLike)
var array1 = ['a', ['b', 'c'], 'd']; var array2 = Array.from(array1);
Array.prototype.concat()
var array1 = ['a', ['b', 'c'], 'd']; var array2 = array1.concat([1,2]);
Array.prototype.slice()
var array1 = ['a', ['b', 'c'], 'd']; var array2 = array1.slice(0,2);
引用賦值:地址的賦值,將對象指針賦值給一個變量,讓此變量指向對象
深拷貝
將一個對象從內存中完整的拷貝一份出來,從堆內存中開闢一個新的區域存放新對象,且修改新對象不會影響原對象
實現方式如下:
- JSON.parse()和JSON.stringify()
- 遞歸
JSON.parse()和JSON.stringify()
let parseObject = { a: { b: 1 } } let cloneParseObject = JSON.parse(JSON.stringify(parseObject)); parseObject.a.b = 2; console.log('cloneParseObject'); console.log(cloneParseObject);
缺陷:
- 會忽略 undefined
- 會忽略 symbol
- 無法對function進行處理 需要確認.
- 不能解決循環引用的對象
遞歸(簡單版)
// 深拷貝 function cloneDeep(obj) { if (!obj && typeof obj !== 'object') { throw new Error('錯誤參數'); } const targetObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { //只對對象自有屬性進行拷貝 if (obj.hasOwnProperty(key)) { if (obj[key] && typeof obj[key] === 'object') { targetObj[key] = cloneDeep(obj[key]); } else { targetObj[key] = obj[key]; } } } return targetObj; }
關鍵點
- 判斷參數類型
- 判斷是否是數組
- for in遍歷
- 判斷是否是自有對象
- 判斷子屬性是否是對象,是對象則遞歸
遞歸(複雜版)
const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const objectTag = '[object Object]'; const argsTag = '[object Arguments]'; const boolTag = '[object Boolean]'; const dateTag = '[object Date]'; const numberTag = '[object Number]'; const stringTag = '[object String]'; const symbolTag = '[object Symbol]'; const errorTag = '[object Error]'; const regexpTag = '[object RegExp]'; const funcTag = '[object Function]'; const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]; function forEach(array, iteratee) { let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array; } function isObject(target) { const type = typeof target; return target !== null && (type === 'object' || type === 'function'); } function getType(target) { return Object.prototype.toString.call(target); } function getInit(target) { const Ctor = target.constructor; return new Ctor(); } function cloneSymbol(targe) { return Object(Symbol.prototype.valueOf.call(targe)); } function cloneReg(targe) { const reFlags = /\w*$/; const result = new targe.constructor(targe.source, reFlags.exec(targe)); result.lastIndex = targe.lastIndex; return result; } function cloneFunction(func) { const bodyReg = /(?<={)(.|\n)+(?=})/m; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); } } function cloneOtherType(targe, type) { const Ctor = targe.constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(targe); case regexpTag: return cloneReg(targe); case symbolTag: return cloneSymbol(targe); case funcTag: return cloneFunction(targe); default: return null; } } function clone(target, map = new WeakMap()) { // 克隆原始類型 if (!isObject(target)) { return target; } // 初始化 const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target, type); } else { return cloneOtherType(target, type); } // 防止循環引用 if (map.get(target)) { return map.get(target); } map.set(target, cloneTarget); // 克隆set if (type === setTag) { target.forEach(value => { cloneTarget.add(clone(value, map)); }); return cloneTarget; } // 克隆map if (type === mapTag) { target.forEach((value, key) => { cloneTarget.set(key, clone(value, map)); }); return cloneTarget; } // 克隆對象和數組 const keys = type === arrayTag ? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget; } module.exports = { clone };
示例代碼
參考