# 十二.数据拷贝

# 1.浅拷贝

# 1.扩展运算符

var arr1 = [1, 2, 5, 6, 4]
var arr2 = [...arr1]
console.log(arr1 == arr2)
1
2
3

# 2.Object.assign

var arr1 = [1, 2, 5, 6, 4]
var arr2 = Object.assign([], arr1)
console.log(arr1 == arr2)
1
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
  • 循环引用:深拷贝报错
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
  • 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
  • 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
  • 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

# 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

这个方法的优点是,能解决循环对象的问题,也支持许多内置类型的克隆。并且是同步的。但是缺点是有的浏览器对调用频率有限制。比如 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

同样是优点缺点并存,优点是可以解决循环对象问题,也支持许多内置类型的克隆,并且是同步的。缺点就是这个 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
  • 拷贝函数
const func = (value) => {
  alert(value)
}
const funcStr = func + ""
const funcTest = eval("(" + func.toString() + ")")
console.log(funcTest)
1
2
3
4
5
6
  • 1.JSON(只针对 JSON 如果不是,就不显示 function 正则不认)
let obj = { a: 1 }
console.log(JSON.parse(JSON.stringify(obj)))
1
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

# 6.lodash 的_.cloneDeep()