# 十二.数据拷贝
# 1.浅拷贝
# 1.扩展运算符
var arr1 = [1, 2, 5, 6, 4]
var arr2 = [...arr1]
console.log(arr1 == arr2)
1
2
3
2
3
# 2.Object.assign
var arr1 = [1, 2, 5, 6, 4]
var arr2 = Object.assign([], arr1)
console.log(arr1 == arr2)
1
2
3
2
3
# 3.Array.slice
# 4.Array.concat
# 2.深拷贝
# 1.JSON.parse
- 会忽略 undefined
let obj = { a: null, b: undefined, c: 1 }
JSON.parse(JSON.stringify(obj))
// {a: null, c: 1}
1
2
3
4
2
3
4
- 循环引用:深拷贝报错
let a = {}
let b = { a }
a.b = b
let copy = JSON.parse(JSON.stringify(a))
console.log(copy)
/* VM2404:4 Uncaught TypeError: Converting circular structure to JSON
--> starting at object with constructor 'Object'
| property 'b' -> object with constructor 'Object'
--- property 'a' closes the circle
at JSON.stringify (<anonymous>)
at <anonymous>:4:28 */
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- Map:Map 会丢失
let m = new Map()
m.set("a")
let obj = { a: 1, m }
let copy = JSON.parse(JSON.stringify(obj))
console.log(copy) // {a: 1, m: {…}}
1
2
3
4
5
2
3
4
5
- Set:Set 会丢失
let m = new Set()
m.add("a", 1)
let obj = { a: 1, m }
let copy = JSON.parse(JSON.stringify(obj))
console.log(copy) // {a: 1, m: {…}}
1
2
3
4
5
2
3
4
5
- RegExp
- Date
- ArrayBuffer
- 不会序列化函数,会被忽略
- 不能解决循环引用的对象
# 2.MessageChannel
建立两个端,一个端发送消息,另一个端接受消息。
function structuralClone(obj) {
return new Promise(resolve =>{
const {port1, port2} = new MessageChannel();
port2.onmessage = ev => resolve(ev.data);
port1.postMessage(obj);
})
}
const obj = /* ... */;
structuralClone(obj).then(res=>{
console.log(res);
})
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 3.History API
利用 history.replaceState。这个 api 在做单页应用的路由是可以做无刷新的改变 url。这个对象使用结构化克隆,而且是同步的。但是我们需要注意,在单页中不要把原有的路由逻辑搞乱了。所以我们克隆完一个对象的时候,要恢复路由的原状。
function structuralClone(obj) {
const oldState = history.state
history.replaceState(obj, document.title)
const copy = history.state
history.replaceState(oldState, document.title)
return copy
}
var obj = {}
var b = { obj }
obj.b = b
var copy = structuralClone(obj)
console.log(copy)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这个方法的优点是,能解决循环对象的问题,也支持许多内置类型的克隆。并且是同步的。但是缺点是有的浏览器对调用频率有限制。比如 Safari 30 秒内只允许调用 100 次
# 4.Notification API
这个 api 主要是用于桌面通知的。如果你使用 Facebook 的时候,你肯定会发现时常在浏览器的右下角有一个弹窗。可以用这个 api 实现深拷贝。
function structuralClone(obj) {
return new Notification("", { data: obj, silent: true }).data
}
var obj = {}
var b = { obj }
obj.b = b
var copy = structuralClone(obj)
console.log(copy)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
同样是优点缺点并存,优点是可以解决循环对象问题,也支持许多内置类型的克隆,并且是同步的。缺点就是这个 api 的使用需要向用户请求权限,但是用在这里克隆数据的时候,不经用户授权也可以使用。在 http 协议的情况下会提示你在 https 的场景下使用。
# 5.自己实现
- 拷贝函数
const func = (value) => {
alert(value)
}
const funcStr = func + ""
const funcTest = new Function("return " + funcStr)()
console.log(funcTest)
1
2
3
4
5
6
2
3
4
5
6
- 拷贝函数
const func = (value) => {
alert(value)
}
const funcStr = func + ""
const funcTest = eval("(" + func.toString() + ")")
console.log(funcTest)
1
2
3
4
5
6
2
3
4
5
6
- 1.JSON(只针对 JSON 如果不是,就不显示 function 正则不认)
let obj = { a: 1 }
console.log(JSON.parse(JSON.stringify(obj)))
1
2
2
- 2.实现深拷贝 保留继承关系 可以实现各种类型的拷贝 实现递归拷贝
function deepClone(obj) {
if (typeof obj !== "object") return obj
if (obj == null) return null
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
Object.prototype.toString.call(obj) === "[object Array]"
let o = new obj.constructor() //保留类的继承关系
for (let key in obj) {
o[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key]
}
}
let o = { a: { a: 1 } }
let newObj = deepClone(o)
o.a.a = 2
console.log(newObj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6.lodash 的_.cloneDeep()
← 十一.数据转换