likes
comments
collection
share

聊一聊深拷贝和浅拷贝

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

前言

面试官:你说一下什么是深拷贝,什么是浅拷贝吧

我: 浅拷贝顾名思义就是浅层拷贝 ,就如一个对象,存有基本数据和引用数据,基本数据会直接赋值,引用类型数据会存放地址。当浅拷贝出来的这个数据修改基本数据类型时,对源数据无影响,修改引用类型时会源数据会同步变化,比如我们常用的三点运算符, Object.assign 等。深拷贝的话就是无论是基本类型数据还是引用类型数据都会开辟新的空间来存放,修改数据时对源数据无影响,比如我们常用的 JSON.parse(JSON.stringify(xxx)) 或者 lodash 中的 cloenDeep 等。

面试官: 为什么浅拷贝时改变引用类型数据源数据会改变呢?

我: 基本类型数据是存放在栈中,引用类型数据是存放在堆中,当一个数据中有引用类型数据时,它是在栈中存放一个指针,指向堆中存放的数据。当进行浅拷贝时,拷贝的是指针指向,而不是真实数据,所以浅拷贝的引用数据改变时源数据会发生改变。

面试官:那使用 JSON 进行深拷贝有什么弊端呢?

我: 使用 JSON 需要源数据中不能有函数,正则,new Date() 对象等,不然拷贝出来的数据会不一致

面试官: 为什么,知道原因吗?

我:这个不太清楚

面试官:那你有自己手写过深拷贝吗,需要考虑哪些因素呢

我: 就是用递归方式实现,如果是基本数据类型就赋值,如果是引用类型就递归处理呗,考虑因素就是类型嘛。平常项目直接用 JSON 或者 cloneDeep,具体的写法没考虑过那么多

面试官:这......那你回去等通知吧

一天后: 通过与您沟通,您的简历与我司不匹配,已将简历收入我们的人才库,希望有机会再合作

在聊深拷贝和浅拷贝之前我们先聊下 JS 数据类型和赋值的概念

JS 数据类型

分为两种:基本数据类型引用数据类型 基本数据类型: number boolean null undefined string bigint symbol 引用数据类型:Object 包括对象 数组,Function 等 存放地址: 基本类型数据的值存在栈中,引入类型数据的指针存在栈中,数据存在堆中

赋值

说深浅拷贝之前先说下复制或者加赋值。简单来说就是数据A打包给到数据B 但包里有什么东西,B用了以后会不会导致A改变呢,取决于A属于哪种数据类型

基本数据类型赋值: 赋值时候直接开辟了一个空间,来存放数据,此时B已经和A没有关系了,无论怎么修改,都不会对A产生影响。此时是赋值 举个简单的例子。你同桌做好一份电子档作业,你想抄一下,他把文件复制后发你了,你觉得不能让老师发现,得改一改作业,无论你怎么改,都不会影响你同桌的作业内容。 我们来看下代码

const a="甄姬"
let b=a
b='李白'
console.log(a)//甄姬
console.log(b)//李白

引用类型数据赋址: 赋值时候在栈中开辟了个空间,来放对象的引用地址,两者会指向同一个地址。新数据改变,源数据同步改变,此时是赋址 举个简单的例子 你的卡里面有五毛钱,你绑定了微信或者支付宝。当你从微信或者支付宝消费时,余额都会减少。 我们来看下代码

const role={
    name:'甄姬',
    job:'法师'
}
const user=role
user.name="李白"
console.log(role)
console.log(user)

聊一聊深拷贝和浅拷贝

浅拷贝

浅拷贝 顾名思义就是浅层的赋值。结合我们上面聊的赋值和赋址,当对象的属性是基本数据类型的话,他是赋值,如果是引用数据类型就是赋址 举个例子

const user={
    name:'甄姬',
    age:999,
    skill:{
        one:'击飞',
        two:'弹弹弹'
    }
}

const user2={...user}//扩展运算符我们会面会聊
user2.name='李白'
user2.skill.one="忘了啥技能"
console.log(user)
console.log(user2)

聊一聊深拷贝和浅拷贝

常用的浅拷贝方法

扩展运算法符

