IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 对Javscript中浅拷贝和深拷贝的探索和详解 -> 正文阅读

[JavaScript知识库]对Javscript中浅拷贝和深拷贝的探索和详解

前言

要想对深浅拷贝理解透彻,那我们还真不能开门见山,我们还得先了解一下门,再找到钥匙,把门打开后才会看到山,这样一来你也就完全理解了山存在的意义;

因此我们要先了解一下中Js的数据类型以及存储方式,就会再认识到什么是栈(stack)和堆(heap),接下来你还会意识到对象的赋值和赋址的区别,然后会继续探索对深浅拷贝的理解以及赋值和浅拷贝的区别,最后我们还要能手动实现对象的深浅拷贝

事实上,深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的

那接下来我们也会围绕以上提到的几个知识点串联一下,融会贯通,一步一步去探索和理解深浅拷贝;


Js数据类型和存储

面试中第一个最常问到的就是让我们说出Js有哪些数据类型了,这个大家都不陌生,有两种数据类型,分别是基本数据类型引用数据类型
在这里插入图片描述

基本数据类型

  • 基本数据类型包括 String,Number,Boolean,Undefined,Null,Symbol(其中Symbol是ES6新增,表示独一无二的值)
  • 数据类型存储在栈(stack)内存中的,数据大小确定,内存空间大小可以分配。

引用数据类型

  • 引用类型统称为Object,细分的话,分为5个:Object对象、Array数组、RegExp正则、Date时间对象、Function函数
  • 引用数据类型是存储在堆(heap)内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置
  • 每个内存空间大小不一样,要根据情况开进行特定的分配

那什么是栈内存什么是堆内存,这两种数据结构又是以什么的形式去存储的呢,那接下来我们就一起去了解一下栈和堆;


栈(stack)和堆(heap)

在编译阶段,除了声明变量和函数,查找环境中的标识符这两项工作之外,还会进行内存分配。不同类型的数据会分配到不同的内存空间;

栈内存
关于栈,你可以结合这么一个贴切的例子来理解,一条单车道的单行线,一端被堵住了,而另一端入口处没有任何提示信息,堵住之后就只能后进去的车子先出来,这时这个堵住的单行线就可以被看作是一个栈容器,车子开进单行线的操作叫做入栈,车子倒出去的操作叫做出栈。

所以,栈就是类似于一端被堵住的单行线,车子类似于栈中的元素,栈中的元素满足后进先出的特点。(前面我还写了一篇JavaScript的执行机制——调用栈,可供参考有助于更理解什么是栈)

  • 栈主要用于来保存基本值和引用类型值的地址;
  • 存放的变量一般都是已知大小或者已知上限范围的,算是一种简单存储;
  • 栈是自动分配的相对固定大小的内存空间,其数据读取快,写入速度快,但存储内容少,变量一旦不使用就由系统自动清理释放;

堆内存

堆的存取是随意,这就如同我们在图书馆的书架上取书, 虽然书的摆放是有顺序的,但是我们想取任意一本时不必像栈一样,先取出前面所有的书, 我们只需要关心书的名字。

  • 用来保存一组无序且唯一的引用类型值,可以使用栈中的键名来取得。
  • 堆的读取和写入速度慢,但存储的内容多,一般对象会存储在堆中,存储的数据对于大小在这方面都是未知的
  • 堆是动态分配的内存,大小不定也不会自动释放。

在这里插入图片描述


赋值和赋址的区别

前面之所以要说明什么是内存中的堆、栈以及变量类型,实际上就是为了更好的理解赋值,深拷贝以及浅拷贝;

基本类型与引用类型最大的区别实际就是赋值与赋址的区别。

赋值与赋址

为一个变量赋基本值时,实际上是创建一个新值,然后把该值赋给新变量,可以说这是一种真正意义上的" 赋值 “;

为一个变量赋引用值时,实际上是为新变量添加一个指针,指向堆内存中的一个对象,属于一种” 赋址 "操作;

  var a = [1,2,3,4,5];
  var b = a;//赋址 ,把a的地址指针赋值给了b变量
  var c = a[0];//赋值,把对象中的属性/数组中的数组项赋值给变量,这时变量C是基本数据类型,存储在栈内存中
  alert(b);//1,2,3,4,5
  alert(c);//1

  //改变数值        
  b[4] = 6;
  c = 7;
  alert(a[4]);//6
  alert(a[0]);//1

在这里插入图片描述

从上面我们可以得知,当我改变b中的数据时,a中数据也发生了变化;但是当我改变c的数据值时,a却没有发生改变。

这就是赋值与赋址的区别,因为a是数组,属于引用类型,所以它赋予给b的时候传的是栈中的地址(相当于新建了一个不同名“指针”),而不是堆内存中的对象。而c仅仅是从a堆内存中获取的一个数据值,并保存在栈中。所以b修改的时候,会根据地址回到a堆中修改,c则直接在栈中修改,并且不能指向a堆内存中。


