【前端进阶】JS中基本数据类型有哪几种?null 是对象吗?基本数据和复杂数据类型有什么区别?

背景:由高级前端工程师@刘小夕 在github上发起的一个开源项目:每个工作日发布一个前端相关的问题,每周进行一次汇总。我觉得很不错,也参与其中,如果你也感兴趣,可以一起参与,项目地址:https://github.com/YvetteLau/Step-By-Step

以下是我关于这道题目的回答,如果有不正确的地方,非常欢迎各位能指出,感激不尽~

基本数据类型

基本数据类型有7种

  • null
  • undefined
  • string
  • number
  • boolean
  • symbol(ES6新增)
  • BigInt(ES10新增)

关于BigInt,可以阅读以下链接的文章来学习:

https://www.baidu.com/link?url=wZ6XIQ-G-TqRbzy-FBlxI61DEwLZaKrxIuImGqegFlTqO4pGGV4rSQckn9joKSy8XREP8ppCl-6CA2DAqHoxdYQr17DBaGKc17hPqw0NlGaHi-l2K4Xh0wGEcphr14-itIl7AH9s9vT1pha1emV8t_&wd=&eqid=a73ebe45000069d5000000065ce57aac

https://zhuanlan.zhihu.com/p/36330307

复杂数据类型

复杂数据类型就一种

  • object

null是对象吗?

先看下MDN上的解释:

null 特指对象的值未设置。它是 JavaScript 基本类型 之一。

由此可见,null并不是一个对象,虽然typeof null返回的值为"object"

《你不知道的JavaScript(上卷)》这本书说过:

null 有时会被当做一种对象类型,但是这其实只是语言本身的一个bug,即对null执行typeof null时会返回字符串"object"。实际上,null本身是基本类型。 原理是这样的,不同的对象在底层都表示为二进制,在JavaScript中二进制前三位都为0的话会被判断为object类型,null的二进制表示是全0,自然前三位也是0,所以执行typeof时会返回“object”。

思考:为什么typeof null会返回"object"而不是"null"

大家可以看下MDN上的解释:

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了”object”。 ECMAScript提出了一个修复(通过opt-in),但被拒绝。这将导致typeof null === ‘null’。

感兴趣的,也可以看下这篇文章:http://www.cnblogs.com/xiaoheimiaoer/p/4572558.html

基本数据和复杂数据类型有什么区别?

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

基本数据类型:存储在栈中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为它们占据的空间是固定的,所以可将它们存储在较小的内存区域-栈。这样存储便于迅速查寻变量的值。

复杂数据类型:存储在堆中的对象,栈中存储的变量的值是一个指针,指向堆中的引用地址。这是因为复杂数据类型的值是会改变的,所以不能把它放在栈中,否则会降低变量查寻的速度。

2.不同的访问机制

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

而基本数据类型的值是可以直接访问得到的,即按值访问

3.复制变量时的不同

基本数据类型:在将保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,不会相互影响,只是它们拥有相同的value而已。

看下面的例子:

var a = 10;
var b = a;
b = 20;
console.log(a); // 10

上面的代码说明:b获取的是a值的一份拷贝,虽然两个变量的值相等,但是两个变量保存了两个不同的基本数据类型,它们之间不会相互影响。

复杂数据类型:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说两个变量都指向了堆内存中d的同一个对象,它们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象的指针罢了)

看下面的例子:

var a = {
    name: 'dazhi'
}

var b = a;
b.name = 'dazhi_fe';

console.log(a.name); // "dazhi_fe"

上面的代码说明:a和b都指向了同一个堆中的对象,所以对其中一个作出改变,另一个也会跟着改变。

4.参数传递的不同

首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。这也是我们容易疑惑的地方,因为访问变量有按值和按引用两种方式,而参数只能按值传递。这一点等下我们举例来说明。

基本数据类型:拷贝的是值

看例子:

function addTen(num) {
    num += 10;
    return num;
}
var count = 20;
var result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30

只是把变量的值传递给参数,之后参数和这个变量互不影响。

复杂数据类型:拷贝的是引用地址

看例子:

function setName(obj) {
    obj.name = "Nicholas";
}
var person = {
    name: 'jack'
}
setName(person);
console.log(person.name); // "Nicholas"

以上代码创建了一个对象,并将其保存在了变量person中。然后这个变量被传递到setName()函数之中就被复制给了obj。在这个函数内部,ojb和person引用的是同一个对象。于是在函数内部修改了name属性后,函数外部的person也会有所反映。所以我们会错误的认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。

为了证明对象是按值传递的,我们再来看一个例子:

var obj1 = {
    value: '111'
}

var obj2 = {
    value: '222'
}

function changeStuff(obj) {
    obj.value = '333';
    obj = obj2;
    return obj.value;
}

var foo = changeStuff(obj1);

console.log(foo); // 222 参数obj指向了新的对象obj2
console.log(obj1.value); // 333
obj1仍然指向原来的对象,之所以value改变了,是因为changeStuff里的第一条语句,这个时候obj是指向obj1的;如果是按引用传递的话,这个时候obj1.value应该是等于’222’的。

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

参考:

  1. https://www.zhihu.com/question/27114726

  2. 《JavaScript高级程序设计(第3版)》第4章的内容