JavaScript中的深拷贝

1.什么是深拷贝

浅拷贝只是解决了第一层的拷贝问题,拷贝第一层的***基本类型值***,以及第一层的***引用类型地址***,并没有递归拷贝第二层以后的属性。

深拷贝会拷贝所有的属性,拷贝的属性指向动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。深拷贝可以解决多层拷贝的问题。

2.深拷贝的使用

1.JSON.parse(JSON.stringify(object))

案例1:根据原始A对象浅拷贝一个新的B对象出来

// 1.JSON.parse(JSON.stringify(object)) 深拷贝
  let A = {
    name:'liujun',
    age:25,
    job:{
      name:'web',
      year:4
    },
    lan:[
      'java',
      'js'
    ]
  }
  console.log(A) // { name: 'liujun', age: 25, job: { name: 'web', year: 4 }, lan: [ 'java', 'js' ] } }
  // 1.1 根据原始A对象深拷贝一个新的B对象出来
  let B = JSON.parse(JSON.stringify(A))
  console.log(B) // { name: 'liujun', age: 25, job: { name: 'web', year: 4 }, lan: [ 'java', 'js' ] }

  // 1.2 修改A对象3个属性。发现B对象并没有影响。拷贝前后两个对象互不影响。
  A.name = '刘军'
  A.job.name = '前端开发'
  A.lan[1] = 'JavaScript'
  console.log('--------------------------------')
  console.log(A) // { name: '刘军', age: 25, job: { name: '前端开发', year: 4 }, lan: [ 'java', 'JavaScript' ] }
  console.log(B) // { name: 'liujun', age: 25, job: { name: 'web', year: 4 }, lan: [ 'java', 'js' ] }

总结:JSON.parse(JSON.stringify(object)) 深拷贝会拷贝所有的属性,拷贝的属性指向动态分配的内存。拷贝前后两个对象互不影响。

案例2:根据原始 A浅拷贝一个新的B对象出来

  // 1.JSON.parse(JSON.stringify(object)) 深拷贝
  let A = {
    name:'liujun', // 1.拷贝成功
    value: undefined, // 3.没有拷贝
    default: null, // 1.拷贝成功
    symbol:Symbol('liujun'), // 3.没有拷贝
    show:function(){  // 3.没有拷贝
      console.log('show')
    },
    date:new Date(), // 2.拷贝失败,变成了字符串
    reg:new RegExp(/1[3|4|5|6|7|8]\d{9}/)  // 2.拷贝失败,变成了{}
  }
  
  console.log(A) 
  /**
    { 
      name: 'liujun',
      value: undefined,
      default: null,
      symbol: Symbol(liujun),
      show: [Function: show],
      date: 2019-09-29T01:32:32.911Z,
      reg: /1[3|4|5|6|7|8]\d{9}/ 
   }
   */
  // 1.1 根据原始A对象深拷贝一个新的B对象出来
  let B = JSON.parse(JSON.stringify(A))
  console.log(B) 
  /**
  { 
    name: 'liujun',
    default: null,
    date: '2019-09-29T01:32:32.911Z',
    reg: {} 
  }
   */

  console.log(typeof A.date) // object:还是Date的对象
  console.log(typeof B.date) // string:不是Date的对象,不能调用Date对象的方法
  

总结:JSON.parse(JSON.stringify(object)) 深拷贝会拷贝部分的属性,拷贝的属性指向动态分配的内存。拷贝前后两个对象互不影响。

存在的问题:

  1. Date 和 RegExp类型虽然拷贝了,但是拷贝还是失败,类型发生了改变。Date 类型变成了字符串,RegExp变成了{}, 在使用它拷贝的时候一定要注意

  2. undefined、null、Symbol、function压根就没有拷贝, 在使用它拷贝的时候一定要注意。

  3. 对象的循环引用时的拷贝会报错, 例如下面 c 指向了 b 的引用,属于循环引用:

    let obj = {
        a: 1,
        b: {
            c: 2,
       		d: 3
        }
    }
    obj.a = obj.b;
    obj.b.c = obj.a;
    
    let b = JSON.parse(JSON.stringify(obj));
    // Uncaught TypeError: Converting circular structure to JSON
    

