# 深浅拷贝

clone

# 一、数据类型

最新的 ECMAScript 标准定义了 8 种数据类型:

  • 7种原始类型(String、Number、Boolean、Null、Undefined、BigInt、Symbol):栈内存,使用 typeof 运算符检查。但 typeof null === "object"
  • 引用类型(Object):引用地址-栈内存,指向内容-堆内存,instanceof

# 二、浅拷贝

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。浅拷贝,引用类型拷贝的就是内存地址。下面简单实现一个浅拷贝:

function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

JavaScript中,浅拷贝的实现方式有:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制

# 1. Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade

注意:当object只有一层的时候,是深拷贝

let obj = {
    username: 'kobe'
    };
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}

# 2. slice()、concat()

补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}]
const fxArrs = fxArr.slice(0)
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "Two", "Three", { name: 'Bob'}] // 基础类型未改变,引用类型的值改变了
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]

# 3. 拓展运算符

const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}]
const fxArrs = [...fxArr]
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "Two", "Three", { name: 'Bob'}]
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]

# 三、深拷贝

深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

目前实现深拷贝的方法不多,主要是两种:

# 1. JSON.parse(JSON.stringify())

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

undefinedfunctionsymbol、正则、 new Date()、数字对象 会在转换过程中被忽略或不好使。

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false

# 2. 手写递归

递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

// 实现深度克隆---对象/数组
function deepClone(target) {
    // 初始化变量 result 成为最终克隆的数据;判断数据类型
    let result, targetType = checkedType(target);
    if (targetType === 'Object') {
        result = {};
    } else if (targetType === 'Array') {
        result = [];
    } else {
        return target;
    }
    // 遍历目标数据
    for (let i in target) {
        let value = target[i];
        if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
            result[i] = deepClone(value); // 递归遍历获取到value值
        } else {
            result[i] = value;
        }
    }
    return result;
}
// 定义检测数据类型的功能函数
function checkedType(target) {
    return Object.prototype.toString.call(target).slice(8, -1); // [object Object] | [object Array]
}

# 3. Lodash.cloneDeep()

该函数库也有提供_.cloneDeep用来做 Deep Copy

var _ = require('lodash');
var obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false

# 四、区别

# 赋值与浅拷贝

赋值:赋的其实是该对象在栈中的地址,而不是堆中的数据

浅拷贝:是按位拷贝对象,创建一个新对象,对原始对象属性值的一份精确拷贝。

// 赋值
const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}];
const fxArrs = fxArr;
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "love", "Three", { name: 'Bob'}] // 原对象一同改变
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]

// 浅拷贝
const fxArr = ["One", "Two", "Three", { name: 'Jimmy'}];
const fxArrs = [...fxArr];
fxArrs[1] = "love";
fxArrs[3].name = 'Bob';
console.log(fxArr) // ["One", "Two", "Three", { name: 'Bob'}] // 基础类型未改变,引用类型的值改变了
console.log(fxArrs) // ["One", "love", "Three", { name: 'Bob'}]

# 深拷贝与浅拷贝

对于基本数据类型的拷贝,并没有深浅拷贝的区别,我们所说的深浅拷贝都是对于引用数据类型而言的。

  • 浅拷贝:就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生变化。
  • 深拷贝:就是对目标的完全拷贝,引用和值都复制了。只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
类型 与原数据是否指向同一对象 值为基本数据类型时是否一同改变 值为对象时是否一同改变
赋值
浅拷贝 不变
深拷贝 不变 不变

# 参考文献

更新时间: 3/28/2022, 7:14:34 PM