前言

首先我们先来了解一下什么叫栈堆基本数据类型引用数据类型

1.栈(stack)和堆(heap)stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放。

2.基本的数据类型:String, Number, boolean, Null, Undefined,Symbol(ES6新增)  

  特点: 存储的是该对象的实际数据,(存放在栈中)

3.对象数据类型(也称为引用数据类型):Array,Object,Function

  特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存里,(存放在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置)

注:在JS中除了基数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象

1、区别: 浅拷贝/深度拷贝
判断: 拷贝是否产生了新的数据还是拷贝的是数据的引用
知识点:对象数据存放的是对象在栈内存的引用,直接复制的是对象的引用

2、常用的拷贝技术
  1). arr.concat(): 数组浅拷贝
  2). arr.slice(): 数组浅拷贝
  3).Object.assign()对象浅拷贝
  4). JSON.parse(JSON.stringify(arr/obj)): 数组或对象深拷贝, 但不能处理函数数据
  5). 浅拷贝包含函数数据的对象/数组
  6). 深拷贝包含函数数据的对象/数组

1.浅拷贝

在ES6中,Object对象新增了一个assign方法,可以实现对象的浅复制。这里谈谈Object.assign方法的具体用法,因为稍后会分析jQuery的extend方法,实现的原理同Object.assign方法差不多。

对于基本数据类型来说,复制一个变量值,本质上就是copy了这个变量。一个变量值的修改,不会影响到另外一个变量。

  1. <script type="text/javascript">
  2. /*1.直接赋值给一个变量*/
  3. let obj = {username:'genius'};
  4. console.log(obj); //genius
  5. //拷贝数组/对象 没有生成新的数据而是复制了一份引用。
  6. let arr = [1,4,{username:'lucas',age:23}];
  7. let arr2 = arr;
  8. arr2[0] = 'abcd0';
  9. console.log(arr,arr2); //俩个输出结果相同
  10.  
  11. /*Object.assign*/
  12. let colors=['red','green','blue'];
  13. let colors2=Object.assign([],colors);
  14. colors2[0]="orange";
  15. console.log(colors2);//['orange','green','blue']
  16. console.log(colors);//['red','green','blue']
  17.  
  18. /*3.concat方式*/
  19. let arr = [1,3,{username:'lucas'}];
  20. let testArr = [2,4];
  21. let arr2 = arr.concat(testArr); //连接函数
  22. //arr2[2] = {username:'tiantian'};
  23. console.log(arr2); //输出如下:数组中有对象
  24. // 输出数组:
  25. // 0:1
  26. // 1:3
  27. // 2:{username:'lucas'} //这个值会随下面的更改而变
  28. // 3:2
  29. // 4:4
  30.  
  31. /*4.slice方式*/
  32. console.log('-------------------');
  33. arr2[2].username = 'fengdudu'; //修改了arr的值,因为arr2[2]等于arr[2]
  34. console.log(arr); //和922行输出结果一样,只是下标2的内容改变了
  35. let arr3 = arr.slice(); //有startindex,endindex选项
  36. arr3[2].username = 'HHHHH'; //修改了arr的值,因为arr2[2]等于arr[2]
  37. console.log(arr); //和922行输出结果一样,只是下标2的内容改变了
  38.  
  39. /*5.深度克隆*/
  40. console.log('===================');
  41. let arr4 = JSON.parse(JSON.stringify(arr));
  42. console.log(arr4); //只拷贝了arr并没有testArr的数组,所以只有下标3长度
  43. arr4[2].username = 'duncan';
  44. console.log(arr,arr4);
  45. arr[2].username='MM'; //修改了arr、arr2、arr3的下标2的值。
  46. //JSON.stringify要求你放入的是原生的JS对象或数组,不能放数组。
  47. //JSON.stringify(arr)执行后arr的数据已经是JSON 字符串了,然后parse又转字符串,最终拿到一个JSON字符串,然后转换为一个对应的JS数组。
  48. console.log('-------------------');
  49. </script>

注:只会对只是一级属性复制,比浅拷贝多深拷贝了一层而已,所以还是无法达到深度克隆的目的.

2.深拷贝

对于复杂数据类型来说,同基本数据类型实现的不太相同。对于复杂数据类型的复制,要注意的是,变量名只是指向这个对象的指针。当我们将保存对象的一个变量赋值给另一个变量时,实际上复制的是这个指针,而两个变量都指向都一个对象。因此,一个对象的修改,会影响到另外一个对象。

在实际的开发项目中,前后端进行数据传输,主要是通过JSON实现的。

JSON对象下有两个方法,一是将JS对象转换成字符串对象的JSON.stringify方法;一个是将字符串对象转换成JS对象的JSON.parse方法。

这两个方法结合使用可以实现对象的深复制。也就是说,当我们需要复制一个obj对象时,可以先调用JSON.stringify(obj),将其转换为字符串对象,然后再调用JSON.parse方法,将其转换为JS对象。就可以轻松的实现对象的深复制。

