JS中深拷贝的实现
- JSON.parse(JSON.stringify())
- 递归实现深拷贝
使用
JSON.parse(JSON.stringify())
实现 无法拷贝 函数、正则、时间格式、原型上的属性和方法等
递归实现深拷贝
es5实现深拷贝
- 源对象
const obj = {
name: '张桑',
age: 18,
hobby: [
{
name: '篮球',
year: 5,
loveStar: ['Luka', 'Curry', 'Dragic']
}
],
run: function () {
return 'xxxx'
}
}
- deepClone实现
// 深拷贝(es5实现)
function deepClone (origin, target) {
// 1.判断传入的origin 是否存在
var tar = target || {}
// 5.toString是 object对象原型上的方法 Object.prototype.toString.call({}) => [object Object]
var toStr = Object.prototype.toString
var arryType = '[obeject Array]'
// 2.遍历对象
for (var k in origin) {
// 4.这里不能拷贝其原型上的属性
if (origin.hasOwnProperty(k)) {
// 3.判断k是否为对象 且不能为null typeof null === 'object'
if (typeof origin[k] === 'object' && origin[k] !== null) {
// 6. 这里已经判断origin[k] 是object 只需要判断是{} 还是 []
tar[k] = toStr.call(origin[k]) === arryType ? [] : {}
deepClone(origin[k], tar[k])
} else {
tar[k] = origin[k]
}
}
}
return tar
}
const newObj = deepClone(obj, {})
obj.age = 19
console.log(newObj)
实现思路
1. 先判断传入的origin是否存在
2. 存在就遍历该对象
3. 通过hasOwnProperty(k)
判断是否是其原型上的属性,不拷贝其原型上的属性
4. 判断k是否为对象 且不能为null typeof null === 'object'
5. 通过是 object对象原型上的方法 toString
判断k是{}
还是[]
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
6.再进行递归拷贝deepClone(origin[k], tar[k])
es6实现深拷贝
// null undefined 和 不是对象的数据 直接返回
// 封装一个判断方法
function isObject(origin) {
return typeof origin === "object" && origin !== null;
}
function deepClone (origin) {
if (!isObject(origin)) return origin
// 还需要考虑的 Date RegExp 等 可以通过instanceof 判断其构造函数
if (origin instanceof Date) {
return new Date(origin)
}
if (origin instanceof RegExp) {
return new RegExp(origin)
}
// 处理 {} []
// 通过构造器判断它是否为对象、 数组
/**
* const obj = {}
* const newObj = new obj.constructor() // 相当于拷贝了origin
* 这样就不需要判断origin是对象还是数组
*/
const target = new origin.constructor()
for (let k in origin) {
if (origin.hasOwnProperty(k)) {
target[k] = deepClone(origin[k])
}
}
return target
}
const newObj = deepClone(obj)
console.log(newObj)
let target = {name: 'target'};
target.target = target
const newObj = deepClone(target)
console.log(newObj)
实现思路
- 这里首先封装了一个方法用来 null、 undefined 和 不是对象的数据,这些数据直接可以返回
function isObject(origin) {
return typeof origin === "object" && origin !== null;
}
// deepClone中判断
if (!isObject(origin)) return origin
- 针对
Date
、RegExp
等 可以通过instanceof
判断其构造函数来区分,做特殊处理。
if (origin instanceof Date) {
return new Date(origin)
}
if (origin instanceof RegExp) {
return new RegExp(origin)
}
- es5方法中是通过是 object对象原型上的方法
toString
判断k是{}
还是[]
,这里可以使用new 其对象的构造方法则不需要判断origin是对象还是数组
const target = new origin.constructor() // 相当于拷贝了origin
- 遍历origin,递归拷贝
origin[k]
使用WeakMap优化
问题
- 循环拷贝(对象的属性引用自己) 问题
使用上面es6 的deepClone方法,直接栈溢出了
let target = {name: 'target'};
target.target = target
const newObj = deepClone(target)
console.log(newObj)
2. 重复拷贝(对象的属性引用同一个对象) 问题
let obj = {};
let target = {a: obj, b: obj};
这里拷拷贝a的时候拷贝了obj,拷贝b的时候又拷贝了一次
// 深拷贝 (WeakMap)
// 1. 解决循环拷贝(对象的属性引用自己) 问题
// 2. 解决重复拷贝(对象的属性引用同一个对象) 问题
// WeakMap 防止内存泄漏 键名如果外部没有引用,键值键名就会被内存回收掉
function isObject(origin) {
return typeof origin === "object" && origin !== null;
}
function deepClone (origin, hashMap = new WeakMap()) {
if (!isObject(origin)) return origin
// 还需要考虑的 Date RegExp 等 可以通过instanceof 判断其构造函数
if (origin instanceof Date) {
return new Date(origin)
}
if (origin instanceof RegExp) {
return new RegExp(origin)
}
const hashKey = hashMap.get(origin)
if (hashKey) {
return hashKey
}
// 处理 {} []
// 通过构造器判断它是否为对象、 数组
/**
* const obj = {}
* const newObj = new obj.constructor() // 相当于拷贝了obj
* 这样就不需要判断origin是对象还是数组
*/
const target = new origin.constructor()
hashMap.set(origin,target)
for (let k in origin) {
if (origin.hasOwnProperty(k)) {
target[k] = deepClone(origin[k], hashMap)
}
}
return target
}
let target = {name: 'target'};
target.target = target
const newObj = deepClone(obj)
console.log(newObj)
实现思路
这里给deepClone 方法加了一个参数,默认是new WeakMap()
,利用WeakMap键名如果外部没有引用,键值键名就会被内存回收掉的特性,记录origin是否被拷贝过,拷贝过则直接返回
const hashKey = hashMap.get(origin) // 查询是否有origin这个键
if (hashKey) {
return hashKey
}
hashMap.set(origin,target) // 拷贝前将其存起来