JavaScript clone

2023-1-27 日,第 623 期 JavaScript Weekly 介绍了最新的原生深拷贝函数 structuredClone(),借此总结一下 JavaScript 中 clone 相关的方法。

JavaScript 中的数据类型分为原始类型和引用类型,他们的区别之一,就是在拷贝时的表现不一样。

  • 原始类型存储在栈内存中,每次拷贝都会生成一份新的数据,对新数据的任何操作都不会影响原有数据。
  • 引用类型存储在堆内存中,拷贝时只会拷贝指向对象的指针,修改新数据会影响原有数据。

浅拷贝 shallow clone

原始类型生成新数据;引用类型生成新的指针,还是指向原有的对象。

Object.assign

1
2
3
4
5
6
7
8
9
10
11
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
}

let clone = Object.assign({}, user)

alert( user.sizes === clone.sizes ) // true,同一个对象

Spread 语法(对象解构)

1
2
3
4
5
6
7
8
9
10
11
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
}

let clone = {...user}

alert( user.sizes === clone.sizes ) // true,同一个对象

深拷贝 deep clone

JSON.parse(JSON.stringify())

1
2
3
4
5
6
7
8
9
10
11
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
}

let clone = JSON.parse(JSON.stringify(user))

alert( user.sizes === clone.sizes ) // false,不是同一个对象

缺点

  • 无法处理递归数据解构
  • 无法处理部分 JS 内置数据类型:Map, Set, Date, RegExp, ArrayBuffer.
  • 无法拷贝对象的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const obj = {
set: new Set([1, 3, 3]),
map: new Map([[1, 2]]),
regexp: /foo/,
deep: { array: [ new File(someBlobData, 'file.txt') ] },
error: new Error('Hello!')
}

const clone = JSON.parse(JSON.stringify(obj))

console.log(clone)

// 我们会得到 🙂
{
set: {},
map: {},
regex: {},
deep: {
array: [
{}
]
},
error: {},
}

lodash.cloneDeep()

  • 使用 Object.prototype.toString.call 来获取详细类型,不同类型分别处理;
  • 递归克隆,数组遍历项,对象遍历属性;
  • 使用 Array/Map 缓存引用,解决循环引用的问题;
  • loadsh 不会拷贝函数,将返回 {}。 因为克隆函数没有实际意义,公用同一个函数也没问题。 而且涉及到 科里化,this,闭包等问题,无法克隆相等价值的函数。
  • 非要克隆函数的话,可以使用 new Function('return ' + fn.toString())()

缺点

需要额外装包。

structuredClone(结构化克隆)

2022 年,HTML Living Standard 新增了一个全局函数 structuredClone,可以直接深拷贝引用类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const obj = {
title: "Builder.io Conf",
date: new Date(123),
attendees: ["Steve"]
}

const clone = structuredClone(obj)
console.log(clone)

// 我们将得到 😍
{
title: "Builder.io Conf",
date: "1970-01-01T00:00:00.123Z",
attendees: ["Steve"]
}

浏览器支持情况(2023 年 1 月 30 日):

缺点

  • 原型链丢失,如果在类实例中使用 structuredClone() ,那么将得到一个普通对象;
  • structuredClone 同样不会克隆函数;
  • 有些值是不能够被结构化的,比如 Error 对象和 DOM 节点,structuredClone 不会克隆这些对象;

参考

  • javascript.info
  • MDN
  • builder.io
  • web.dev