2.编写深拷贝函数

1.编写浅拷贝

function deepCopy(object){
    var target = {}
    for(var key in object){
      // 1.判断是否是自身的属性。object借用hasOwnProperty(key)函数,传递key参数
      if(Object.prototype.hasOwnProperty.call(object, key)){
          target[key] = object[key]
      }
    }
    return target
}

2.编写深拷贝函数

function deepCopy(object){
    var target = {}
    for(var key in object){
      // 1.判断是否是自身的属性。object借用hasOwnProperty(key)函数,传递key参数
      if(Object.prototype.hasOwnProperty.call(object, key)){
        // 2.如果object[key]是对象或者是数组需要递归遍历
        if(typeof object[key] === 'object'){
          target[key] = deepCopy(object[key]) // 3.深拷贝比浅拷贝就是多了这里
        } else {
          target[key] = object[key]
        }
       
      }
    }
    return target
}

3.优化深拷贝函数

function deepCopy(object){
  // typeof null // "object"
  // typeof {} // "object"
  // typeof [] // "object"
  // typeof new Date() // "object"
  // typeof new RegExp() // "object"
  // typeof function foo(){} // "function"
  if(typeof object === 'object' && object !=null){
      
    var target = Array.isArray(object) ? [] : {}
    for(var key in object){
      // 1.判断是否是自身的属性。object借用hasOwnProperty(key)函数,传递key参数
      if(Object.prototype.hasOwnProperty.call(object, key)){
        // 2.如果object[key]是对象或者是数组需要递归遍历,例如:{}, [], new Date(), new RegExp() 
        if(typeof object[key] === 'object'){
          target[key] = deepCopy(object[key]) // 3.深拷贝比浅拷贝就是多了这里
        } else {
          target[key] = object[key]
        }
       
      }
    }
    return target
      
  }else {
    // 传入 `undefined、null、Symbol、function` ` 时应返回 `undefined、null、Symbol、function` `  
    return object
  }

}

1.对传入参数进行校验,传入 null 时应该返回 null 而不是 {}, 例如:

if(typeof object === 'object' && object !=null)

2.考虑数组的兼容,例如:

var target = Array.isArray(object) ? [] : {}

3.测试深拷贝函数

  // 1.deepCopy(object) 深拷贝
  let A = {
    name:'liujun', // 1.拷贝成功
    value: undefined, // 1.拷贝成功
    default: null, // 1.拷贝成功
    symbol:Symbol('liujun'), // 1.拷贝成功
    show:function(){  // 1.拷贝成功
      console.log('show')
    },
    date:new Date(), // 2.拷贝失败,变成了{}
    reg:new RegExp(/1[3|4|5|6|7|8]\d{9}/)  // 2.拷贝失败,变成了{}
  }
  console.log(A) 
  /**
    { 
      name: 'liujun',
      value: undefined,
      default: null,
      symbol: Symbol(liujun),
      show: [Function: show],
      date: 2019-09-29T01:32:32.911Z,
      reg: /1[3|4|5|6|7|8]\d{9}/ 
   }
   */
  // 1.1 根据原始A对象深拷贝一个新的B对象出来
  let B = deepCopy(A)
  console.log(B) 
  /**
  { 
    name: 'liujun',
    value: undefined,
    default: null,
    symbol: Symbol(liujun),
    show: [Function: show],
    date: {},
    reg: {} 
  }
   */

  console.log(typeof A.date) // object:还是Date的对象
  console.log(typeof B.date) // object:不是Date的对象,不能调用Date对象的方法

