# 循环引用

在处理 拷贝 函数时,我们要关注几个点

  1. 循环引用
  2. 相同引用
  3. 各种数据类型

我们先看看循环引用和相同引用的处理

# 什么是循环引用

我们以数组举例

const a = [1]
a.push(a)

这个时候就出现了循环引用

# 什么是相同引用

var arr = [1,2,3]
var obj = {}
obj.a = arr
obj.b = arr

对象中的元素,指向了同一内存空间

# 浅拷贝

const a = [{a: 1}]

const {length} = a

const b = new Array(length)

let index = -1

while (++index < length) {
  b[index] = a[index]
}

这样就实现了一个简单的浅拷贝

浅拷贝的问题就是在于,当引用类型的值改变时,拷贝出来的对象对应的值也会发生改变,所以出现了深拷贝

# 深拷贝

我们以数组举例

const a = [{a: 1}]

相对用的比较多的拷贝方法,是使用 JSON.parse JSON.stringify

const b = JSON.parse(JSON.stringify(a))

但是这种方式如果放到循环引用的时候就会出错

const a = [1]
a.push(a)

JSON.parse(JSON.stringify(a))

在这个时候我们就不能使用 JSON.parse 的形式来进行 clone 了

# 递归函数

以数组举例

function clone (arr) {
  if (!Array.isArray(arr)) return arr
  const {length} = arr
  const result = new Array(length)
  let index = -1
  while (++index < length) {
    result[index] = clone(arr[index])
  }
  return result
}

如果当它是数组时,我们递归处理,但是递归处理对于普通的数组没有问题,对于循环引用的问题还是没有得到解决

可以看到,报 栈溢出

# 那么对于循环引用的问题应该怎么解决

  1. 维护一个变量用来缓存已经遍历的值
  2. 每次递归遍历时,判断当前值是否已经在变量中,如果有,说明已经递归过当前值,直接停止当前递归,返回上次递归的值即可

以数组为例,实现如下

function cloneDeep (arr) {
  const map = new Map
  function _deep(target) {
    if (!Array.isArray(target)) return target

    if (map.get(target)) return map.get(target)

    const {length} = target
    const result = new Array(length)

    map.set(target, result)

    let index = -1
    while (++index < length) {
      result[index] = _deep(target[index])
    }
    return result
  }
  return _deep(arr)
}

通过一个 Map 对象来缓存,在递归调用时,进行一个判断,如果 key 值已经重复了,直接返回对应的 value 即可

如果有处理 数组或者对象等引用类型对比的函数 比如 equalArrays 时,也是这样的思路来进行循环引用的处理,可以使用 == 来进行对比

== 会遵循 Abstract Equality Comparison (opens new window) 判断逻辑,在类型相同时,会执行 Strict Equality Comparison (opens new window) 判断逻辑

最终如果都是引用类型会走到 SameValueNonNumber (opens new window) 规范,该规范规定 如果 x 和 y 指向同一个对象,返回 true, 否则返回 false

至此,就解决了循环引用和相同引用的问题