深拷贝
1:
// 循环递归法1 function isObject(obj) { return (typeof obj === 'object' || typeof obj === 'function') && obj !== null } // 迭代递归法:深拷贝对象与数组 function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一个对象!') } let isArray = Array.isArray(obj) let cloneObj = isArray ? [] : {} for (let key in obj) { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] } return cloneObj } let sym = Symbol('我是一个Symbol') let obj1 = { a: { b: 1, }, } obj1[sym] = 111 let obj2 = deepClone(obj1) console.log(obj1) console.log(obj2) /* 深拷贝是针对引用类型的, 在进行深拷贝之前, 我们应该先知道js中有哪些引用类型, js中引用类型目前有六种: object, array, date, regexp, function, err。 下面的两种方法只能实现object, array的深拷贝。 循环递归法 function、Date、RegExp 和Error无法复制 因为它们有特殊的构造函数。 */
2:
// 循环递归法2 function isObject(o) { return (typeof o === 'object' || typeof o === 'function') && o !== null } function deepClone(obj) { if (!isObject(obj)) { throw new Error('obj 不是一个对象!') } let isArray = Array.isArray(obj) let cloneObj = isArray ? [...obj] : { ...obj } // Reflect.ownKeys 返回对象的所有属性 Reflect.ownKeys(cloneObj).forEach(key => { cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] }) return cloneObj } let sym = Symbol('我是一个Symbol') let obj1 = { a: { b: 1, }, } obj1[sym] = 111 let obj2 = deepClone(obj1) console.log(obj1) console.log(obj2)
3:
// 序列化反序列化法 使用JSON对象的parse和stringify方法来实现深拷贝 function deepClone(obj) { let _obj = JSON.stringify(obj), objClone = JSON.parse(_obj); return objClone } let obj1 = { a: { b: 1 } }; Object.defineProperty(obj1, 'innumerable', { value: '不可枚举属性', enumerable: false }); let obj2 = deepClone(obj1) console.log(obj1) console.log(obj2) // 它也只能深拷贝对象和数组,对于其他种类的对象,会失真。 // 这种方法比较适合平常开发中使用,因为通常不需要考虑对象和数组之外的类型。 // 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失 // 无法拷贝不可枚举的属性, 无法拷贝对象的原型链 // 拷贝Date引用类型会变成字符串 // 拷贝RegExp引用类型会变成空对象 // 对象中含有NaN、 Infinity和 - Infinity, 则序列化的结果会变成null // 无法拷贝对象的循环应用(即obj[key] = obj)
4:
// 3. lodash中深拷贝的实现 // Lodash是一个轻量级的JavaScript工具函数库, 它方便了日常开发中对数据的操作, 提高了开发效率。 // 著名的 lodash 中的 cloneDeep 方法同样是使用 Reflect 法 实现的, // 只不过它支持的对象种类更多, 具体的实现过程读者可以参考 lodash 的 baseClone 方法。 // lodash可以完成array、 object、 date、 regexp的深拷贝, 但 // function 和 error 仍然不可拷贝 // https://github.com/lodash/lodash/blob/master/.internal/baseClone.js // 日常开发中,通常会对数据,特别是数组和对象进行各种读写等操作: // 比如去重,拷贝,合并,过滤,求交集,求和等等。根据平时开发中对数据的操作, const _ = require('lodash') const obj = { a: 1 } obj.loopObj = obj const obj1 = _.cloneDeep(obj) console.log(obj1 === obj)
5:
/* 对象成环怎么办? 我们给 test 加一个 loopObj 键,值指向自身: test.loopObj = test 这时我们使用第一种方法中的 for..in 实现和 Reflect 实现都会栈溢出: 环对象深拷贝报错 而使用第二种方法也会报错: 但 lodash 却可以得到正确结果: 因为 lodash 使用的是栈把对象存储起来了,如果有环对象,就会从栈里检测到, 从而直接返回结果,悬崖勒马。这种算法思想来源于 HTML5 规范定义的结构化克隆算法, 它同时也解释了为什么 lodash 不对 Error 和 Function 类型进行拷贝。 当然,设置一个哈希表存储已拷贝过的对象同样可以达到同样的目的: */ 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 result = Object.keys(obj).map(key => { return { [key]: deepClone(obj[key], hash) } }) return Object.assign(cloneObj, ...result) } //这里我们使用 WeakMap 作为哈希表,因为它的键是弱引用的,而我们这个场景里键恰好是对象,需要弱引用。
6:
// 键值不是字符串而是 Symbol var test = {} let sym = Symbol('我是一个Symbol') test[sym] = 'symbol' let result = deepClone(test) console.log(result) console.log(result[sym] === test[sym]) // 拷贝失败了, 为什么? // 因为 Symbol 是一种特殊的数据类型, 它最大的特点便是独一无二, 所以它的深拷贝就是浅拷贝
7:
/* 但如果这时我们使用 Reflect 实现的版本: 成功了,因为 for...in 无法获得 Symbol 类型的键,而 Reflect 是可以获取的。 当然,我们改造一下 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 }
8:
// for...in 会追踪原型链上的属性, // 而其它三种方法(Object.keys、Reflect.ownKeys 和 JSON 方法)都不会追踪原型链上的属性:9:
// 需要拷贝不可枚举的属性 // 第四种情况, 就是我们需要拷贝类似属性描述符, setters 以及 getters 这样不可枚举的属性, 一般来说, 这就需要一个额外的不可枚举的属性集合来存储它们。 类似在第二种情况使用 // for... in 拷贝 Symbol 类型键时: // 我们给 test 变量里的 obj 和 arr 属性定义一下属性描述符: 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 }
10:
/* 1. 日常深拷贝,建议序列化反序列化方法。 2. 面试时遇见面试官搞事情,写一个能拷贝自身可枚举、自身不可枚举、自身 Symbol 类型键、原型上可枚举、原型上不可枚举、原型上的 Symol 类型键,循环引用也可以拷的深拷贝函数: 3. 有特殊需求的深拷贝,建议使用 lodash 的 copyDeep 或 copyDeepWith 方法。 */ // 将之前写的 deepClone 函数封装一下 function cloneDeep(obj) { let family = {} let parent = Object.getPrototypeOf(obj) while (parent != null) { family = completeAssign(deepClone(family), parent) parent = Object.getPrototypeOf(parent) } // 下面这个函数会拷贝所有自有属性的属性描述符,来自于 MDN // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign function completeAssign(target, ...sources) { sources.forEach(source => { let descriptors = Object.keys(source).reduce((descriptors, key) => { descriptors[key] = Object.getOwnPropertyDescriptor(source, key) return descriptors }, {}) // Object.assign 默认也会拷贝可枚举的Symbols Object.getOwnPropertySymbols(source).forEach(sym => { let descriptor = Object.getOwnPropertyDescriptor(source, sym) if (descriptor.enumerable) { descriptors[sym] = descriptor } }) Object.defineProperties(target, descriptors) }) return target } return completeAssign(deepClone(obj), family) }
浅拷贝
1:
let target = {} let obj = { a: { b: 3 } }; Object.assign(target, obj); console.log(target); //{a:1} obj.a.b = 1; console.log(obj); //{a:2} console.log(target); //{a:1}
2:
let obj1 = { a: { b: 1 }, sym: Symbol(1) }; Object.defineProperty(obj1, 'innumerable', { value: '不可枚举属性', enumerable: false }); let obj2 = {}; Object.assign(obj2, obj1) obj1.a.b = 2; console.log('obj1', obj1); console.log('obj2', obj2);
3:
let obj = { a: 1, b: { c: 1 } } let obj2 = { ...obj }; obj.a = 2; console.log(obj); //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}} obj.b.c = 2; console.log(obj); //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
标签:obj,实现,Object,let,key,console,拷贝,方法 From: https://www.cnblogs.com/z-bky/p/17033800.html