1:對數據進行備份的時候,如果這個數據是基本的數據類型,那么很好辦,通過賦值實現復制即可。
賦值與淺拷貝的區別 var obj1 = { 'name' : 'zhangsan', 'age' : '18', 'language' : [1,[2,3],[4,5]], }; var obj2 = obj1; //賦值得到的對象 var obj3 = shallowCopy(obj1); //通過淺拷貝得到的對象 function shallowCopy(src) { var dst = {}; for (var prop in src) { if (src.hasOwnProperty(prop)) { dst[prop] = src[prop]; } } return dst; } obj2.name = "lisi"; //影響obj1 obj3.age = "20"; //不影響obj1 obj2.language[1] = ["二","三"]; //影響obj1 obj3.language[2] = ["四","五"]; //影響obj1 console.log(obj1); //obj1 = { // 'name' : 'lisi', // 'age' : '18', // 'language' : [1,["二","三"],["四","五"]], //}; console.log(obj2); //obj2 = { // 'name' : 'lisi', // 'age' : '18', // 'language' : [1,["二","三"],["四","五"]], //}; console.log(obj3); //obj3 = { // 'name' : 'zhangsan', // 'age' : '20', // 'language' : [1,["二","三"],["四","五"]], //}; 注釋:這是因為淺拷貝只復制一層對象的屬性,並不包括對象里面的為引用類型的數據。所以就會出現改變淺拷貝得到的 obj3 中的引用類型時,會使原始數據得到改變
深拷貝方法1:JSON.parse(JSON.stringify(obj)) let obj1 = { name:'pan' } let obj2 = JSON.parse(JSON.stringify(obj1)) obj2.name = 'rui' console.log(obj1) //{name:pan} console.log(obj2) //{name:rui} console.log(obj1 === obj2) //false //缺點:對象必須遵從JSON的格式 let obj1 = { a: '1', b: '2', c: function func() {} } let obj4 = JSON.parse(JSON.stringify(obj1)) console.log(obj4) //{ a: '1', b: '2' } 好像漏了些什么
深拷貝方法2:Object.assign(target, …sources) //Object.assign() 方法用於將所有可枚舉的屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象 // 復制 let c = Object.assign({}, { a: 'apple' }); console.log(c); // { a: 'apple' } //合並 let o = {} let c = Object.assign(o, { a: 'apple' }, { b: 'banana' }, { c: 'cake' } ) console.log(c) // { a: 'apple', b: 'banana', c: 'cake' } //如果對象本身存在的屬性會更新,不存在的屬性會增加 let o = { name:'pan' } let oo = { name:'rui', id:100 } let c = Object.assign(o, oo); console.log(o); //{name:'rui',id:100} console.log(oo);//{name:'rui',id:100} console.log(c);//{name:'rui',id:100}
// 判斷是否為對象 function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } //測試用例需要的對象 let test = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '我是一個對象', id: 1 }, arr: [0, 1, 2], func: function() { console.log('我是一個函數') }, date: new Date(0), reg: new RegExp('/我是一個正則/ig'), err: new Error('我是一個錯誤') }
深度拷貝方法3:迭代遞歸法 for...in // 迭代遞歸法:深拷貝對象與數組 function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一個對象!') } //判斷傳進來的是對象還是數組 let isArray = Array.isArray(obj) let cloneObj = isArray ? [] : {} //通過for...in來拷貝 for (let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] } return cloneObj } let result = deepClone(test) console.log(result) for (let key in result) { if (isObject(result[key])) console.log(`${key}相同嗎? `, result[key] === test[key]) } //我們發現,arr 和 obj 都深拷貝成功了,它們的內存引用已經不同了,但 func、date、reg 和 err 並沒有復制成功,因為它們有特殊的構造函數。
深度拷貝方法4:迭代遞歸法 Reflect // 代理法 function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一個對象!') } let isArray = Array.isArray(obj) let cloneObj = isArray ? [...obj] : { ...obj } Reflect.ownKeys(cloneObj).forEach(key => { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return cloneObj } let result = deepClone(test) console.log(result) for (let key in result) { if (isObject(result[key])) console.log(`${key}相同嗎? `, result[key] === test[key]) } //結果其實和使用for...in相同。
2 lodash 中的深拷貝 https://github.com/lodash/lodash/blob/master/.internal/baseClone.js
let result = _.cloneDeep(test)
我們發現,arr、obj、date、reg深拷貝成功了,但 func 和 err 內存引用仍然不變 //這跟 lodash 中的 cloneableTags 有關
3lodash自定義拷貝對象
function customizer(value) { if (_.isElement(value)) { return value.cloneNode(true); } } var el = _.cloneDeepWith(document.body, customizer); console.log(el === document.body); // => false console.log(el.nodeName); // => 'BODY' console.log(el.childNodes.length); // => 20
4小姐姐,在了解一下
4.1:對象成環,當我們使用上述方法(for...in 與Reflect都會出現棧溢出的錯誤,但是lodash卻可以)
注意:因為 lodash 使用的是棧把對象存儲起來了,如果有環對象,就會從棧里檢測到,從而直接返回結果,懸崖勒馬。這種算法思想來源於 HTML5 規范定義的結構化克隆算法,它同時也解釋了為什么 lodash 不對 Error 和 Function 類型進行拷貝。
4.2:鍵不是字符串而是 Symbol
使用for...in拷貝的時候就會拷貝失敗,因為 Symbol 是一種特殊的數據類型,它最大的特點便是獨一無二,所以它的深拷貝就是淺拷貝
改造for...in function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一個對象!') } let isArray = Array.isArray(obj) let cloneObj = isArray ? [] : {} let symKeys = Object.getOwnPropertySymbols(obj) // console.log(symKey) if (symKeys.length > 0) { symKeys.forEach(symKey => { cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey]) : obj[symKey] }) } for (let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] } return cloneObj }
使用Reflect是可以的
4.3 for...in會拷貝原型上面的屬性,對於下面代碼,只有for..in能夠成功,而其他的方法都不能追蹤到原型上面
let childTest = Object.create(test)
let result = deepClone(childTest)
4.4 對象上的屬性又分為可枚舉屬性和不可枚舉屬性,如何拷貝不可枚舉的屬性呢?
//定義對象 Object.defineProperties(test, { 'obj': { writable: false, enumerable: false, configurable: false }, 'arr': { get() { console.log('調用了get') return [1,2,3] }, set(val) { console.log('調用了set') } } }) function deepClone(obj, hash = new WeakMap()) { if (!isObject(obj)) { return obj } // 查表,防止循環拷貝 if (hash.has(obj)) return hash.get(obj) let isArray = Array.isArray(obj) // 初始化拷貝對象 let cloneObj = isArray ? [] : {} // 哈希表設值 hash.set(obj, cloneObj) // 獲取源對象所有屬性描述符 let allDesc = Object.getOwnPropertyDescriptors(obj) // 獲取源對象所有的 Symbol 類型鍵 let symKeys = Object.getOwnPropertySymbols(obj) // 拷貝 Symbol 類型鍵對應的屬性 if (symKeys.length > 0) { symKeys.forEach(symKey => { cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey], hash) : obj[symKey] }) } // 拷貝不可枚舉屬性,因為 allDesc 的 value 是淺拷貝,所以要放在前面 cloneObj = Object.create( Object.getPrototypeOf(cloneObj), allDesc ) // 拷貝可枚舉屬性(包括原型鏈上的) for (let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key]; } return cloneObj }
總結:微笑中透露着貧窮