面试经典之深浅拷贝与克隆函数

面试中经常会被问到克隆函数的实现,这篇文章我们就介绍一下克隆函数简单实现以及
JavaScript中的深浅拷贝。

首先我们先来了解一下JavaScript中的深浅拷贝:

数组拷贝

为了更全面了解数组的拷贝,希望大家不要吐槽接下来我们举例使用的神奇数组:

var source = [1, null, undefined, {name: 'anjou', age: undefined, sex: null}, [2, 3, 4], function() {}]
浅拷贝

概念:只能拷贝数组的第一层基本数据类型数据,无法切断数组内部引用类型数据的引用关系。

Array.slice()与Array.concat()

var target = source.slice() || source.concat()
target[0] = 5
target[3].name = 'anpoly'
target[4][0] = 6
console.log(source)  // [1, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]
console.log(target)  // [5, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]

通过比较我们发现两个数组只有arr[0]的值不同,结论: Array.slice与Array.concat方法不会更改现有数组,而是将数据浅拷贝到一个新数组。数组内引用类型数据依旧保持引用关系。

es6 扩展运算符

var target = [...source]
target[0] = 5
target[3].name = 'anpoly'
target[4][0] = 6
console.log(source)  // [1, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]
console.log(target)  // [5, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]

结果与第一种方法相同,所以使用扩展运算符也可以实现数组的浅拷贝。

es6 Object.assign()

var proto = Object.getPrototypeOf(source)
var target = Object.assign({}, Object.create(proto), source)
target[0] = 5
target[3].name = 'anpoly'
target[4][0] = 6
console.log(source)  // [1, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]
console.log(target)  // [5, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]

结果与第一种方法相同,使用Object.assign方法依旧无法切断数组内部引用数据类型的引用关系。

深拷贝

概念:彻底切断了数组内引用类型的引用关系。

JSON.parse()与 JSON.stringify()

var target = JSON.parse(JSON.stringify(source))
target['0'] = 5
target['3'].name = 'anpoly'
target['4'][0] = 8
console.log(source)  // [1, null, undefined, {name: 'anjou', age: undefined, sex: null}, [2, 3, 4], ƒ ()]
console.log(target)  // [5, null, null, {name: 'anpoly', sex: null}, [6, 3, 4], null]

通过上面的运行结果,我们发现使用JSON.parse与JSON.stringify可以实现数组的深拷贝。但是我们发现拷贝后的数组与我们预想的结果有很大差别,这是因为JSON.stringify(…) 在对象中遇到 undefined 、 function 和 symbol 时会自动将其忽略, 在 数组中则会返回 null (以保证单元位置不变)。

对象拷贝

为了方便对比数组的拷贝我们在对象拷贝的例子中也要使用一个神奇的对象:

var source2 = {0:1, 1:null, 2:undefined, 3:{name: 'anjou', age: undefined, sex: null}, 4:[2, 3, 4], 5: function() {}}
浅拷贝

概念:只能拷贝对象中的基本数据类型数据,无法切断对象内部引用类型数据的引用关系。

es6 Object.assign()

var obj = Object.getPrototypeOf(source2)
var target = Object.assign({}, Object.create(obj), source2)
target['0'] = 5
target['3'].name = 'anpoly'
target['4'][0] = 6
console.log(source2)  // {0: 1, 1: null, 2: undefined, 3: {name: "anpoly", age: undefined, sex: null}, 4: [6, 3, 4], 5: 
ƒ ()}
console.log(target)  // {0: 5, 1: null, 2: undefined, 3: {name: "anpoly", age: undefined, sex: null}, 4: [6, 3, 4], 5: 
ƒ ()}

结果与数组类似,也无法实现对象的深拷贝。

深拷贝

概念:彻底切断了对象内引用类型的引用关系。

JSON.parse()与 JSON.stringify()

var target = JSON.parse(JSON.stringify(source2))
target['0'] = 5
target['3'].name = 'anpoly'
target['4'][0] = 6
console.log(source2)  // {0: 1, 1: null, 2: undefined, 3: {name: "anjou", age: undefined, sex: null}, 4: [2, 3, 4], 5: 
ƒ ()}
console.log(target)  // {0: 1, 1: null, 3: {name: "anpoly", sex: null}, 4: [6, 3, 4]}

可以实现对象的深度克隆,但是一些不安全的JSON值(undefined 、 function 和 symbol)无法拷贝。原因在数组拷贝已经说过,这里不再赘述。

克隆函数

通过上面的这些拷贝技巧的分析,我们发现没有一个可以做到比较完整的深拷贝。那要如何实现一个数组或者一个对象的深拷贝呢? 接下来我们将写一个简单的克隆函数来实现数组与对象的深拷贝。

// 克隆函数
function deepCopy(data) {
    if (typeof data !== 'object' || data == null) return data
    var newData = data instanceof Array ? [] : {}
    for (var key in data) {
        newData[key] = typeof data[key] === 'object' ? (data[key] === null ? null : deepCopy(data[key])) : data[key]
    }
    return newData
}
var target = deepCopy(source)
target[0] = 5
target[3].name = 'anpoly'
target[4][0] = 6
console.log(source)  // [1, null, undefined, {name: 'anjou', age: undefined, sex: null}, [2, 3, 4], ƒ ()]
console.log(target)  // [5, null, undefined, {name: 'anpoly', age: undefined, sex: null}, [6, 3, 4], ƒ ()]

这样就可以达到我们的预期,但是递归调用克隆函数会带来一定的性能问题,所以在实际开发中应根据实际情况进行选择。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值