JavaScript中的深拷贝

Posted LiuJun2Son

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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]\\d9/)  // 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]\\d9/ 
   
   */
  // 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]\\d9/)  // 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]\\d9/ 
   
   */
  // 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()

以上是关于JavaScript中的深拷贝的主要内容,如果未能解决你的问题,请参考以下文章

javascript实现引用数据类型的深拷贝和浅拷贝详解

Javascript中的深拷贝和浅拷贝

JavaScript中的深拷贝和浅拷贝常用方法总结

Python中的深拷贝和浅拷贝

低门槛彻底理解JavaScript中的深拷贝和浅拷贝

JavaScript中的深拷贝和浅拷贝到底是什么