JS对象的拷贝
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 }
总结:微笑中透露着贫穷
不忘初心,不负梦想