我们来聊下 const user2={...user} 这里面的运算 三个点叫做扩展运算符 ,也叫三点运算符,作用就是将这些数据进行一个拆解 与他相反作用(一般用来参数整合)的是rest参数,具体可以参考大佬博客 es6.ruanyifeng.com/#docs/funct… rest参数 es6.ruanyifeng.com/#docs/array 扩展运算符

那顺便简单聊下三点运算符的常用场景

1 对象浅拷贝

2 字符串转为数组

const str='abcdefg'
const arr=[...str]
console.log(arr)

3 数组合并

const arr1=[1,2,3]
const arr2=[4,5,6]
const arr3=[...arr1,...arr2]
console.log(arr3)

4 将类数组对象转化为数组

function add(){
  console.log(arguments)
  const arr=[...arguments]
  return arr
}
add('甄姬','李白') // ['甄姬', '李白']

5 用于函数调用

function add(x, y) {
  return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42

Object.assign()

该方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。返回目标对象

Object.assign(target, ...sources) 参数: target--->目标对象 source--->源对象 返回值:target,即目标对象 举例

const user={
    name:'甄姬',
    age:999,
    skill:{
        one:'击飞',
        two:'弹弹弹'
    }
}
const user3= Object.assign({}, user) 拷贝一个对象    
const user4= Object.assign({}, user,{job:'法师'}) 拷贝多个对象

深拷贝

深拷贝就比较好理解了,无论你是什么类型的数据,我都给你一份新的,A是A,B是B 我们来简单看下

const user={
    name:'甄姬',
    skill:{
        one:'击飞',
        two:'弹弹弹'
    },
}
const user2=   JSON.parse(JSON.stringify(user))
user2.name='李白'
user2.skill.one="忘了啥技能"
console.log(user)
console.log(user2) 

不过使用JSON方式对一些特殊格式数据还是有影响的,我们来简单看下

function Role (name) {
    this.name = name
}

const liBai = new Role('李白')
const user={
    name:'甄姬',
    age:NaN,
    skill:{
        one:'击飞',
        two:'弹弹弹'
    },
    time:new Date(),
    reg:/^(?:(?:\+|00)86)?1\d{10}$/,
    desc:function(){
        return "我是一个法师"
    },
    from:undefined,
    team:liBai
}
const user2=   JSON.parse(JSON.stringify(user))
user2.name='李白'
user2.skill.one="忘了啥技能"
console.log(user)
console.log(user2)

聊一聊深拷贝和浅拷贝 通过对比我们会发现 几类数据比如 NaN,函数, 构造函数, 正则,undefined ,new Date() 都会和原来数据有所不同(改变类型或者被忽略

我们来梳理一下 JSON 方式深拷贝中存在的问题

1、如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。 2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。 3、如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。 4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。 5、JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是由构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。 6、如果对象中存在循环引用的情况也无法正确实现深拷贝。

常用的深拷贝方法 (仅对一般类型使用)

JSON

JSON.parse(JSON.stringify(xxx))

lodash 中的 cloneDeep

const user2=   cloneDeep(user)

具体 lodash 其他的功能可以参考官网 www.lodashjs.com/docs/lodash…

JSON 方式为什么不能深拷贝出一些特殊类型数据呢?

这个问题查了好久,各种文章说的都是造成的影响,并没有说原因,我查到的原因感觉也不太能让自己理解,如果有大佬知道,麻烦指点一二

简单聊一下实现一个的深拷贝的思路

主要是对不同数据类型来做考虑,比如给到的数据是基本数据还是引用类型数据,是数组还是对象,如果是引用类型,除了上述的几种情况,还有递归引用,Map, Set, WeakMap, WeakSet 等等。具体可以参考这篇文章,针对每一种数据类型做了不同的逻辑处理,感觉讲的还是挺不错的。也就面试时可能会涉及到细节实现的考虑,真实项目会直接用封装好的库来使用。 www.cnblogs.com/echolun/p/1…

最后

深浅拷贝的知识点暂时就聊到这里,下期会聊下 JS 的数据类型与判断方法,也是面试经常问的一个知识点。我是南岸月明致力于用最通俗易懂的话聊明白一个知识点,希望能在帮到自己的同时也能够帮助你,共同学习是我目标!