数组和对象 -- 浅拷贝和深拷贝

一、基本类型 和 引用类型

1、ECMAScript 中的变量类型分为两类:

  • 基本类型:undefined ,null , 布尔值(Boolean) , 字符串(String) , 数值(Number)
  • 引用类型: 统称为Object类型,细分的话,有:Object类型,Array类型,Date类型,Function类型等。

2、不同类型的存储方式:

基本数据类型 保存在 栈内存,形式如下:栈内存中分别存储着变量的标识符以及变量的值。

引用类型 保存在 堆内存  栈内存存储的是变量的标识符以及对象在堆内存中的存储地址,当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从对应的堆内存中取得所需的数据。

3、不同类型的复制方式:

基本类型 的复制:当你在复制基本类型的时候,相当于把值也一并复制给了新的变量。

引用类型 的复制:当你在复制引用类型的时候,实际上只是复制了指向堆内存的地址,即原来的变量与复制的新变量指向了同一个东西。 

所谓 深浅拷贝

01、对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为 浅拷贝

02、而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为 深拷贝

深浅拷贝 的主要区别就是:复制的是引用(地址)还是复制的是实例。

由深复制的定义来看,深复制要求如果源对象存在对象属性,那么需要进行递归复制,从而保证复制的对象与源对象完全隔离。然而还有一种可以说处在浅复制和深复制的粒度之间,也是jQuery的extend方法在deep参数为false时所谓的“浅复制”,这种复制只进行一个层级的复制:即如果源对象中存在对象属性,那么复制的对象上也会引用相同的对象。这不符合深复制的要求,但又比简单的复制引用的复制粒度有了加深。

一. 深拷贝:不是简单的拷贝引用,而是在堆中重新分配内存,并且把源对象实例的所有属性都进行新建复制,以保证深拷贝的对象的引用不包含任何原有对象

1.  JSON对象的parse和stringify          JSON.parse(JSON.stringify(xxx))

JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深复制。

      let bbb = [1,[2,3,4]]
      let ccc = JSON.parse(JSON.stringify(bbb))
      ccc[1][0] = 222
      console.log(bbb,ccc) // [1,[2,3,4]]   [1,[222,3,4]]

这个方法使用较为简单,可以满足基本的深复制需求,而且能够处理JSON格式能表示的所有数据类型,但是对于正则表达式类型、函数类型等无法进行深复制(而且会直接丢失相应的值),同时如果对象中存在循环引用的情况也无法正确处理

    let a = [{
      'aa': undefined,
      'bb': [1, 2, 3],
      'cc': null,
      'dd': new Date(),
      'ee': () => {}
    }]
    let b = JSON.parse(JSON.stringify(a))
    console.log(a, b)


    打印结果:
      a的打印结果: 
        aa:undefined
        bb:[1, 2, 3]
        cc:null
        dd:Wed Mar 13 2019 09:46:13 GMT+0800 (中国标准时间)
        ee:ƒ ee()
      
      b的打印结果:
        bb:[1, 2, 3]
        cc:null
        dd:Wed Mar 13 2019 09:46:13 GMT+0800 (中国标准时间)

2.  jQuery中的extend复制方法

 jQuery中的extend方法可以用来扩展对象,这个方法可以传入一个参数:deep(true or false),表示是否执行深复制(如果是深复制则会执行递归复制)

这个方法基本的思路就是如果碰到array或者object的属性,那么就执行递归复制,这也导致对于Date,Function等引用类型,jQuery的extend也无法支持

let a=[0,1,[2,3],4],
    b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);   // [1,1,[1,3],4]   [0,1,[2,3],4]

3. 自定义深拷贝:封装一个深拷贝的函数

利用 递归 来实现深复制,对属性中所有引用类型的值,遍历到是基本类型的值为止。

      //   深拷贝的实现
      function deepClone(obj) {
        let objClone = Array.isArray(obj) ? [] : {}

        if (obj && typeof obj == 'object') {
          for (key in obj) { 

            // for-in遍历的是原型链,需要用hasOwnProperty 判断是否是自有属性
            if (obj.hasOwnProperty(key)) {
              //   判断obj子元素是否为对象。如果是,则递归复制
              if (obj[key] && obj[key] === 'object') {
                objClone[key] = deepClone(obj[key])
              } else {
                // 如果不是,简单复制
                objClone[key] = obj[key]
              }
            }
          }
          return objClone
        } else {
          //如果obj不是对象,那么直接返回值就可以了
          return obj
        }
      }
      
      console.log('使用自定义的深拷贝')
      let arrA = [1, 2, 3]
      let arrAcopy = deepClone(arrA)
      arrAcopy[0] = 111
      console.log(arrA, arrAcopy) // (3) [1, 2, 3] (3) [111, 2, 3]

      let array2 = [1, [2, 3, 4]]
      let array2Copy = deepClone(array2)
      array2[0] = 111
      console.log(array2, array2Copy) // [111,[2,3,4]]   [1,[2,3,4]]

      let stringA = 11
      let aCopy = deepClone(stringA)
      stringA = 22
      console.log(stringA, aCopy) // 22 11

      let stringB = null
      let bCopy = deepClone(stringB)
      console.log(bCopy) // null

