likes
comments
collection
share

我猜你不会拷贝!

作者站长头像
站长
· 阅读数 26

不管是在写代码的时候还是在日常处理文件的时候,我们都会遇到复制粘贴的情况,就是将一个文件或一段代码复制一份到其他的地方使用。这个过程叫复制,又叫拷贝。但是我们今天聊的拷贝并不是我们常用的只用ctrl+c操作,而是在JavaScript中将一个变量值复制给另外的一个变量。这样听起来很简单,有些人认为直接将 a = b,进行赋值操作就行了。但是!并没有那么简单!

为什么说拷贝并不是a=b呢?

拷贝,是从一个现有的数据实例生成一个新的实例,使得新实例具有与原实例相同的数据状态,也就是说拷贝过来的数据与原数据一模一样。那我们常用的赋值操作就这样的啊,咋还说不是a=b呢。我们想想我们平时在日常生活中进行的拷贝工作,如果我们修改原始的文件内容,那我们拷贝得到的副本是不会变的。但是在我们的代码人生中就会出现一种修改了原本数据,副本中的数据也会发生改变。所以说并不是仅仅是a=b这么简单。例如:

let a ={
    name:'tom',
    age:18
}

let b = a

a.age = 20
console.log(b);

此时我们得到的结果是: 我猜你不会拷贝!

修改了对象a中的属性值,最后b中的属性值也发生了改变。嗯?what?这与我们的想法不一样啊?

其实我们弄清楚了js的执行过程我们就容易弄清楚了。

为什么b中的属性值也会发生改变?

在JavaScript代码实际执行之前,js引擎会进行预编译。它会在调用栈中创建一个全局执行上下文或者函数执行上下文(对于函数内部的代码)。在这个阶段,变量会被初始化(但尚未赋值,除了直接赋默认值的const和let声明),函数定义会被处理,作用域链会被建立。接下来,就开始进行赋值。此时,原始数据类型的变量就开始直接赋值了,但是引用数据类型是在堆里面,只能赋个地址给引用类型的变量,到时候直接在堆中寻找这个地址。用图可能更好理解一点

我猜你不会拷贝!

而我们使用obj1=obj时,也是直接将引用地址传给了obj,所以修改obj中的值,就是通过地址找到然后进行修改。obj1中的值也会发生改变。

浅拷贝与深拷贝

我们会发现有两种拷贝情况:一种是拷贝后,修改原始值后,拷贝后的副本值不会发生改变,一种是修改了原始值后,副本也会发生改变。我们将这两种情况称为深拷贝和浅拷贝。

浅拷贝

顾名思义,是浅浅的拷贝并不深入,即拷贝后的对象受原对象的影响(共用同一个地址)

我们常见的浅拷贝有如下一些:

  1. Object.create(obj)
  2. Object.assign({},obj)
  3. [].concat(arr)
  4. 解构
  5. arr.slice(0)

通过这些拷贝后得到的对象会因为原来的对象发生改变而改变

let obj = {
    a:1,
    b:[1,2]
}
let obj2 = Object.assign({},obj)
obj.b.push(3)
console.log(obj2);

此时打印出来的值就是

let obj2 = { a:1, b:[1,2,3] }
  • Object.assign() :运用对象中合并两个对象的方法来实现,该方法接受俩个参数,将后者合并到前者。
  • Object.create():接收一个对象参数,创建一个新对象,其原型为提供的对象。
  • [].concat(arr):是数组的合并方法
  • arr.slice(0):这是切割数组返回一个新数组的方法 ···

通过这些我们就可以发现,我们想要实现一个浅拷贝方法,就需要新建一个对象,然后向其中添加原来对象的属性和值。通过这个我们可以自己手搓一个浅拷贝的实现方法。

function shallowCopy(obj){
    let newobj = {}
    for(let key in obj){
        // 判断是不是显示具有的
        if(obj.hasOwnProperty(key)){
            newobj[key] = obj[key]
        }
    }
    return newobj
}

这个还是比较简单的,就是新建一个对象,然后遍历原对象,向新对象中添加值。注意我们要在这里判断一下是不是对象直接拥有的属性,如果是的话就向新对象中添加。

深拷贝

深拷贝就是完全复制对象及其所有嵌套的对象,产生一个全新的独立对象,无论怎样修改都不会影响到原对象。

一些大佬们在没出es6新特性的时候,通常使用JSON.parse(JSON.stringify(obj)) 方法,将需要对象先转出字符串,在转换成对象以得到一个全新的对象,这个对象与之前那个对象就没有任何关联了。所以就是一个简单的深拷贝例子。但是这个方法还是有一定的局限性的,例如:

  1. 无法识别bigInt类型
  2. 无法拷贝undefined,function,Symbol
  3. 无法处理循环引用
let obj = {
    a:1,
    b:{n:2},
    c:'cc',
    d:true,
    e:null,
    f:undefined,
    g:function(){},
    h:Symbol(1),
     i:123n
}
 let newObj = JSON.parse(JSON.stringify(obj))

console.log(newObj);

我猜你不会拷贝!

之后又出现了一个新的方法,这使得深拷贝变得容易了不少structuredClone(obj) 但是这个方法也有一定的缺陷,就是不能拷贝functionSymbol类型的数据,具体的大佬们可以一会去试试。

重点:面试官叫你手写一个深拷贝函数。

其实这个和浅拷贝差不了太多,我们深拷贝就是要让拷贝后的对象完全独立于老对象。所以我们就需要一层一层的遍历对象中的对象,确保每一层都是独立的。这里我们可以使用递归的思想,其他的都差不了太多!

丢一份手写深拷贝出来:

function deepCopy(obj){
    let newObj = {}
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            // 判断obj[key]是不是对象
            if(obj[key] instanceof Object){
                newObj[key] = deepCopy(obj[key])
            }else{
                newObj[key] = obj[key]
            }
        }
    }
    return newObj
}

总结

深拷贝与浅拷贝是JavaScript中处理对象复制时不可忽视的概念。选择合适的拷贝方式,能够有效地避免数据不一致和难以预料的副作用,从而保证程序的稳定性和可靠性。所以大佬不要以后听到拷贝就认为是复制粘贴哦,还有在拷贝对象哦!

转载自:https://juejin.cn/post/7391703459810902068
评论
请登录