浅拷贝和深拷贝

所谓拷贝,就是赋值。把一个变量赋给另外一个变量,就是把变量的内容进行拷贝。把一个对象的值赋给另外一个对象,就是把一个对象拷贝一份。

基本类型数据赋值时,赋的是数据,所以不存在深拷贝和浅拷贝的问题;

浅拷贝
创建一个新的对象,不会指向同一个地址,这个对象有着原始对象属性值的精确拷贝。就是对象的浅拷贝只会对“主”对象进行拷贝,拷贝的是对象属性的基本类型的值,如果属性是引用类型,拷贝的就是内存地址,拷贝的不深,所以称为浅拷贝;

  • 修改浅拷贝对象第一层的非对象引用类属性,都不会影响原对象
  • 由于浅拷贝不会拷贝对象里面的对象,“里面的对象”会和原对象共享内存,所以修改浅拷贝对象的子属性对象里面的属性,原对象也会受到影响

利用 for…in循环实现浅拷贝

	let obj = {
	    name: '李四',
	    age: 20,
	    action: {
	        eat: '苹果',
	        sing: '《安静》'
	    },
	};
	
	let obj2 = {};
	for (let key in obj) {
	    obj2[key] = obj[key];
	}
	
	obj2.name = '小六';
	obj2.sex = '男';
	obj2.sction.eat = '草莓';
	
	console.log('obj', obj);
	// obj  {name: '李四',age: 20,action: {eat: '草莓',sing: "《安静》"}}
	console.log('obj2', obj2);
	// obj2  {name: '小六',age: 20,action: {eat: '草莓',sing: "《安静》"},sex:‘男’}

利用Object.assign()方法

let obj = {
	    name: '李四',
	    age: 20,
	    action: {
	        eat: '苹果',
	        sing: '《安静》'
	    },
	};
	
    Object.assign(obj2, obj);
	
	obj2.name = '张三';
	obj2.sex = '男';
	obj2.sction.eat = '草莓';
	
	console.log('obj', obj);
	// obj  {name: '李四',age: 20,action: {eat: '草莓',sing: "《安静》"}}
	console.log('obj2', obj2);
	// obj2  {name: '张三',age: 20,action: {eat: '草莓',sing: "《安静》"},sex:‘男’}

深拷贝

深拷贝不仅将元对象的各个属性逐个复制,还将原对象各个属性所包含的对象属性也一次采用深复制的方法递归复制到新对象上,所以修改拷贝后的对象不会影响原对象;

使用递归的方式实现深拷贝

    function deepClone1(obj) {
      //判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
      var objClone = Array.isArray(obj) ? [] : {};
      //进行深拷贝的不能为空,并且是对象或者是
      if (obj && typeof obj === "object") {
        for (key in obj) {
          if (obj.hasOwnProperty(key)) {
            if (obj[key] && typeof obj[key] === "object") {
              objClone[key] = deepClone1(obj[key]);
            } else {
              objClone[key] = obj[key];
            }
          }
        }
      }
      return objClone;
    }

通过 JSON 对象实现深拷贝

  • 这种方式无法拷贝 正则表达式,undefine,function
 //通过js的内置对象JSON来进行数组对象的深拷贝
    function deepClone2(obj) {
      var _obj = JSON.stringify(obj),
        objClone = JSON.parse(_obj);
      return objClone;
    }

通过jQuery的extend方法实现深拷贝

  • 第一个参数 true 为深拷贝,false 为浅拷贝
  var newArray = $.extend(true,[],array);

slice()、concat()对数组进行深拷贝

	let arr = [1, 2, 3, 4, 5, 6];
	
	let arr2 = arr.slice(0);
	arr2[0] = 'Rose';
	arr2[1] = '女';
	
	let arr3 = arr.concat();
	arr3[0] = 'Tom';
	arr3[1] = '男';
	
	console.log('arr', arr); // [1, 2, 3, 4, 5, 6]
	console.log('arr2', arr2); // ["Rose", "女", 3, 4, 5, 6]
	console.log('arr3', arr3); // ["Tom", "男", 3, 4, 5, 6]

赋值、浅拷贝和深拷贝的区别

和原数据是否指向同一个对象第一层数据为基本数据类型原数据中包含的子对象
赋值会使原数据一起改变会使原数据一起改变
浅拷贝不会使原数据一起改变会使原数据一起改变
深拷贝不会使原数据一起改变不会使原数据一起改变

上面已经详细的介绍了赋值,浅拷贝和深拷贝,不理解的可以再认真查看,为了更方便了解,下面我放上三者的内存图:

赋值
在这里插入图片描述

浅拷贝
在这里插入图片描述

深拷贝
在这里插入图片描述

上面的内存图是目前我按照个人的理解去画的,如您参考时发现错误,请及时留言纠正,我们一起学习,一起进步;

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-25 12:26:58  更:2021-10-25 12:28:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:51:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码