二、浅拷贝:复制后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响

1.  直接赋值

let aa = [1,2,3]

let bb = aa

bb[1] = 22

console.log(aa,bb) // (3) [1, 22, 3] (3) [1, 22, 3]

2. ES6拓展运算符 [...list]

    let arr = [1,2,3,4]
    let list = [...arr]
    list[0] = '111'
    console.log(arr,list)

arr的打印结果:
[1, 2, 3, 4]

list的打印结果:
 ["111", 2, 3, 4]

ES6的拓展运算符也可以实现数据的拷贝,但是只遍历一层。属于浅拷贝 

    let arr = [{
      name: '小草莓',
      hobby: ['a', 'b', 'c']
    }]
    let list = [...arr]
    list[0].hobby = ['a', 'b', 'c', 'd']
    list[0].name = 'new 小草莓'
    console.log(arr)
    console.log(list)

arr的打印结果:
[{
    name: 'new 小草莓',
    hobby: ['a', 'b', 'c', 'd']
}]

list的打印结果
[{
    name: 'new 小草莓',
    hobby: ['a', 'b', 'c', 'd']
}]

3. js的slice()方法

rrayObj.slice(start, [end])

数组中的slice()方法返回指定截取的一段,原数组不改变。当start为0素,end不填则默认复制整个数组

    let c = [1,2,3,4,5]
    let d = c.slice(0)
    d[2] = 333
    console.log(c,d) // (5) [1, 2, 3, 4, 5] (5) [1, 2, 333, 4, 5]

Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用了,因此这两个方法并不是真正的深复制 

      let cc = [1,[2,3,4]]
      let ee = cc.slice(0)
      ee[1][0] = 222
      console.log(cc,ee) // [1,[222,3,4]]

4. js的concat()方法

arrayObject.concat(arrayX,arrayX,......,arrayX)

数组中的concat()方法返回合并之后的数组,原数组不改变。

    let e = [1,2,3,4]
    let f = e.concat()
    f[1] = 222
    console.log(e,f)  // (4) [1, 2, 3, 4] (4) [1, 222, 3, 4]

Array的slice和concat方法都会返回一个新的数组实例,但是这两个方法对于数组中的对象元素却没有执行深复制,而只是复制了引用了,因此这两个方法并不是真正的深复制


      let g = [1,[2,3,4]]
      let h = g.concat()
      h[1][0] = 222
      console.log(g,h)

5. Object.assign(target, …sources)

      console.log('对象浅拷贝')

      let arr = [1,2,3,4]
      let obj1 = { a: 1, b: 2, c: 3 }
      let obj2 = { a: 11, b: 22, c: 33, d:arr}
      let obj3 = Object.assign({}, obj1, obj2)

      obj1.a = 'aaa'
      obj2.d[0] = 'bbb'

      console.log(obj1, obj2, obj3)
      // {a: "aaa", b: 2, c: 3}
      // {a: 11, b: 22, c: 33, d: ['bb',2,3,4]}
      // {a: 11, b: 22, c: 33, d: ['bb',2,3,4]}

自己实现一个拷贝方法,通过传入deep参数来表示是否执行深拷贝

      //util作为判断变量具体类型的辅助模块
      var util = (function() {
        var class2type = {};

        [('Null',
          'Undefined',
          'Number',
          'Boolean',
          'String',
          'Object',
          'Function',
          'Array',
          'RegExp',
          'Date')
        ].forEach(function(item) {
          class2type['[object ' + item + ']'] = item.toLowerCase()
        })

        function isType(obj, type) {
          return getType(obj) === type
        }

        function getType(obj) {
          return class2type[Object.prototype.toString.call(obj)] || 'object'
        }

        return {
          isType: isType,
          getType: getType
        }
      })()


      function copy(obj, deep) {
        //如果obj不是对象,那么直接返回值就可以了
        if (obj === null || typeof obj !== 'object') {
          return obj
        } 

        //定义需要的局部变量,根据obj的类型来调整target的类型
        var i,
          target = util.isType(obj, 'array') ? [] : {},
          value,
          valueType

        for (i in obj) {
          value = obj[i]
          valueType = util.getType(value) 
           //只有在明确执行深复制,并且当前的value是数组或对象的情况下才执行递归复制
          if (deep && (valueType === 'array' || valueType === 'object')) {
            target[i] = copy(value)
          } else {
            target[i] = value
          }
        }
        return target
      }

更过例子可参考:https://blog.csdn.net/lyt_angularjs/article/details/86599820

猜你喜欢

转载自blog.csdn.net/qq_38290251/article/details/88525712
今日推荐