例1

  1. function deepClone(obj){
  2. let _obj = JSON.stringify(obj),
  3. objClone = JSON.parse(_obj);
  4. return objClone
  5. }
  6. let a=[0,1,[2,3],4];
  7. b=deepClone(a);
  8.  
  9. a[0]=1;
  10. a[2][0]=1;
  11. console.log(a,b);

效果如下:

例2

  1. <script type="text/javascript">
  2. /*
  3. 思考:
  4. 如何实现深度拷贝(克隆)
  5. 拷贝的数据里有对象/数组
  6. 拷贝的数据里不能有对象/数组,即使有对象/数组可以继续遍历对象、数组拿到里边每一项值,一直拿到是基本数据类型,然后再去复制,就是深度拷贝。
  7. */
  8.  
  9. //知识点储备
  10. /*
  11. 如何判断数据类型:arr-Array null -Null
  12. 1.typeof返回的数据类型有:String,Number,Boolean,Undefined,Object,Function。
  13. 2.Object.prototype.toString.call(obj)。
  14. */
  15.  
  16. let result = 'abcd';
  17. result = null;
  18. result = [1,3];
  19. console.log(Object.prototype.toString.call(result).slice(8,-1)); //[object Array],sclice截取字符串后:Array(拿到分类)。
  20. //console.log(typeof Object.prototype.toString.call(result)); //string
  21.  
  22. //for in 循环对象(属性名)、数组(下标),推荐在循环对象属性的时候,使用for...in,在遍历数组的时候的时候使用for...of。
  23. //for in 循环遍历对象属性名
  24. let obj = {username:'zhangsan',age:22};
  25. for(let i in obj){
  26. console.log(i); //username age
  27. }
  28.  
  29. //for in 循环遍历数组下标
  30. let arr = [1,3,'abc'];
  31. for(let i in arr){ //数组的可以用for of对应数组值
  32. console.log(i); //0 1 2
  33. }
  34.  
  35. //定义检测数据类型的功能函数
  36. function checkedType(target){
  37. return Object.prototype.toString.call(target).slice(8,-1);
  38. }
  39. console.log(checkedType(result)); //Array
  40.  
  41. //实现深度克隆--对象/数组
  42. function clone(target){
  43. //判断拷贝的数据类型
  44. //初始化变量的result 成为最终克隆的数据
  45. let result,targetType = checkedType(target);
  46. if(targetType === 'Object'){
  47. result = {};
  48. }else if(targetType === 'Array'){
  49. result = [];
  50. }else{
  51. return target; //如果是基本数据类型:(String, Number, boolean, Null, Undefined)就直接反回去。
  52. }
  53.  
  54. //遍历目标数据
  55. for(let i in target){
  56. //获取遍历数据结构的每一项值。
  57. let value = target[i]; //i=1,i=2,i=..
  58. //判断目标结构里的每一值是否存在对象/数组
  59. if(checkedType(value) === 'Object' || checkedType(value) === 'Array'){ //如果对象OR数组里嵌套了对象/数组
  60. //继续遍历获取到的value值
  61. result[i] = clone(value); //这个只执行一次,数组里只有一个对象
  62. }else{ //获取到的value值是基本的数据类型或者是函数。
  63. result[i] = value; //因为arr3数组的下标0和1都是Number类型,只有下标2才是Object(转去执行1046行)
  64. }
  65. }
  66. return result;
  67. }
  68. let arr3 = [1,2,{username:'dudu',age:32}];
  69. let arr4 = clone(arr3); //相当于复制了一份arr3的基本数据
  70. console.log(arr4);
  71. arr4[2].username = 'gate';
  72. arr4[2].age = 65;
  73. console.log(arr3,arr4); //arr3下标2是{username:'dudu':age:32},arr4下标2是{username:gate,age:65}
  74. </script>

输出如下:

总结基本数据类型和引用数据类型区别

1、声明变量时内存分配不同

 *原始类型:在栈中,因为占据空间是固定的,可以将他们存在较小的内存中-栈中,这样便于迅速查询变量的值

 *引用类型:存在堆中,栈中存储的变量,只是用来查找堆中的引用地址。

    这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响

2、不同的内存分配带来不同的访问机制

在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问

而原始类型的值则是可以直接访问到的。

3、复制变量时的不同

1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

2)引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,

    也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。
    (这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针
4、参数传递的不同(把实参复制给形参的过程
首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。

  但是为什么涉及到原始类型与引用类型的值时仍然有区别呢?还不就是因为内存分配时的差别。  

  1)原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
  2)引用值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!
    因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象。

部分参考资料:JS基本数据类型和引用数据类型的区别

