JavaScript和python的深浅拷贝

210 阅读3分钟

最近一直在纠结这个问题,决定彻底搞懂深浅拷贝,为了方便理解和记忆,决定将python和js放在一起探讨下。

首先看下直接用'='赋值两种语言的表现。

JavaScript:

var l1 = [['js','python'],12,{"name":"jack"}]
var l2 = l1
l1.push('emmmmm')
l1[0].push('go')
console.log('l1',l1)
console.log('l2',l2)
console.log('l1 === l2:',l1 === l2)
/*
输出:
l1 [ [ 'js', 'python', 'go' ], 12, { name: 'jack' }, 'emmmmm' ]
l2 [ [ 'js', 'python', 'go' ], 12, { name: 'jack' }, 'emmmmm' ]
l1 === l2: true
*/

Python:

l1 = [['js','python'],12,{"name":"jack"},(13,14)]
l2 = l1

l1.append('emmmmm')
l1[0].append('go')
print('l1',l1)
print('l2',l2)
print('l1 is l2',l1 is l2)
'''
输出:
l1 [['js', 'python', 'go'], 12, {'name': 'jack'}, (13, 14), 'emmmmm']
l2 [['js', 'python', 'go'], 12, {'name': 'jack'}, (13, 14), 'emmmmm']
l1 is l2 True
'''

毫无疑问:用等号赋值就是直接指向拷贝对象,两者完全相同

浅拷贝

用构造器实现浅拷贝

JavaScript实现浅拷贝的几种方式:

  1. 数组浅拷贝array.slice()
var l1 = [['js','python'],12,{"name":"jack"}]
var l2 = l1.slice()
l1.push('emmmmm')
l1[0].push('go')
console.log('l1',l1)
console.log('l2',l2)
console.log('l1 === l2:',l1 === l2)

/*
l1 [ [ 'js', 'python', 'go' ], 12, { name: 'jack' }, 'emmmmm' ]
l2 [ [ 'js', 'python', 'go' ], 12, { name: 'jack' } ]
l1 === l2: false
*/
  1. Object.assign()——ES6语法

    var l1 = [['js','python'],12,{"name":"jack"}]
    var l2 = Object.assign(l1)
    l1.push('emmmmm')
    l1[0].push('go')
    console.log('l1',l1)
    console.log('l2',l2)
    console.log('l1 === l2:',l1 === l2)
    
    /*
    输出:
    l1 [ [ 'js', 'python', 'go' ], 12, { name: 'jack' }, 'emmmmm' ]
    l2 [ [ 'js', 'python', 'go' ], 12, { name: 'jack' }, 'emmmmm' ]
    l1 === l2: true
    */
    
  2. 扩展符...

    var l1 = [['js','python'],12,{"name":"jack"}]
    console.log(...l1)
    l1.push('emmmmm')
    l1[0].push('go')
    console.log(...l1)
    
    /*
    输出:
    [ 'js', 'python' ] 12 { name: 'jack' }
    [ 'js', 'python', 'go' ] 12 { name: 'jack' } 'emmmmm'
    */
    

Python:

l1 = [['js','python'],12,{"name":"jack"},(13,14)]
l2 = list(l1)

l1.append('emmmmm')
l1[0].append('go')
l1[3] += (15,16)
print('l1',l1)
print('l2',l2)
print('l1 is l2',l1 is l2)
'''
输出:
l1 [['js', 'python', 'go'], 12, {'name': 'jack'}, (13, 14, 15, 16), 'emmmmm']
l2 [['js', 'python', 'go'], 12, {'name': 'jack'}, (13, 14)]
l1 is l2 False
'''

可以看出,python、JavaScript的拷贝都是浅拷贝:即子元素都是拷贝对象子元素的引用。

比较特殊的是python存在元组这种类型,元组是不可变的,因此l1对元组的操作实际上是创建了一个新元组,而l2并没有重新指向新元组。

深拷贝

深度拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。

Python:

import copy
l1 = [['js','python'],12,{"name":"jack"},(13,14)]
l2 = copy.deepcopy(l1)

l1.append('emmmmm')
l1[0].append('go')
l1[3] += (15,16)
print('l1',l1)
print('l2',l2)
print('l1 is l2',l1 is l2)
'''
输出:
l1 [['js', 'python', 'go'], 12, {'name': 'jack'}, (13, 14, 15, 16), 'emmmmm']
l2 [['js', 'python'], 12, {'name': 'jack'}, (13, 14)]
l1 is l2 False
'''

JavaScript实现深拷贝的几种方式:

  1. JSON.parse/stringify

    这种方法适用于:对象中不包含Date函数,undefined,Infinity,正则,映射,集合,Blob,FileLists,ImageDatas,稀疏数组,类型化数组或其他复杂类型。如果包含了上述类型,深拷贝会出现偏差

    const a = {
        string: 'string',
        number: 123,
        bool: false,
        nul: null,
        date: new Date(),  // stringified
        undef: undefined,  // lost
        inf: Infinity,  // forced to 'null'
        re: /.*/,  // lost
      }
      console.log(a);
      console.log(typeof a.date);  // Date object
      const clone = JSON.parse(JSON.stringify(a));
      console.log(clone);
      console.log(typeof clone.date);  // result of .toISOString()
      
    /*
    输出:
    { string: 'string',
      number: 123,
      bool: false,
      nul: null,
      date: 2019-09-02T03:25:11.116Z,
      undef: undefined,
      inf: Infinity,
      re: /.*/ }
    object
    { string: 'string',
      number: 123,
      bool: false,
      nul: null,
      date: '2019-09-02T03:25:11.116Z',
      inf: null,
      re: {} }
    string
    
    */
    
  2. 使用一些库的深拷贝函数

    1. loadsh.cloneDeep()

      const loadsh = require('loadsh')
      
      const a = {
          string: 'string',
          number: 123,
          bool: false,
          nul: null,
          date: new Date(),  // stringified
          undef: undefined,  // lost
          inf: Infinity,  // forced to 'null'
          re: /.*/,  // lost
      }
      
      const clone = loadsh.cloneDeep(a)
      
      console.log(a)
      console.log(clone)
      
      /*
      输出:
      { string: 'string',
        number: 123,
        bool: false,
        nul: null,
        date: 2019-09-02T07:21:57.823Z,
        undef: undefined,
        inf: Infinity,
        re: /.*/ }
      { string: 'string',
        number: 123,
        bool: false,
        nul: null,
        date: 2019-09-02T07:21:57.823Z,
        undef: undefined,
        inf: Infinity,
        re: /.*/ }
      */
      
    2. angular.copy()

      // Module: copyExample
      angular.
        module('copyExample', []).
        controller('ExampleController', ['$scope', function($scope) {
          $scope.leader = {};
      
          $scope.reset = function() {
            // Example with 1 argument
            $scope.user = angular.copy($scope.leader);
          };
      
          $scope.update = function(user) {
            // Example with 2 arguments
            angular.copy(user, $scope.leader);
          };
      
          $scope.reset();
        }]);