深入 简单 直观 的理解 深拷贝 浅拷贝问题,以及如何实现深拷贝 浅拷贝,前端面试

笔者最近看了js的深拷贝浅拷贝的问题,刚开始比较难以理解,因为不同文章质量参差不齐,有的越看越懵,但是在看了好几篇文章,外加自己梳理后,慢慢搞清楚了,本文将尽量以通俗易懂简洁的方式讲解相关知识,以供大家更直观深入的理解。

学好深拷贝浅拷贝,这一篇就够了!

目录

首先,要想弄清楚深拷贝,浅拷贝问题,我们必须先弄清楚一个问题:

接下来我们步入正题:深拷贝,浅拷贝

浅拷贝深入讲解:

深拷贝深入讲解:

一.一般的深拷贝

二.对象嵌套时的深拷贝


首先,要想弄清楚深拷贝,浅拷贝问题,我们必须先弄清楚一个问题:

js中的数据类型

js中数据类型共分为两大种:

1.基本数据类型:7个 Number,String,Boolean,null,undefined,symbol独一无二的值,bigint表示任意大的整数。(后两个为ES6新增)

2.引用数据类型:object(对象类型)(其中数组(Array)、函数(function)是一种特殊的对象)

关于这两大种数据类型的差别,主要有两方面:

1.存储在内存里的位置不一样。

基本数据类型是直接存储在中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基本类型值和执行代码的空间。

引用数据类型是存储在内存中,占据空间大、大小不固定。引用数据类型在中存储了指针,该指针指向堆 中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。

引用数据类型存的值(即地址)在栈,但是值(地址)指向的数据在堆区

2.基本数据类型变量,对应的内存区域存储的直接是值。而引用数据类型存储的是存储的是相关值的地址。

例:(写function 是为了表示year,fruit为局部变量)

function test(){
    var year=2022;
    var fruit=[banana,apple,orange]
}

对应的内存图:

因为year为基本数据类型,所以其存储在栈区,而fruie为引用数据类型,其存的值为地址在栈区,其地址指向的值存在堆区。  

综上,我们可以做一个小结:基本数据类型直接存的值和引用数据类型直接存的值(地址)都存在栈中,而引用数据类型存的地址指向的数据存在堆中。

接下来我们步入正题:深拷贝,浅拷贝

深拷贝浅拷贝都是对于引用数据类型来说的,基本数据类型没有深拷贝浅拷贝之分。

何为拷贝?拷贝就是复制粘贴。何为深拷贝,浅拷贝?我们先看一个例子:

var arr1=[0,1,2,3]
var arr2=arr1;

 将数组arr1的值赋给arr2,如果我们执行语句arr2[0]=66;我们就会发现arr1[0]和arr2[0]的值都会变成66。这就是一个很典型的浅拷贝。

var num1=1;
var num2=num1;

若执行语句num2=200;那么num1的值还是1,num2的值为200,互不影响。

所以何为深拷贝浅拷贝?

1.根本的区别:浅拷贝就是简单的把指向别人的值的一个地址给复制过来,拷贝的深度不够 。深拷贝就是实实在在的把别人的值给复制过来。

2.直观的区别:浅拷贝就是双方不是独立的,还会互相影响;深拷贝是不会影响到彼此,是独立的个体。

所以我们说基本数据类型没有深拷贝浅拷贝之分,都是深拷贝,即互不影响。

现在请记住这一句话(非常重要):

如果我们直接用=用A变量对B变量进行赋值(B=A),我们实际上是将A中直接存的东西(值或地址)赋给了B。

浅拷贝深入讲解:

例:

//定义一个对象
var test={
    tname:"oliver",
    tage:18,
    tcolor:['red','blue','yellow']
}

内存图:

解释一下:因为test是对象,所以他是引用数据类型,所以他在栈区存的是指向内容的地址,而他里面的内容(tname,tage,tcolor)则存在堆区。

对于堆区里面的tcolor是数组(特殊的对象),所以也是引用数据类型,所以存的是地址,指向数组中的内容(也存在堆中)

进行一次浅拷贝:

var test2={};
for(let i in test){
    test2[i]=test[i];
}
test2.tcolor[0]="RED";
console.log(test);
console.log(test2);

运行结果: 

我们发现test和test2里的tcolor[0]都发生了改变.

