基本数据类型和引用数据类型有哪些区别_JS数据类型之基本类型和引用类型的区别...

每篇文章纯属个人经验观点,如有错误疏漏欢迎指正

  ECMAScript 变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值指的是
简单的数据段,而引用类型值指那些可能由多个值构成的对象。

  在将一个值赋给变量时,解析器必须确定这个值是基本类型值还是引用类型值,因为两种数据类型不仅存放的位置不同,访问的方式也不同。

一、区分数据类型

  • 基本数据类型(简单数据类型):String,Number,Boolean,Undefined,Null,Symbol

  • 引用数据类型(复杂数据类型):Object

  引用数据类型只有一种 Object ,但 Object 是一个包罗万象的类型,像 Array,Function 等等不在其它六种简单数据类型之外的数据,都属于 Object

二、栈和堆

  我们简单理解一下栈和堆的区别:

  • 栈:存放基本类型数据,系统会自动分配内存空间,由系统自动释放,占据固定大小的空间;

  • 堆:存放引用类型数据,系统会动态分配内存空间,系统不会自动释放,且占据的空间大小不定;

  在 JS 中,声明一个引用数据类型时,会在堆内存中保存一个对象,在栈中保存变量名和一个指针(地址),这个指针(地址)指向的是堆内存中对应的对象。

  指针(地址)指的是堆内存中的引用地址,通过这个引用地址可以快速查找到保存中堆内存中的对象。而由于 JS 不可以对堆内存进行直接访问和操作,又延伸出两种访问方式:

  • 按值访问:对于基本类型,可以直接操作保存在栈中的实际值;

  • 按引用访问:对于引用类型,通过栈中的引用地址连接到堆中对象,操作的是堆中的对象而不是栈中的地址。

三、基本数据类型(简单数据类型)

  基本数据类型均为简单数据,这些原始类型的简单数据在内存中占据的空间是固定的,因此会被保存在栈中,这样存储便于迅速查寻变量的值,可以直接访问。

var a = 1;var b = a;b = 2;console.log(a);    // 1console.log(b);    // 2

  在声明一个基本数据类型变量时,会直接在栈中的相应位置上创建一个值;如果从一个变量向另一个变量复制基本类型的值时,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。这表明两个变量的值是相互独立的,因此,这两个变量可以参与任何操作而不会相互影响。

  下图演示了上述代码在栈内存中的演化过程:

4b0db1cb1e3b5694e1a0a676099c28c2.png

  因为基本数据类型都是简单数据段,它们的数据大小是固定的,所以会直接按值存放,并可以直接按值访问,而按值访问就代表着我们可以操作保存在变量中的实际的值。

四、引用数据类型(复杂数据类型)

   引用数据类型都是 Object,其中包含了 Array,Function,Date 等,也就是说引用类型的内容实际上就是存放在堆内存中的对象,变量保存的是栈内存中一个指向堆内存中对应内容地址的指针。

   这是因为引用类型的数据大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。而引用地址的大小是固定的,所以把它存储在栈中对性能没有负面影响。

var obj1 = {a: 1};var obj2 = obj1;obj2.a = 2;console.log(obj1);    // {a: 2}console.log(obj2);    // {a: 2}

  我们发现修改 arr2 后,arr1 发生了改变。这是因为这两个引用数据类型指向了同一个堆内存对象。obj1 复制给 obj2,实际上是将 obj1 这个堆内存对象在栈内存的引用地址复制了一份给了 obj2 ,而他们的引用地址共同指向同一个堆内存对象,所以修改 obj2 会导致 obj1 也发生改变。

   也就是说,引用类型在保存时,会在堆内存中保存这个对象,在栈内存中保存变量名和一个指向这个对象的引用地址。在进行复制操作时,复制的是栈内的引用地址,而不是堆内的对象,这将导致两个变量实际上将指向同一个对象。因此,改变其中一个变量,就会影响另一个变量。

  下图演示了上述代码在内存中的演化过程:

b509c3d4cbb1ccb5a88c3a0074842fc0.png

  简单来说,引用类型的数据在复制时,复制的是栈中的引用地址,而非堆中储存的对象。而两者复制的引用地址指向的是同一个对象,这就导致了无论修改元数据还是复制后的数据,都会给二者造成同样的影响。

  我们再来尝试一下,将复杂数据中的简单数据提取出来:

var arr1 = [1, 2, 3, 4, 5];var arr2 = arr1;var num1 = arr2[0];arr2[0] = 0;console.log(arr1);    // [0, 2, 3, 4, 5]console.log(arr2);    // [0, 2, 3, 4, 5]console.log(num1);    // 1

  我们修改 arr2 后,arr1 跟着发生了改变,但 num1 并没有发生变化。这是因为 arr2[0] 是一个基本数据类型,num1 复制的是 arr2 堆中对象里的一个基本数据类型,会将这个简单值直接复制到栈内,此时,无论对堆内对象进行任何更改,都不会影响到栈内的值。

24afcc4c30351f0d34869c119c1bf587.png

五、按值传递和按引用传递

  • 按值传递:在调用函数时将参数的值复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数;

  • 按引用传递:在调用函数时将参数的引用地址传递到函数中,操作的其实是堆内存中的对象,修改会影响到实际参数;

  按值传递示例:

var num = 1;function fn(n){  n++;  console.log(n)}fn(num);        // 2console.log(num);    // 1

  我们在看一个例子:

var obj1 = { a: 1 };function fn(obj) {  obj.b = 2;  console.log(obj);}fn(obj1);        // { a: 1, b: 2 };console.log(obj1);    // { a: 1, b: 2 };

  这里需要注意 JS 中没有按引用传递,所有的参数都是按值传递,但即使这个变量是按值传递的,引用数据类型的参数也会按引用来访问同一个对象。

  我们在上方示例中创建一个对象 obj1 并将其复制后的内容传递到函数 fn 中,然后在函数中为其添加了一个新属性 b ,在这个函数内部,obj 和 obj1 引用的是同一个对象,于是,当在函数内部为 obj 添加属性后,函数外部的 obj1 也发生了改变,因为 obj1 指向的对象在堆内存中只有一个,而且是全局对象。

  有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,我们再看一看下面这个经过修改的例子:

var obj1 = { a: 1 };function fn(obj) {  obj.b = 2;  obj = {    c: 3  };  console.log(obj);}fn(obj1)        // { c: 3 }console.log(obj1);    // { a: 1, b: 2 }

  这个示例中,在把 obj1 传递给 fn 后,函数为参数 obj 添加了一个新属性 b ,然后,又将一个全新的对象赋给变量 obj,同时赋予了它一个新属性 c 。

  如果 obj1 是按引用传递的,那么 obj1 就会指向这个新创建的包含属性 c 的对象。但是,当接下来再打印 obj1 时,显示的值仍然是具有 a,b 属性的对象,这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。

  实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁。

六、总结

  JavaScript 变量可以用来保存两种类型的值:基本数据类型和引用数据类型,除了 Object 为引用数据类型外,其它数据均为基本数据类型。

基本数据类型:

  • 基本类型是储存在栈中的简单数据段,占据固定大小的空间,系统会自动释放;

  • 基本类型的值可以直接访问到,也就是按值访问;

  • 基本类型的变量在复制时,会将原变量的值复制给新变量,但两个变量是完全独立的,只是值相同;

  • 基本类型作为参数时,会将原始值复制一份作为参数传递,参数和原始值互不影响,也就是按值传递;

  • 区分是否为基本类型数据可以使用 typeof 运算符来判断是 String,Number 等;

引用数据类型:

  • 引用类型是储存在堆中的对象,栈内存储的是指向堆中对象的指针,内存空间的大小由系统分配,系统不会自动释放;

  • 引用类型的值需要根据栈中存储的路径去堆中查找相应的对象,也就是按引用访问;

  • 引用类型的变量复制时,会将原变量的指针复制给新变量,两个变量指向的是堆中的同一个对象,修改时会互相影响;

  • 引用类型作为参数时,会将引用路径复制一份作为参数传递,因此两个变量最终都指向同一个对象,但这是按值传递而非按引用传递;

  • 区分引用数据的类型可以使用 instanceof 操作符来判断是 Array,Function 等;


感谢大家的观看及支持,我们下篇博客再见! 如果有问题需要老王帮忙或者想看关于某个主题的文章,也可以通过留言等方式来联系老王。
每篇文章纯属个人经验观点,如有错误疏漏欢迎指正。 转载请附带作者信息及出处。您的评论和关注是我更新的动力!
点击查看原文跳转到 博客            点下再看,少个BUG c8a2a0cf6f8294b8d7c2436af1fc93b9.png
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值