JS基本数据类型和引用数据类型的区别及深浅拷贝的更多相关文章

  1. JS基本数据类型和引用数据类型区别

    1.栈(stack)和堆(heap) stack为自动分配的内存空间,它由系统自动释放:而heap则是动态分配的内存,大小也不一定会自动释放 2.数据类型 JS分两种数据类型: 基本数据类型:Numb ...

  2. js中基本数据类型和引用数据类型的区别

    1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据类型指的是有多个值构成的对象. 当我们把变量赋值给一个变 ...

  3. js原始数据类型和引用数据类型=>callback数据传输原理

    摘要:js的数据类型有种划分方式为 原始数据类型和 引用数据类型. 原始数据类型 存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置.栈区包括了 变量的标识符和变量的值. ...

  4. Object 对象(对象的分类、属性(属性名和属性值)、基本数据类型与引用数据类型区别)

    Object——引用数据类型 基本数据类型的不足之处:基本数据类型是单一的值,不能表现出值与值之间的所属关系 object分为内建对象.宿主对象和自定义对象 a 内建对象:ES标准中定义的对象,在任何 ...

  5. JavaScript中基本数据类型和引用数据类型的区别(栈——堆)

    JavaScript中基本数据类型和引用数据类型的区别 1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据 ...

  6. js 原始数据类型、引用数据类型

    js的数据类型划分方式为 原始数据类型和 引用数据类型 栈: 原始数据类型(Undefined,Null,Boolean,Number.String) 堆: 引用数据类型(对象.数组.函数) 两种类型 ...

  7. JAVA-基本数据类型与引用数据类型区别

    package com.liu.u6.copy1; /* * 基本数据类型与引用数据类型有什么区别 */ public class Sjlx { public int age; } package c ...

  8. int与integer的区别(基本数据类型与引用数据类型)

    一.先说说int与integer的区别 int 是基本数据类型,默认值为0,不需要进行实例化 integer 是引用数据类型,是int的封装类型,默认值为null,创建该类型需要进行实例化. 基本数据 ...

  9. JavaScript中基本数据类型和引用数据类型的区别

    1.基本数据类型和引用数据类型 ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型. 基本数据类型指的是简单的数据段,引用数据类型指的是有多个值构成的对象. 当我们把变量赋值给一个变 ...

随机推荐

  1. 【Offer】[64] 【求1+2+...+n】

    题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 求1+2+...+n,要求不能使用乘除法.for. while if else. switch. case等关键字及条件判断语句( A? ...

  2. 第四篇 跟踪过程以及openvslam中的相关实现详解

    在成功初始化之后,会创建地图以及局部地图. 创建地图 在初始化正常过后,紧接着会创建地图 // src/openvslam/module/initializer.cc:67 // create new ...

  3. Sublime配置Python & sublime操作

    前言 前几天我发了一个配置C++的博客,今天再给大家掏一掏Python如何配置.但是主要是操作,文件并没有很多. 正文 文件地址:python 提取码:3gb7 先全部解压,sublime就按照上面说 ...

  4. Go语言获取系统性能数据gopsutil库

    psutil是一个跨平台进程和系统监控的Python库,而gopsutil是其Go语言版本的实现.本文介绍了它的基本使用. Go语言部署简单.性能好的特点非常适合做一些诸如采集系统信息和监控的服务,本 ...

  5. javascript合并两个数组

    在开发的过程中,我们很多时候会遇到需要将两个数组合并成一个数组的情况出现. var arr1 = [1, 2, 3]; var arr2 = [4, 5, 6]; // 将arr1和arr2合并成为[ ...

  6. 人工智能-智能创意平台架构成长之路(四)-丰富多彩的banner图生成解密第一部分(对标阿里鹿班的设计)

    我们之前讲了很多都是平台架构的主体设计,应用架构设计以及技术架构的设计,那么现在我们就来分享一下丰富多彩的banner图是怎么生成出来的. banner图的生成我们也是不断的进行迭代和优化,这块是最核 ...

  7. JSP中的两种跳转方式分别是什么,有什么区别?

    forward跳转:<jsp:forward page ="跳转页面地址"> response跳转:response.sendRedirect("跳转页面地址 ...

  8. Mysql - 高可用方案之MHA

    一.概述 本文将介绍mysql的MHA(Master High Availability)方案,官方文档地址:https://github.com/yoshinorim/mha4mysql-manag ...

  9. ACM卡常数(各种玄学优化)

    首先声明,本博文部分内容仅仅适用于ACM竞赛,并不适用于NOIP与OI竞赛,违规使用可能会遭竞赛处理,请慎重使用!遭遇任何情况都与本人无关哈=7= 我也不想搞得那么严肃的,但真的有些函数在NOIP与O ...

  10. fscanf函数的用法

    fscanf函数用法 简要介绍 fscanf()函数是格式化读写函数.它读取的对象是磁盘文件 函数原型: int fscanf(FILE * fp,char * format,...); 其中fp为文 ...