存在的问题:

  1. Date 和 RegExp` 类型虽然拷贝了,但是拷贝还是失败,类型发生了改变。Date 类型变成了{},RegExp变成了{}。为什么会是 {} ?是递归函数返回的 { }
  2. undefined、null、Symbol、function 属于直接拷贝, 直接走了 deepCopy 的 else 代码块返回原始数据。
  3. 对象的循环引用时的拷贝会报错

3.完整深度拷贝函数

// 1.获取到数据源的数据类型
function _getDataType(data) {   
  return Object.prototype.toString.call(data).slice(8, -1)
}
// 2.负责拷贝 RegExp 对象
function copyRegExp(regExp) {
  let attrs = ''
  if (regExp.global) attrs += 'g'
  if (regExp.ignoreCase) attrs += 'i'
  if (regExp.multiline) attrs += 'm'
  const newRegExp = new RegExp(regExp, attrs)
  newRegExp.lastIndex = regExp.lastIndex
  return newRegExp
}

// 3.深克隆,考虑到(1.类型检查,2.递归爆栈,3.相同引用,4.Date和Function等特殊类型克隆,原型克隆)
function deepClone(x) {
  // 3.1 String Number Boolean Undefined Null 返回自身
  if (x == null || typeof x !== 'object') return x
  // 3.2 RegExp Date Function 克隆
  const type = _getDataType(x)
  let root
  switch (type) {
    case 'RegExp':
      // 3.3克隆正则    
      return copyRegExp(x)
    case 'Date':
      // 3.4 克隆Date类型    
      return new Date(x.getTime())
    case 'Function':
      // 3.5 克隆函数类型    
      return x
    case 'Array':
      root = []
      break
    default:
      root = Object.create(Object.getPrototypeOf(x))
  }
  // 3.6 Array Object 类型的克隆
  // 用来去重 解决原数据中多个属性引用同一对象克隆后不相同问题( 防止循环引用问题 )
  const uniqueList = []
  // 使用栈结构解决递归爆栈问题
  const stack = [
    {
      parent: root,
      key: undefined,
      data: x
    }
  ]
  // 深度优先循环(防止递归爆栈)
  while (stack.length) {
    const { parent, key, data } = stack.pop()
    // 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
    let res = parent
    if (typeof key !== 'undefined') {
      const type = _getDataType(data)
      switch (type) {
        case 'RegExp':
          parent[key] = copyRegExp(data)
          continue
        case 'Date':
          parent[key] = new Date(data.getTime())
          continue
        case 'Function':
          parent[key] = data
          continue
        case 'Array':
          res = parent[key] = []
          break
        default:
          const proto = Object.getPrototypeOf(data)
          res = parent[key] = Object.create(proto)
      }
    }
    // 数据引用已经存在则赋值并退出本次循环,不存在则缓存
    const uniqueData = uniqueList.find(item => item.source === data)
    if (uniqueData) {
      parent[key] = uniqueData.target
      continue
    } else {
      uniqueList.push({
        source: data,
        target: res
      })
    }
    for (const k in data) {
      if (data.hasOwnProperty(k)) {
        if (data[k] == null || typeof data[k] !== 'object') {
          // 基础类型克隆
          const descriptor = Object.getOwnPropertyDescriptor(data, k)
          Object.defineProperty(res, k, descriptor)
        } else {
          // 引用类型加入stack循环处理
          stack.push({
            parent: res,
            key: k,
            data: data[k]
          })
        }
      }
    }
  }
  return root
}

4.其它深拷贝函数

1.jQuery.extend()

案例1:根据原始 A浅拷贝一个新的B对象出来

var A = [1,2,3,4];
var B = $.extend(true,[],A);

2. lodash.cloneDeep()

案例1:根据原始 A浅拷贝一个新的B对象出来

var A = [{ 'a': 1 }, { 'b': 2 }];
var B = lodash.cloneDeep(A); //  _.cloneDeep()
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值