JS-拷贝问题

前言

拷贝,在编程中用处广泛,而在JS语言中,拷贝分为两种情况:浅拷贝与深拷贝,本文就介绍了JS拷贝的相关知识。


一、浅拷贝与深拷贝有什么区别?

想要知道这两者之间的区别,首先要先明白为什么会出现这两种拷贝情况。在JS中,数据类型被分为七种基本数据类型和一种引用数据类型,分别如下。

基本数据类型

  • 字符串(String)
  • 数字(Number)
  • 布尔(Boolean)
  • 空(Null)
  • 未定义(Undefined)
  • Symbol
  • 大数(BigInt)

引用数据类型

  • 对象(Object)
  • 数组(Array)
  • 函数(Function)
  • 正则(RegExp)
  • 日期(Date)

基本数据类型存放在栈中,而引用数据类型存放在堆中,基本数据类型在调用时使用的就是它本身的数据,也因此对于基本数据类型的拷贝,并不区分什么深浅拷贝,而引用数据类型则不同,比如这个例子:

const obj = {a:1, b:2}

这行代码运行时,首先会在栈中定义一个属性obj,之后会在堆中开辟一个内存空间用于存放对象{a:1, b:2},而这个新开辟的空间会有一个自己的内存地址,假如说这里叫0x001,之后JS会把这个堆中开辟的内存地址赋值给属性obj,因此,堆于引用数据类型,在拷贝时会出现一些特殊情况,比如:

const obj1 = {a:1}
const obj2 = obj1

obj1.a = 2

console.log(obj1) // {a:2}
console.log(obj2) // {a:2}
console.log(obj1===obj2) // true

这个例子中,直接将obj1赋值给了obj2,其实在内存空间中,是相当于把obj1的内存地址直接赋值给了obj2,因此这两个属性obj1和obj2其实指向的都是同一个对象,所以,在修改obj1中的属性时,obj2中的属性也会受到影响。这种拷贝我们就称之为浅拷贝。

除了直接对引用数据类型赋值外,以下这些情况也同样属于浅拷贝
1.使用扩展运算符(…)

const obj = { a: 1, b: 2 };
const shallowCopy = { ...obj };

2.使用Object.assign()

const obj = { a: 1, b: 2 };
const shallowCopy = Object.assign({}, obj);

3.使用Array的slice()方法(仅适用于数组)

const arr = [1, 2, 3];
const shallowCopy = arr.slice();

4.手动遍历对象属性进行拷贝

function shallowCopy(original) {
  let copy = {};
  for (let key in original) {
    if (original.hasOwnProperty(key)) {
      copy[key] = original[key];
    }
  }
  return copy;
}
 
const obj = { a: 1, b: 2 };
const shallowCopy = shallowCopy(obj);

这几种常见情况都是浅拷贝,也因此当输出 obj === shallowCopy时会发现都是true。

二、深拷贝

了解了什么是浅拷贝,深拷贝也就很容易就明白了,既然浅拷贝其实拷贝的仅仅只是堆内存中的地址,那与之对应的,在堆中重新开辟一块新的内存空间,而空间中存储的值与被拷贝的值完全相同就是深拷贝了,目前深拷贝最常用的方法有两种,分别如下

1.通过JSON进行拷贝

const deepClone = function deepClone(obj){
    return JSON.parse(JSON.stringify(obj))
}
const obj1 = { a: 1, b: 2 };
const obj2 = deepClone(obj1);
console.log(obj2) //  { a: 1, b: 2 }
console.log(obj1===obj2) // false

这种方法深拷贝的方法很方便,但也有其局限性
1.会忽略不可序列化的值,例如函数、undefined、循环引用等。
2.不能复制对象的不可枚举属性,比如原型链。
3.如果对象中包含日期、正则表达式或者其他特殊值,可能会导致转换失败。

2.自己手写一个深拷贝方法

const deepClone = function deepClone(value) {
    const cache = new WeakMap(); // 用WeakMap可以不阻碍垃圾回收机制运行
    function _deepClone(value){
        if(value === null || typeof value !=='object'){
            return value        
        }    
        if(cache.has(value)){
            return cache.get(value)        
        }
        const result = Array.isArray(value)?[]:{}
        cache.set(value, result)
        for (const key in value) {
            if(value.hasOwnProperty(key)){
                result[key] = _deepClone(value[key])            
            }        
        }
        return result             
    }
    return _deepClone(value)        
} 

const obj1 = { a: 1, b: 2 };
const obj2 = deepClone(obj1);
console.log(obj2) //  { a: 1, b: 2 }
console.log(obj1===obj2) // false

思路就是:首先判断传入的拷贝是否是基本数据类型,如果是,直接返回,之后判断传入的是对象还是数组,决定返回的是什么类型,之后判断当前需要拷贝的值是否还是引用数据类型,如果是则递归调用_deepClone,否则进行赋值。这里考虑到了循环引用的问题,因此创建了一个cache用于保存当前的拷贝结果,如果发现存在引用,则直接返回缓存中的值。

3.通过MessageChannel实现

const {port1, port2} = new MessageChannel()
const obj1={a: 1, b:2}
port1.postMessage(obj1)
port2.onmessage=(res)=>{
    const obj2 = res.data
    console.log(obj2)  //  { a: 1, b: 2 }
    console.log(obj1===obj2)  // false
}

通过MessageChannel也可以实现深拷贝,但通常我们不会使用这种方法,仅仅作为了解即可,想要深入了解详情的小伙伴儿也可以下去查一下MessageChannel的功能和用法,这里就不多赘述了。


总结:

以上就是JS深浅拷贝问题的内容了,本文仅仅简单介绍了浅拷贝与深拷贝的区别和一些使用方法,想深入了解的小伙伴儿也可以进一步查找相关资料后深入学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值