本文github地址,欢送star
在平时开发中,个别解决两种数据类型:
- 根本数据类型
- 援用数据类型
根本数据类型存在栈中,援用数据类型寄存在堆中。
在谈到深浅拷贝时,也是围绕这两种数据类型开展的。
浅拷贝
创立一个对象,从新复制或援用的源对象的值。
- 如果对象属性是根本的数据类型,复制的就是根本类型的值给新对象。
- 如果属性是援用数据类型,拷贝进去的指标对象的指针和源对象的指针指向的内存空间是同一块空间, 批改源对象同时也会对指标对象产生影响。
JS
提供了如下办法来对象和数据进行浅拷贝。
开展运算符
能够在函数调用/数组结构时, 将数组表达式或者 string
在语法层面开展;还能够在结构字面量对象时, 将对象表达式按 key-value
的形式开展。
// 数据的拷贝let arr = [1, 2, 3, 4, { a: 1}]let arrCopy = [...arr]arrCopy[4].a = 100console.log(arrCopy[4].a) // 100console.log(arr[4].a) // 100// 对象的拷贝let obj = { a: 1, b: { c: 3 }}console.log(obj.b.c) // 3let objCopy = { ...obj}objCopy.b.c = 4console.log(obj.b.c) // 4console.log(objCopy.b.c) // 4
不难发现数据类型都是根本类型,应用开展运算符拷贝十分不便。
Object.assign
Object.assign
办法用于将所有可枚举属性的值从一个或多个源对象调配到指标对象。它将返回指标对象。
let t = {}let s = { a: { b: 100 }}Object.assign(t, s)console.log(s.a.b) // 100t.a.b = 200console.log(s.a.b) // 200console.log(t.a.b) // 200
须要留神的几个中央
- 不会拷贝对象的继承属性
- 不会拷贝对象的不可枚举的属性
- 能够拷贝 Symbol 类型的属性
Object.assign
会遍历原对象的属性,通过复制的形式将其赋值给指标对象的相应属性(包含 Symbol 类型的对象)
手写实现浅拷贝
对于根本类型间接进行拷贝复制,对于援用类型,在内存中开拓一块新的空间进行拷贝复制。
const isObject = (obj) => typeof obj === 'object' && obj !== null;const shallowClone = (obj) => { if (!is(obj)) return obj const cloneObj = Array.isArray(obj) ? [] : {}; for (let prop in obj) { if (obj.hasOwnProperty(prop)) { cloneObj[prop] = obj[prop]; } } return cloneObj;}
深拷贝
将一个对象从内存中残缺地拷贝进去一份给指标对象,并从堆内存中开拓一个全新的空间寄存新对象,且新对象的批改并不会扭转原对象,二者实现真正的拆散。
JSON.stringfy
JSON.stringfy
是前端中最简略的深拷贝形式,把对象序列化成为 JSON
的字符串,并将对象外面的内容转换成字符串,再用 JSON.parse
将 JSON
字符串生成一个新的对象。
let obj1 = { a: 1, b: [1, 2, 3]}let str = JSON.stringify(obj1);let obj2 = JSON.parse(str);console.log(obj2); //{a:1,b:[1,2,3]} obj1.a = 2;obj1.b.push(4);console.log(obj1); //{a:2,b:[1,2,3,4]}console.log(obj2); //{a:1,b:[1,2,3]}
然而 JSON.stringfy
在深拷贝会呈现以下的问题:
- 拷贝的对象的值中如果有函数、
undefined
、symbol
这几种类型,通过JSON.stringify
序列化之后的字符串中这个键值对会隐没。 - 拷贝
Date
援用类型会变成字符串。 - 无奈拷贝不可枚举的属性。
- 无奈拷贝对象的原型链。
- 拷贝
RegExp
援用类型会变成空对象。 - 对象中含有
NaN
、Infinity
以及-Infinity
,JSON
序列化的后果会变成null
。 - 无奈拷贝对象的循环利用。
let testObj = { [Symbol('test')]: 1, number: -1, string: 'Hello World', boolean: true, undefined: undefined, nul: null, obj: { name: '我是一个对象', id: 1 }, list: [11, 22, 33], fn: function() { console.log('Hello World') }, date: new Date(), regExp: new RegExp('/[\w]/ig'), }; Object.defineProperty(testObj, 'innumerable', { enumerable: false, value: 'innumerable' }); obtestObjj = Object.create(testObj, Object.getOwnPropertyDescriptors(testObj)) // 设置循环援用, 不能拷贝,将会报错 // testObj.loop = testObj let cloneObj = JSON.parse(JSON.stringify(testObj)) cloneObj.list.shift() console.log('testObj', testObj) console.log('cloneObj', cloneObj)
手写深拷贝
对于上述 JSON.stringfy
毛病,在实现深拷贝进行如下的改善:
- 当对象为
Date
、RegExp
类型,间接返回一个新的实例。 - 对象的不可枚举和
symbol
属性,采纳Reflect.ownKeys
。Reflect.ownKeys
办法用于返回对象的所有属性,根本等同于Object.getOwnPropertyNames
与Object.getOwnPropertySymbols
之和。 - 无奈拷贝对象的原型链对象,能够
Object.getPrototypeOf
获取对象的原型对象,以及对象的属性形容对象Object.getOwnPropertyDescriptors
。 - 对于循环援用,能够采纳
WeakMap
,WeakMap
是弱援用类型,能够无效避免内存透露。
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)const deepClone = function(obj, hash = new WeakMap()) { // 解决 RegExp 对象 if (obj.constructor === RegExp) return new RegExp(obj) // 解决 Date 对象 if (obj.constructor === Date) return new Date(obj) // 解决循环援用 if (hash.has(obj)) return hash.get(obj) // 获取属性的形容器 let allDesc = Object.getOwnPropertyDescriptors(obj) // 创立新的对象,设置原型链 let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc) // 解决循环援用 hash.set(obj, cloneObj) // Reflect.ownKeys 能够获取不可枚举和 symbol 属性 for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = (isComplexDataType(obj[key]) ? deepClone(obj[key], hash) : obj[key] } return cloneObj }}
接下来深拷贝测试
// 测试 对象let testObj = { [Symbol('test')]: 1, number: -1, string: 'Hello World', boolean: true, undefined: undefined, nul: null, obj: { name: '我是一个对象', id: 1 }, list: [11, 22, 33], fn: function() { console.log('Hello World') }, date: new Date(), regExp: new RegExp('/[\w]/ig'),};Object.defineProperty(testObj, 'innumerable', { enumerable: false, value: 'innumerable'});obtestObjj = Object.create(testObj, Object.getOwnPropertyDescriptors(testObj))// 设置循环援用testObj.loop = testObjlet cloneObj = deepClone(testObj)cloneObj.list.shift()console.log('cloneObj', testObj)console.log('cloneObj', cloneObj)
能够看出解决了 JSON.stringfy
在进行深拷贝的时候的毛病。
总结
在平时开发过程,咱们能够采纳第三方库来实现深拷贝,比方 loadsh.cloneDeep
。