深拷贝和浅拷贝

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

栈堆、基本数据类型、引用数据类型

栈堆:存放数据的地方

基本数据类型:number,string,boolean,null,undefined.

引用数据类型(Object类)有常规名值对的无序对象{a:1},数组,以及函数等。

浅拷贝

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

深拷贝

const simpleClone = param => JSON.parse(JSON.stringify(param));

let a = [1,2,3]; b = simpleClone(a);
console.log(a === b); // false
a[2] = 'a';
console.log(a,b) // (3) [1, 2, 'a'] (3) [1, 2, 3]

引用类型和基本类型栈内存储

  • 基础类型
  • 引用类型

当引用类型参与判断时,其实是判断内存地址是否相同(是否是一个地址)

一些常见的例子

Array的slice和concat方法

slice拷贝

let a = [1,2,3];
let b = a.slice(); //slice
console.log(b === a); // false
a[0] = 4;
console.log(a); // (3) [4, 2, 3]
console.log(b); // (3) [1, 2, 3]

concat拷贝

let a = [1,2,3];
let b = a.concat();  //concat
console.log(b === a); // false
a[0] = 4;
console.log(a); // (3) [4, 2, 3]
console.log(b); // (3) [1, 2, 3]

看到结果,如果你觉得,这两个方法是深拷贝,那就恭喜你跳进了坑里!

看下面的例子

let a = [[1,2,3],4,5];
let b = a.slice();
console.log(a === b); // false
a[0][0] = 6;
a[1] = 'a';
console.log(a);
console.log(b);

可以看到 slicecontact 对于第一层是深拷贝,但对于多层的时候,是复制的引用,所以是浅拷贝

展开运算符
let a = {obj: {a:1,b:2}, k1: 1, k2: 2};
let b = {...a};
console.log(a === b); // false
a.k1 = 2;
a.obj.a = 'a';
console.log(a);
console.log(b);

slicecontact 类似,展开运算符也是一层深拷贝,多层为引用关系,也是浅拷贝

对数组使用展开运算符相同

Object.assign

先说结论:一层深拷贝,多层为引用关系,也是浅拷贝

使用babel将ES6转换为ES5后,获取了Object.assign方法的ES5版本:

rowData=Object.assign(rowData,obj);

//ES5版本代码如下:

var _extends = Object.assign || function (target) {
    for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];
        //遍历一个对象的自身和继承来的属性,
        //常常配合hasOwnProperty筛选出对象自身的属性
        for (var key in source) {  
            //使用call方法,避免原型对象扩展带来的干扰
            if (Object.prototype.hasOwnProperty.call(source, key)) {
                target[key] = source[key];
            }
        }
    }
    return target;
};

rowData = _extends(rowData, obj);

一目了然,这也就解释了,为什么 Object.assign 是浅拷贝,因为只处理第一层关系

真正的深拷贝

大家都知道使用 JSON序列化 实现的 深拷贝 是不完整的

  • 如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
  • 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;
  • 如果obj里有function,Symbol 类型,undefined,则序列化的结果会把函数或 undefined丢失;
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
  • JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
let obj1 = {
    str: '',
    bool: true,
    num: 0,
    numNaN: NaN,
    numMax: Infinity,
    valNull: null,
    valUndefined: undefined,
    valReg: new RegExp(),
    valSymbol: Symbol(),
    arr: [],
    obj: {},
    funArrow: () => {},
    fun: function(){}
}
const simpleClone = param => JSON.parse(JSON.stringify(param));
let objClone = simpleClone(obj1);

所以真正的深拷贝就会用到 递归类型判断

function cloneDeep(obj) {
    let newObj = Array.isArray(obj) ? [] : {}
    if (obj && typeof obj === "object") {
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (obj[key] === null) {
                    newObj[key] = null; // null为 object
                } else if (obj[key] instanceof RegExp) {
                    newObj[key] = new RegExp(obj[key]); // reg 也是object
                } else {
                    newObj[key] = (obj && typeof obj[key] === 'object') ? cloneDeep(obj[key]) : obj[key];
                }
            }
        }
    } 
    return newObj
}

TIP

虽然说一些特殊类型 json序列化 会丢失,但其实也能满足日常的简单使用,如果真的涉及特殊类 日期 reg Error symbol function

不如考虑用其他方式,通过构造函数创建,class...

另外,关于 symbol ,本身就是一个 唯一 同时又是一个单独类型,目前开发暂时还没有用到过,这里就不再多做处理了,主要还是 null

Last Updated:
Contributors: susheng