深浅拷贝的实现及原理

65 阅读2分钟

js中的赋值操作是深拷贝还是浅拷贝呢?,其实基本数据类型的赋值操作,是深拷贝即两个变量间不会互相影响,对于引用数据类型,赋值操作只是在栈中新增一个指向堆中对象的变量,即复制引用地址。新旧变量之间会互相影响,即在新变量上改变对象值,旧变量对应值也会改变。即浅拷贝。

浅拷贝即只是拷贝一层,更深层次对象级别的只拷贝了地址。 即浅拷贝只是拷贝了数据在栈中的引用,并没有拷贝在堆中的地址本身。

实现浅拷贝的方法: 基本类型数据直接赋值或者使用在es6的Object.assign

Object.assign(target,  ...sources)

  • target:要拷贝给谁
  • source:要拷贝的对象

深拷贝是完完全全的拷贝,新旧变量之间不会相互影响。
对于参数是否是对象有不同的处理方法,如果是对象,对于对象的每个属性和值赋值然后递归处理; 否则直接返回。

实现深拷贝的方法: 使用Json.stringify()将要拷贝的对象转成字符串,然后用Json.parse将字符串转回对象, 但是这个方法存在缺陷 问题:

  • 造成数据丢失和数据异常(null)
  • function、undefined 直接丢失
  • NaN、Infinity 和-Infinity 变成 null
  • RegExpError对象只得到空对象;

递归实现

function deepCopy(obj) {
  // 判断拷贝的数据是对象还是数组 生成定义的数据
  var copy = Array.isArray(obj) ? [] : {}
  for (key in obj) {
    // 循环的时候如果此项为引用类型,需要 在一次的将引用类型里面的值取出来
    if (typeof obj[key] == 'object') {
      // 再次调用该方法取数据
      copy[key] = deepCopy(obj[key])
    } else {
      copy[key] = obj[key]
    }
  }
  return copy
}

但是递归存在同样问题,数据会丢失存在循环引用的问题导致死循环。需要在储存数据前判断是否被拷贝过数据。

function cloneDeep3(source, uniqueList) {
  if (!isObject(source)) return source
  if (!uniqueList) uniqueList = [] // 新增代码,初始化数组
  var target = Array.isArray(source) ? [] : {}

  // 要是有 别再循环拷贝了 直接返回 该值
  var uniqueData = find(uniqueList, source)
  if (uniqueData) {
    return uniqueData.target
  }

  // 数据不存在,保存源数据,以及对应的引用
  uniqueList.push({
    source: source,
    target: target
  })
  // =============

  for (var key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      if (isObject(source[key])) {
        target[key] = cloneDeep3(source[key], uniqueList) // 新增代码,传入数组
      } else {
        target[key] = source[key]
      }
    }
  }
  return target
}

// 新增方法,用于查找
function find(arr, item) {
  for (var i = 0; i < arr.length; i++) {
    if (arr[i].source === item) {
      return arr[i]
    }
  }
  return null
}

实际上在开发中最常用的方法还是引入第三方包loadsh直接深拷贝

安装命令:npm i loadsh

直接引用即可 import {cloneDeep} from loadsh