Header
前几天为了暑期实习又和腾讯的面试官对线了一波,大佬上来直接让我手写一个深拷贝,这不就是想考察我对于JS数据类型和递归算法的一些理解嘛。虽然自己没特意写过,但大概还是知道一些思路什么的,用递归和类型判断稍微写了个简单的深拷贝。
接下来面试官就开始了例行优化,我也按照他的一些建议在结束后优化了下,其中还是有很多值得去学习的点,下面内容就是一个逐步优化的过程。
Content
当时写的是一个比较简陋的版本,今天试着把它写得尽量完善点。关于用Object.assign()或者json序列化的什么优缺点啥的本文不再赘述,大家不应该不会吧~(阴阳怪气)
version 1.0
/**
* @param {*} obj 需要拷贝的数据
* @return {*} 拷贝之后的数据
**/
var cloneObj = function (obj) {
// 先判断是不是null和基本数据类型
if (obj === null || typeof obj !== 'object') {
return obj
}
// 然后判断数组和对象,递归赋值就行
let result = Array.isArray(obj) ? [] : {}
for (let item in obj) {
result[item] = cloneObj(obj[item])
}
return result
}
这个版本就是非常基础的版本,里面其实还有很多很多细节需要完善,因为传的是个对象,对象里面可能有一些正则或者函数,或者是拷贝循环引用的时候报错,又或者是ES6中的一些复杂类型的数据结构:Set、WeakSet、Map、WeakMap,接下来我们就一步步地完善。
version 2.0
我们先来解决循环调用的问题,循环调用就像是套娃,对象一直调用自己,导致函数也一直递归调用,最终内存溢出报错。
var obj = {a: 1}
obj.b = obj
cloneObj(obj)
这个解决思路的话就是保存参数作为判断嘛,可以用WeakMap把之前遍历的值都存储下来,如果有相同的,那么就直接返回对象,不去递归了。这种场景下WeakMap比Map存键值对更加好一点,可以节省内存。
/**
* @param {*} obj 需要拷贝的数据
* @return {*} 拷贝之后的数据
**/
var wm = new WeakMap()
var cloneObj = function (obj) {
// 先判断是不是null和基本数据类型
if (obj === null || typeof obj !== 'object') {
return obj
}
let result = Array.isArray(obj) ? [] : {}
// 判断该值是否在WeakMap中
let existObj = wm.get(obj)
if (existObj) {
return existObj
}
wm.set(obj, result)
// 然后判断数组和对象,递归赋值就行
for (let item in obj) {
result[item] = cloneObj(obj[item])
}
return result
}
这样的话就成功解决循环引用的问题了,亲测chrome调试台能够拷贝。当然还有就是object的Date、RegExp、Function、Math这些对象的处理了,这时候就需要ES5判断类型的方法了。
version 3.0
/**
* @param {*} obj 需要拷贝的数据
* @return {*} 拷贝之后的数据
**/
var wm = new WeakMap()
var cloneObj = function (obj) {
// 先判断是不是null和基本数据类型或者其他对象类型
if (obj === null || typeof obj !== 'object' || Object.prototype.toString.call(obj) !== "[object Object]") {
return obj
}
let result = Array.isArray(obj) ? [] : {}
// 判断该值是否在WeakMap中
let existObj = wm.get(obj)
if (existObj) {
return existObj
}
wm.set(obj, result)
// 然后判断数组和对象,递归赋值就行
for (let item in obj) {
result[item] = cloneObj(obj[item])
}
return result
}
亲测chrome调试台能够拷贝除了Math对象以外的任何对象,有个特例这怎么能忍,这可是完美copy,赶紧瞅了瞅Math对象的数据结构,发现原来里面内嵌了很多方法,那就直接再做一轮判断。尝试了一波,发现好像还是拷贝的引用......
算了我忍了,能想到的方法都试了一遍,还是只能拷贝一个引用,可能这就是内置对象的魅力吧。
version 4.0
目前还有就是Set、WeakSet、Map、WeakMap这四种复杂类型数据结构的拷贝,目前的思路还是用ES5的方法去判断类型,然后再执行相应逻辑。像这种键值对的数据结构就得换一种赋值操作了。
/**
* @param {*} obj 需要拷贝的数据
* @return {*} 拷贝之后的数据
**/
var wm = new WeakMap()
var cloneObj = function (obj) {
// 先判断是不是null和基本数据类型或者其他对象类型
if (obj === null || typeof obj !== 'object') {
return obj
}
// 判断数据类型
let type = Object.prototype.toString.call(obj)
type = type.slice(8, type.length - 1);
let result = new window[type]();
// 判断是不是特殊对象
if (['Funcition', 'RegExp', 'Date'].includes(type)) {
return obj
}
// 判断该值是否在WeakMap中
let existObj = wm.get(obj)
if (existObj) {
return existObj
}
wm.set(obj, result)
// 然后判断复杂数据类型和简单数据类型
if (['Map', 'WeakMap'].includes(type)) {
obj.forEach((value, key) => {
result.set(key, cloneObj(value));
})
} else if (['Set', 'WeakSet'].includes(type)){
obj.forEach((value) => {
result.add(cloneObj(value));
})
} else {
for (let item in obj) {
result[item] = cloneObj(obj[item])
}
}
return result
}
Footer
写到这里,其实还是有很多细节没有处理,比如symbol类型的,还有就是函数的性能问题等等,其实应该还有更好的优化方案,如果有的话,欢迎各位大佬留言告知。先放在这,后期再继续优化。。

如果这篇文章对你有帮助的话,欢迎点赞关注转发,最起码点个赞吧(脸皮真厚嘻嘻)