第七颗皮蛋
新手
新手
  • 最后登录2019-06-14
  • 发帖数7
阅读:9056回复:2

[javascript]JS基本数据类型与引用数据类型的区别(使用赋值运算符需要注意的小'bug')

楼主#
更多 发布于:2019-02-18 21:21




/****************废话较多,如需节约时间,请直接看文末结论****************/

var a = 123;


这句代码非常好理解,即声明了一个名为 a 的变量,a 的值为 123。


那何为变量呢?通俗的理解可以将变量看作一个容器,变量的值即为容器中存放的内容。上面的代码即可以理解为一个名叫 a 的容器,容器中放了 123 这个数据。


上面的解释非常象形和生活化,我们还可以用一种更符合计算机原理的方式来解释变量,那就是把变量理解为电脑中的内存,声明一个变量即在电脑的内存中开辟了一块空间,给变量赋值即将值存储在该空间中。


这样的话,上面的代码就可以理解为在电脑的内存中开辟了一个名为 a 的空间,这个空间中存放了 123 这个数据。


以上两种理解看上去似乎是一模一样的,那为什么还需要第二种理解方式呢?这就涉及到了JS不同数据类型的特点。


JS有两种大的数据类型,分别是基本数据类型和引用数据类型。
基本数据类型分别有:number、string、boolean、undefined、null。
引用数据类型分别有:object、array、function(三种都属于广义的object类型)。


执行一下代码:
    var a = 1;
    var b;
    b=a;
    b++;
    console.log(a);
可以打印到的结果是 1 。


上面的代码很好解释,我们在内存中分别开辟了一个名为 a 的空间和一块名为 b 的空间,并且在 a 空间中存放了 1 这个数据,在 b 空间中没有存放任何数据(此时b=null)。
b=a,即将 a 空间中的值赋予给 b 空间,因此 b 空间中也存放了 1 这个数据。
b++,即将 b 空间中的 1 进行了 ++ 运算,因此 b 空间中的值变为了 2 。
此时再打印 a 的值,当然为 1 。


上面完全是一段废话,大家不用考虑就能够得出代码的结果。
执行代码二:
var a = [1,2,3];
var b;
b=a;
b.push(4);
console.log(a);
此时打印的结果是 [1,2,3] 吗?


大家用浏览器执行一下,会发现结果变成了 [1,2,3,4] 。
按照之前的分析,打印的结果应该为 [1,2,3] ,可是为什么变成了 [1,2,3,4] 呢?


这就与数据类型有关了。
当 a=1 时, a 的数据类型为基本数据类型 number。
当 b=[1,2,3] 时,b 的数据类型为引用数据类型 array。
当我们把值为基本数据类型的 a 赋给变量 b 时,计算机执行的操作是将 a 空间中存储的值拷贝了一份给 b 空间,并存储在其中。
而当我们把值为引用数据类型的 a 赋给变量 b 时,计算机执行的操作并不是将 a 的值拷贝一份给 b ,而是将 a 空间在内存中的地址赋给了 b 。
这用什么区别呢?当我们再访问 b 时,访问到的其实是 a 在内存中的地址,该地址再自动将我们引导到 a 空间,也就是我们访问到了 a 空间中存储的值,即 a 的值。也就是 b 并没有拷贝一个和 a 相同的数据存储在自己的空间中,而是 b 获得了一个指向 a 的指针。
此时执行 b.push(4) ,因为 b 其实是一个指向 a 的指针,所以最终是在 a 空间中push了 4 这个数据。当我们打印 a 的值时便得到了 [1,2,3,4] 。当然此时 b 的值仍然指向 a ,同样是 [1,2,3,4]。


这就是基本数据类型和引用数据类型在使用赋值运算符时需要注意的区别。
但是为什么JS在处理这两种数据时要设置不同的方式呢?
我们知道基本数据类型是单一的数据不能再分解了,而引用数据类型是复合数据,是由其他基本数据甚至复合数据组成的。也就是说一个变量的值为引用数据类型时,其数据可能是非常庞大的。因此我们将一个引用数据类型的变量赋值给另一个变量,如果全部将其中的数据拷贝过去,需要消耗的内存和运算量可能非常的大,这并利于js的运作,而将其在内存中的地址赋值出去,就变得非常简单和高效了。


那我们如何复制一个引用数据类型的变量呢?
同样拿数组来举例:
var a = [1,2,3];
var b;
b=[ ];
for(var i=0; i<a.length; i++){
  b.push(a[ i ]);
}
这种方法比较笨拙,还有其他方法望告知。


/*****************废话结束*******************/


结论:
把一个值为基本类型的变量赋给另一个变量时,是将整个值拷贝给另一个变量;把一个值为引用类型的变量赋给另一个变量时,是将原变量在内存中存储地址的指针赋给另一个变量,因此对后来的变量进行操作会影响原变量的值。


以上为个人理解,若有错误之处,望指出,希望和大家一起进步。

最新喜欢:

doubleyongdouble...
doubleyong
管理员
管理员
  • 最后登录2026-05-25
  • 发帖数1198
  • 最爱沙发
  • 喜欢达人
  • 原创写手
  • 社区居民
  • 忠实会员
沙发#
发布于:2019-02-20 12:55
第七颗皮蛋:数组深拷贝的其他方法:

    arr.slice(start,end)

.slice()  方法用于提取数组中的元素,返回一个新数组,原数组不会改变。start , end 两个参数分别为需要提取的起始索引和结束索引。

var a ...
回到原帖
666
知识需要管理,知识需要分享
第七颗皮蛋
新手
新手
  • 最后登录2019-06-14
  • 发帖数7
板凳#
发布于:2019-02-19 00:57
数组深拷贝的其他方法:

    arr.slice(start,end)

.slice()  方法用于提取数组中的元素,返回一个新数组,原数组不会改变。start , end 两个参数分别为需要提取的起始索引和结束索引。

var a = [1,2,3];
var b;
b = a.slice(0);//第二个参数省略时表示提取到最后。

    arr.concat()

.concat()方法是将原数组与传入的参数数据进行连接,返回一个新数组,并且不改变原数组。

var a = [1,2,3];
var b;
b = a.concat();//不传入参数,即和空数据进行连接,返回原数组本身。

**注意点:上面两种方法可以拷贝一个数组中的值,但是原数组中若包含有引用类型的子数据,拷贝过去的对应子数据仍然是指向原数组子数据的指针。
例如:
var a = [[1,2],3];
var b;
b = a.slice(0);//或者b = a.concat();
b[0].push(4);
b.push(5);
console.log(a);
console.log(b);

上面代码打印的值为[ [1,2,4],3],和[ [1,2,4],3,5]。

因为原数组 a 的第一个值(a[0])是一个数组,所以 b 虽然拷贝了 a 的数据,但拷贝的数据中为引用类型的数据仍然是拷贝的指针,其他基本类型的数据则是完整的拷贝了过来。

同理,经过测试,纠正一个原文章的BUG,利用循环遍历原数组的方法拷贝一个数组,若原数组中存在引用类型的数据时,该子数据仍然只会拷贝一个指针。
游客


返回顶部

公众号

公众号