深拷贝与浅拷贝

深拷贝与浅拷贝

Posted by SkioFox on August 11, 2017

讲解Object.assign API的使用和原理

Object.assign(target, …sources)

函数参数为一个目标对象(该对象作为最终的返回值),源对象(此处可以为任意多个)。通过调用该函数可以拷贝所有可被枚举的自有属性值到目标对象中。

浅拷贝,利用Object.assign可以对只有一层的对象实现深拷贝

  • 这里我们需要强调的三点copy条件:
    • 可被枚举的属性
    • 自有属性
    • string或者Symbol类型是可以被直接分配的
    • 拷贝过程中将调用源对象的getter方法,并在target对象上使用setter方法实现目标对象的拷贝。

      拷贝过程中将调用源对象的getter方法,并在target对象上使用setter方法实现目标对象的拷贝。

// 我们参考上面的原型函数说明即可知道其最开始的o1因为设置为target,则调用其setter方法设置了其他对象的属性到自身。
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, target object itself is changed.

我们自定义了一些对象,这些对象有一些包含了不可枚举的属性,另外注意使用 Object.defineProperty 初始化的对象默认是不可枚举的属性。对于可枚举的对象我们可以直接使用Object.keys()获得,或者使用for-in循环遍历出来.

// 对于不可枚举的属性,使用Object.assign的时候将被自动忽略。
var obj = Object.create({ foo: 1 }, { // foo is an inherit property.
  bar: {
    value: 2  // bar is a non-enumerable property.
  },
  baz: {
    value: 3,
    enumerable: true  // baz is an own enumerable property.
  }
});

var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }  

// 对于只读的属性,当分配新的对象覆盖他的时候,将抛出异常:

var target = Object.defineProperty({}, 'foo', {
  value: 1,
  writable: false
}); 

Object.assign(target, { bar: 2 })

//{bar: 2, foo: 1}

Object.assign(target, { foo: 2 })
//Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'(…)

实现es5版本的Object.assign:

  • 实现步骤:

    • 判断是否原生支持该函数,如果不存在的话创建一个立即执行函数,该函数将创建一个assign函数绑定到Object上。
    • 判断参数是否正确(目的对象不能为空,我们可以直接设置{}传递进去,但必须设置该值)
    • 使用Object在原有的对象基础上返回该对象,并保存为out
    • 使用for…in循环遍历出所有的可枚举的自有对象。并复制给新的目标对象(hasOwnProperty返回非原型链上的属性)
if (typeof Object.assign != 'function') {
    (function () {
        Object.assign = function (target) {
        'use strict';
        if (target === undefined || target === null) {
            throw new TypeError('Cannot convert undefined or null to object');
        }
        
        var output = Object(target);
        for (var index = 1; index < arguments.length; index++) {
            var source = arguments[index];
            if (source !== undefined && source !== null) {
                    for (var nextKey in source) {
                    if (source.hasOwnProperty(nextKey)) {
                        output[nextKey] = source[nextKey];
                    }
                }
            }
        }
        return output;
        };
    })();
}

如何实现深拷贝,使用原始的递归函数

  • 浅拷贝:浅拷贝只复制了指向对象的指针,新旧对象共用同一块内存,修改某一个对象的同时也会把另一个都一并修改了。

如果是数组,我们可以利用数组的一些方法,比如slice,concat方法返回一个新数组的特性来实现拷贝,但假如数组嵌套了对象或者数组的话,使用concat方法克隆并不完整,如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。

我们可以思考下如何实现一个对象或数组的浅拷贝,遍历对象,然后把属性和属性值都放在一个新的对象里即可

// 浅拷贝对象
    var shallowCopyObject = function(obj) {
        // 只拷贝对象
        if (typeof obj !== 'object') return;
        // 根据obj的类型判断是新建一个数组还是对象
        var newObj = obj instanceof Array ? [] : {};
        // 遍历obj,并且判断是obj的属性才拷贝
        for (var key in obj) {
            // hasOwnProperty判断自身存在的可遍历且非继承的属性
            if (obj.hasOwnProperty(key)) {
                newObj[key] = obj[key];
            }
        }
        return newObj;
    }

// 深拷贝数组或者对象最简单的方法:JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象,通过这两个方法,也可以实现对象的深复制。---超经典
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1,new:{new:12}}]
var new_arr = JSON.parse(JSON.stringify(arr) );
console.log(new_arr);
  • 深拷贝:跟浅拷贝最简单明了的区别就是修改拷贝的对象,不会改变源对象,最简单直接的方法就是函数递归。
function deepClone (sourceObj, targetObj) {
    let cloneObj = targetObj || {}
    // 判断非空,非数组的数据类型
    if(!sourceObj || typeof sourceObj !== "object" || sourceObj.length === undefined){
        return sourceObj
    }
    // 数组
    if(sourceObj instanceof Array){
        cloneObj = sourceObj.concat()
    // 对象
    } else {
        // for...in循环得到所有胡可遍历的属性       
        for(let i in sourceObj){
            // if (typeof sourceObj[i] === 'object') {
            //     cloneObj[i] = deepClone(sourceObj[i], {})
            // } else {
            //     cloneObj[i] = sourceObj[i]
            // }
            cloneObj[i] = typeof sourceObj[i] === 'object' ? deepClone(sourceObj[i], {}):sourceObj[i]
        }
    }
    return cloneObj
}
  • js中对象的copy,深拷贝

      var deepCopy = function(obj) {
          if (typeof obj !== 'object') return;
          var newObj = obj instanceof Array ? [] : {};
          for (var key in obj) {
              if (obj.hasOwnProperty(key)) {
              newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
              }
          }
      return newObj;
      }
    
  • 介绍两个用来做深拷贝的库

**jquery**
//使用方法:
let targetObj = $.extent(true,{},sourceObj)
//**lodash函数库**
//使用方法:
//npm install lodash
//**es5写法**
let lodash = require('lodash')
//**es6写法**
import lodash from 'lodash'

let targetOj = lodash.cloneDeep(sourceObj)