在業務中常常會遇到須要對模板數據/初始數據進行加工處理,並且,該數據可能能須要在多處被複用。面試
簡單粗暴更改原始數據的作法,會污染其餘依賴項,因此咱們須要對原始數據進行一份深拷貝,保持各個依賴項數據的獨立性,作到既能夠複用,又不互相污染。segmentfault
也許咱們用到過Object.assign()進行對象合併,亦或者用數組的slice(), concat()方法對數組進行復制,但這幾種方法都不是深拷貝,是淺拷貝。數組
簡言之,深拷貝就是每一個對象都是獨立的,獨立意味着擁有各自的獨立內存空間;而淺拷貝意味着擁有公共的引用空間。函數
因此,值類型的數據不存在淺拷貝的問題,只有引用數據類型才存在。願更深刻的瞭解深拷貝和淺拷貝的,能夠參考文章最末尾的「參考文章」。post
let recursiveClone = val => Array.isArray(val) ? Array.from(val, recursiveClone) : val;
概述JS的數據類型,從大體上分爲兩種:prototype
對於基本數據類型,不存在深拷貝的問題,由於它們是值類型的數據。值類型的數據存放在棧內存中,從新賦值就是獨立的。code
而對於衆多的引用數據類型,須要分別進行處理,集中處理object和array,這也是咱們在業務中遇到最多的狀況。對象
const deepCloneTypes = ['Object', 'Array', 'Map', 'Set']; function isObject(source) { const type = typeof source; return source !== null && (type === 'object' || type === 'function') } function getType(source) { return (Object.prototype.toString.call(source)).split(' ')[1].slice(0, -1) } function processOtherType(source) { const Ctor = source.constructor; return new Ctor(source); } function processFunctionType (source) { let _source = source.toString(); // 區分是不是箭頭函數 if (source.prototype) { // 若是有prototype就是普通函數 let argsReg = /function\s*\w*\(([^\)]*)\)/; let bodyReg = /\{([\s\S]*)\}/; let fnArgs = (argsReg.exec(source))[1]; let fnBody = (bodyReg.exec(source))[1]; console.log(fnArgs, fnBody); return new Function (fnArgs, fnBody) } else { // 箭頭函數沒有prototype return eval(_source) } } function deepClone (source, map = new WeakMap()) { // 首先用typeof來篩選是不是引用數據類型,若是連引用數據類型都不是,那麼就判斷是基本數據類型,直接返回便可 if (!isObject(source)) { return source } const type = getType(source); let cloneTarget; // 防止循環引用 if (map.get(source)) { return map.get(source); } map.set(source, cloneTarget); // 接下來判斷是不是須要進行循環拷貝的引用數據類型,諸如new Boolean, new Number這樣的,也不須要循環拷貝 if (!deepCloneTypes.includes(type)) { cloneTarget = processOtherType(source); } else { cloneTarget = new source.constructor(); // return Object.create(source.constructor.prototype) //不能這樣,這樣是創造了一個對象 if (type === 'Object' || type === 'Array') { let keys = type === 'Object' ? Object.keys(source) : undefined; // 若是支持optional chaining的話能夠寫成?. (keys || source).forEach((val, key) => { if (keys) { key = val; } cloneTarget[key] = deepClone(source[key], map); //在這裏進行遞歸調用 }) } if (type === 'Function') { cloneTarget = processFunctionType(source) } if (type === 'Map') { source.forEach((val, key) => { cloneTarget.set(key, deepClone(val, map)) }) } if (type === 'Set') { source.forEach((val, key) => { cloneTarget.add(deepClone(val, map)) }) } } return cloneTarget }
參考:遞歸