JavaScript知识之 浅拷贝与深拷贝

值传递与引用传递

JavaScript中有两种数据类型:基本数据类型引用数据类型两种。
从名字上来看,大概也能猜到它们的区别:

  • 基本数据类型
    • 值直接存储在栈内存中
  • 对于引用类型来说
    • 它存储了一个引用,而真正的数据存储在堆内存中

当基本数据类型 a 赋值给另一个基本数据类型 b 时,是值传递;当 a 值变化后, b 的值并不会跟着变化:

let a = 5;
b = a; // a 赋值给b
console.log(a); // 5
console.log(b); // 5
a = 15; // a变化
console.log(a); // 15
console.log(b); // 5

base1
在这里插入图片描述

而当引用数据类型赋值给另一个基本数据类型时,是引用传递;当 a 值变化后, b 的值会跟着变化:

let a = [1,2,3,4];
b = a; // a 赋值给b
console.log(a); // [ 1, 2, 3, 4 ]
console.log(b); // [ 1, 2, 3, 4 ]
a[1] = 15;
console.log(a); // [ 1, 15, 3, 4 ]
console.log(b); // [ 1, 15, 3, 4 ] b跟着变化

之所以会跟着变化,是因为引用数据类型存储的是一个引用,当它赋值给另一个引用数据时,只会把引用赋值给它;所以当原来的引用对应的值变化了,由于两个引用指向的是一个值,所以也会随着变化。

在这里插入图片描述
=>
在这里插入图片描述

浅拷贝与深拷贝

上面我们知道了引用数据类型赋值给另一个引用数据类型时,两个变量指向同一个数组,这就是所谓浅拷贝。

浅拷贝只拷贝对象的顶级属性,嵌套的引用类型则直接复制引用。

但是,有的时候,我们需要将一个变量中的 所有值都拷贝一份,当被赋值的变量变化时不会影响到原来的变量;这时候就需要深拷贝:

对于有嵌套的引用类型(如:js对象数组MapSetRegExp等),将一个变量中的 所有值都拷贝一份,使变化时不影响原先变量,就是深拷贝。

深拷贝有很多种方法,可以直接撸 js 代码,通过递归实现;也可以通过第三方库实现;甚至可以利用 JSON 里的方法实现;
下面介绍:

利用 JSON 方法

这种方法只适用于 对 js对象 实现深拷贝!

JSON.stringify()JSON.parse(),可以将原来的数据先变化为JSON字符串,然后再还原为js对象。

let obj = {
    a: [1,3,5,6,[6,7]],
    b: new Set([2,3,4]),
    c: /string/g
}
let objString = JSON.stringify(obj);
let obj2 = JSON.parse(objString);
let obj3 = obj; // 浅拷贝

console.log(obj); // { a: [ 1, 3, 5, 6, [ 6, 7 ] ], b: Set(3) { 2, 3, 4 }, c: /string/g }
console.log(obj2); // { a: [ 1, 3, 5, 6, [ 6, 7 ] ], b: {}, c: {} } 数据丢失
console.log(obj === obj2); // false
console.log(obj === obj3); // true

可以看到,这种方法实现深拷贝可能会出现数据丢失情况;非常不建议使用

递归函数实现

这种方法的想法很简单,就是利用递归调用,分类讨论不同的数据类型,然后寻找变量最底层的 基本数据结构,利用值传递赋值,开辟新的引用。
简单实现:

// 简单实现
function deepClone(obj){
    // 基本数据类型本身就是值传递
    if(obj === null || typeof obj !== 'object'){
        return obj;
    }
    if(obj instanceof Set){ // Set 类型
        const t = new Set();
        obj.forEach((val)=>{
            t.add(deepClone(val));
        })
        return t;
    } else if(obj instanceof Map){ // Map 类型
        const t = new Map();
        obj.forEach((val,i)=>{
            t.set(i,deepClone(val));
        })
        return t;
    } else if(obj instanceof RegExp){ // 正则 类型
        return new RegExp(obj);
    } else { // 其它 js对象
        const t = new obj.constructor();
        for(const i in obj){
            t[i] = deepClone(obj[i]);
        }
        return t;
    }
}

测试:

// test code
const obj = {
    a: 1,
    b: new Set([1,2,3,4,new Set([5,6])]),
    c: new Map([
        ['a','Number'],
        ['b','Set'],
        ['c','Map'],
        ['d','RegExp'],
        ['e','Object']
    ]),
    d: /^regexp/g,
    e: [{a:10},new Date()]
}
const deep = deepClone(obj); // 深拷贝
const shadow = obj; // 浅拷贝

console.log(obj === deep); // false
console.log(obj === shadow); // true

console.log(obj.a === deep.a); // true
console.log(obj.a === shadow.a); // true
console.log(obj.b === deep.b); // false
console.log(obj.b[4] === deep.b[4]); // true
console.log(obj.b === shadow.b); // true

第三方库

第三方库都有封装好的方法,可以直接使用:

  • Lodash
    • _.cloneDeep(value)方法
  • Ramda
    • R.clone(objects)方法

有兴趣的可以去 github 阅读这些第三方库的底层实现原理:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值