面试中经常会被问到克隆函数的实现,这篇文章我们就介绍一下克隆函数简单实现以及
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], ƒ ()]
这样就可以达到我们的预期,但是递归调用克隆函数会带来一定的性能问题,所以在实际开发中应根据实际情况进行选择。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信