内存图:

解释一下:test2中依次直接复制了test中的值:test[0]的oliver,test[1]的18,test[2]的地址,所以test2[2]指向了和test[2]中同样的地址

深拷贝深入讲解:

一.一般的深拷贝

还用上面的例子:要想实现深拷贝,见以下代码:

var test2={};
for(let i in test){
    if(typeof test[i]=='object'){//若test[i]为对象类型(数组)即引用数据类型,则进行深拷贝
        test2[i]=[];
            for(let j in test[i]){
                test2[i][j]=test[i][j];
            }
    }
    else{
        test2[i]=test[i];
    }
}
test2.tcolor[0]="RED";
console.log(test);
console.log(test2);

运行结果:

我们发现只有test2中的tcolor[1]发生了变化

内存图:

解释一下:对于数组tcolor,tcolor直接存储的是地址,但是tcolor[i]直接存储的是具体的数值而非地址,所以在代码运行的时候test2[tcolor].[0],test2[tcolor].[0],test2[tcolor].[0]依次被test[tcolor].[i]赋值。

二.对象嵌套时的深拷贝

接下来我们加大一下难度:如果对象里是的属性也是对象,那我们该怎么办?

举个例子吧:

//定义一个对象
var test={
    tname:"oliver",
    tage:18,
    tcat:{
        cname:"fatty",
        cage:3,
        cfood:{
            morning:"fish",
            noon:"potato"
              }
         }
}

我们发现test对象里面套了两层对象(tcat,cfood)

针对这种层层嵌套的问题,我们只能递归去解决,代码如下:

function deepCopy(obj){
    let newObj={}
    for(let i in obj){
        if(typeof obj[i]=='object'){ //若obj[i]为object(即引用数据类型),进行递归
            newObj[i]=deepCopy(obj[i])
        }else{
            newObj[i]=obj[i]
        }
    }
    return newObj;
}
var test2=deepCopy(test);
    
    test2.tcat.cname="FATTY";
    console.log(test);
    console.log(test2);

运行结果:

我们将test2中的tcat的cname修改成了FATTY,只有test2中的值发生了改变,test未改变,深拷贝成功!

码字不易,希望大家多多鼓励!

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue.js 是一个流行的 JavaScript 前端框架,被广泛应用于 Web 应用程序开发。在 Vue.js 面试中,可能会涉及多个问题,包括框架的基础知识和应用实践等,以下是一些必问题和答案供参考: 1. 请简要介绍 Vue.js 的特点和优点。 Vue.js的特点和优点包括:轻量级,易学易用,组件化思想,数据双向绑定,完整的渐进式框架,灵活的路由系统和状态管理,快速构建单页应用,高性能的虚拟DOM,模板语法简单易懂,生态系统丰富,社区活跃。 2. 什么是Vue.js的生命周期? Vue.js的生命周期包含了钩子函数,它们在 Vue 实例从初始化到销毁的过程中执行,分别包括:创建前/后,载入前/后,更新前/后,销毁前/后等8个阶段。 3. 请介绍Vue.js中的模板语法以及其特点 Vue.js的模板语法包括插值、指令和事件绑定等几种形式,通过简单的表达式绑定数据,实现数据的展示和交互效果,具有简洁直观、易于理解、易于调试等特点。 4. Vue.js的组件化如何实现? Vue.js的组件化实际上基于Vue.js实例的基础,使用Vue.component()方法来创建一个新的组件,该方法接受两个参数:组件名称和组件选项对象,然后将组件注册到Vue实例中,即可在其它组件或者页面中进行调用和使用。 5. Vue.js的路由系统如何实现? Vue.js的路由系统通过vue-router插件来实现,它是基于Vue.js实现的一个轻量级的路由管理器,能够实现前端路由映射、页面跳转、URL参数传递和状态管理等功能。通过Vue.use()方法引入路由插件即可使用,然后通过VueRouter实例化对象并配置路由信息,最后在Vue实例中注册路由即可实现路由系统。 总的来说,Vue.js 是一个功能强大、易用灵活的前端框架,掌握Vue.js的技能对Web开发人员来说是非常重要的。以上问题和答案都是面试中可能涉及到的,希望开发人员在准备面试时能够充分理解和运用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bule Guy

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值