在 JavaScript 中可以使用递归实现一个深拷贝,代码如下:
const obj = {
info: {
person: {
name: 'iwen',
age: '18',
}
}
}
// 第一版
function deepClone(obj) {
let result = Object.create(null)
for (const key in obj) {
if (typeof obj[key] === 'object') {
result[key] = deepClone(obj[key])
} else {
result[key] = obj[key]
}
}
return result
}
let o = deepClone(obj)
console.log(o) // { info: { person: { name: 'iwen', age: '18' } } }
o.info.person = null
console.log(obj) // { info: { person: { name: 'iwen', age: '18' } } }
功能是实现了,但是代码看起来有点复杂,能不能简化一下,如下:
// 简化后
function deepClone(obj, args = {}) {
for (const key in obj) {
args[key] = obj[key]
if (typeof obj[key] === 'object') {
deepClone(obj[key], args[key])
}
}
return args
}
let o = deepClone(obj)
console.log(o) // { info: { person: { name: 'iwen', age: '18' } } }
我们发现代码简洁不少,功能也实现,但是总感觉哪里不对,我们测试一下:
o.info.person = null
console.log(obj) // { info: { person: null } }
怎么回事,不是说好的深拷贝,我递归也用了,怎么还会影响到原来的值,究竟是哪里出问题了,我们检查一下,发现在函数中有这样一步操作,args[key] = obj[key]
,试想一下如果obj[key]
是一个对象,那么直接赋值就是对象的索引给赋值过去了,虽然后面我们仍然使用递归了遍历到了所有的值,但其实已经没有什么用了,之所以出现这样的错误是以为没有理解使用递归的目的,递归的目的是要将对象中的每一个值都转化为值类型然后进行赋值,所以只要避免出现将对象直接赋值就行了。除此之外,我们知道 for in 会将原型链上的属性也遍历到,所以这里可以优化一下
// 第二版
function deepClone(obj) {
let hasOwn = Object.prototype.hasOwnProperty
let result = Object.create(null)
for (const key in obj) {
if (hasOwn.call(obj, key)) {
if (typeof obj[key] === 'object') {
result[key] = deepClone(obj[key])
} else {
result[key] = obj[key]
}
}
}
return result
}
递归除了可以深拷贝之外,还有一个常见的应用就是数组的扁平化,比如有一个数组 [1, [2, [3, 4]]]
要转化为 [ 1, 2, 3, 4 ]
,代码如下:
var arr = [1, [2, [3, 4]]];
// 扁平化
function flatten(arr) {
let _arr = []
for (const item of arr) {
if (Array.isArray(item)) {
_arr = _arr.concat(flatten(item))
} else {
_arr.push(item)
}
}
return _arr
}
console.log(flatten(arr)) // [ 1, 2, 3, 4 ]
思考一下,为什么在操作数组是需要给数组重新赋值一下,_arr = _arr.concat(flatten(item))
,因为 concat
函数是纯函数,它不会影响原来的数组,所以需要手动重置一下,那这里能不能简化一下呢,代码如下:
// 简化后
function flatten(arr, args=[]) {
for (const item of arr) {
if (Array.isArray(item)) {
flatten(item, args)
} else {
args.push(item)
}
}
return args
}
let a = flatten(arr) // [ 1, 2, 3, 4 ]
a = null
console.log(arr) // [ 1, [ 2, [ 3, 4 ] ] ]
是不是和深拷贝的简化版很像,不要被迷惑啦,这里答案是可以的,为什么这里又可以了,因为这里所有push到数组中都是值类型,哈哈。
你这里代码太长了,我要一行代码就实现,什么递归,别整那些没用的,好的,代码如下:
arr.toString().split(',').map(item => +item)