Skip to content

数据类型与变量

数据类型

JavaScript中有7种基本数据类型:

  • undefined
  • null
  • string
  • number
  • boolean
  • symbol(ES6)
  • bigint(ES6)

基本数据类型与引用类型的区别?

首先需要明确:基本数据类型不是对象!var a = 1 声明了一个值为1的数值类型的变量,这并不是一个数值对象。但是为什么你可以调用 a.toFixed(2) 呢?这是因为js引擎会自动对变量 a 进行装箱,相当于调用了 new Number(a).toFixed(2)

数据类型判断

除了这几种基本数据类型外,还存在大量的引用数据类型,对引用数据类型进行 typeof 运算的结果为 object

为什么 typeof null 的结果是 'object'

typeof null 的结果为 object 是一个特例,这是因为js中所有数据都可以表示为二进制,而二进制前3位为0的为对象类型,因为 null 的所有二进制位都是0,所以就被误判为对象类型。

因此,你也可以认为,这是JavaScript语言的一个bug。

数据类型转换

参见:深入类型转换

变量提升

一个函数作用域内,通过 var 声明的变量会被提升到函数作用域的顶部,如:

js
function func() {
  console.log(b)
  var b = 2
}

上述代码中,var b 会被提升到函数的顶部,相当于:

js
function func() {
  var b
  console.log(b)
  b = 2
}

需要注意的,只有声明会被提升,赋值并不会提升。

关于作用域

变量提升说明了一件事:

var 是没有块级作用域的,同时也说明,在ES6以前,没有块级作用域的概念。

其他的编程语言如C、Java等都是有块级作用域的,例如:

c
for (int i = 0; i < 10; i++) {
  int a = 1;
}

for 循环大括号内包裹的都是块级作用域,也就是说,每次循环进来时,声明的 int a = 1 都只针对于当前作用域,在外部是访问不到的。

但是在JavaScript中,if / for 等语句是没有块级作用域的,所以同样的代码会发生看起来很奇怪的结果:

js
for (var i = 0; i < 10; i++) {
}
console.log(i) // 10

因为没有块级作用域,所以 var 被提升了,代码其实变成了这样:

js
var i
for (i = 0; i < 10; i++) {
}
console.log(i)

重学变量提升 重点

我们都知道:作用域下的 var 声明会被提升到顶部。 可是,为什么会这样呢?

大家都说JavaScript是解释型的语言,给人一种错觉好像JavaScript并不像C、Java这些语言一样经历编译过程。但错觉仅仅是错觉,很难想象你写下的一行代码 var a = 1 不会经历词法分析、语法分析阶段,因为计算机是不能执行这些高级语言的代码的,这些阶段其实就共同构成了编译阶段。

所以,所谓解释型语言,无非是将编译与执行的过程合二为一而已。而变量提升,就是发生在编译阶段。

js
console.log(a)
var a = 1

上面的这段代码,在编译阶段会 找到所有的变量声明,并用合适的作用域将其关联起来。 而代码 var a = 1 其实包含了声明与赋值两个部分:

js
var a // 声明
a = 1 // 赋值

由于 var a 是编译阶段发生的,而 a = 1 发生在运行阶段。所以, var a 被提升到了作用域的顶部,变成了:

js
var a
console.log(a)
a = 1

因此,代码的执行结果会是 undefined ,这就是变量提升真正的内涵所在。

参考自 《你不知道的JavaScript 上卷》

变量提升 - 函数优先原则 重点

js中的声明分为变量声明与函数声明两种,提升时,总遵循 函数优先 的原则。

js
console.log(foo) // [Function: foo]

function foo() {
  console.log(1)
}

console.log(foo) // [Function: foo]

var foo = 1

console.log(foo) // 1

以上代码等同于:

js
// 编译阶段
function foo() {
  console.log(1)
}
var foo

// 执行阶段
console.log(foo)
console.log(foo)

foo = 1
console.log(foo)

编译阶段函数声明与变量声明同时提升到顶部,由于函数声明优先,所以前两次打印 foo 时,输出的是函数本身。

而后, foo = 1 这行代码对 foo 声明进行了重新赋值,因此输出为 1

暂时性死区

很多人有一个误区,认为只有 var 声明的变量有提升,而 let/const 不会,并且认为只有 let/const 存在暂时性死区,而 var 没有。

其实,这种理解是错误的。

正确的理解是,不管使用什么方式声明变量,都会发生提升,并且也都存在暂时性死区。

接下来,让我们一步一步来揭开暂时性死区的神秘面纱。

首先,我们先看暂时性死区的概念:

暂时性死区是指从代码区块的开始到变量初始化的区域,变量处于一种无法访问的死亡状态。

在这里,需要重点理解的概念是:什么是变量初始化?先看一段代码:

js
console.log(a)
var a = 1

众所周知,因为 var a = 1 会发生变量的声明提升,因此,这段代码在编译后等同于:

js
var a
console.log(a)
a = 1

因此这段代码打印的是 undefined ,但是,为什么呢?

当代码运行到使用变量的位置,如果发现该变量没有被初始化,就会抛出错误。在上述例子中,理论上 console.log(a) 是会报错的,因为变量 a 并没有被初始化。

这也引出一个非常重要的知识点:js引擎无法使用未经初始化的变量

但是,编译器在提升 var 声明的变量时,会默认初始化该变量为 undefined ,也就是说,编译器主动为开发者完成了初始化的工作。

js
var a = undefined
console.log(a)
a = 1

这也就是为什么打印了 undefined 的原因了。但是,为什么使用 let/const 就会报错呢?

使用 let/const 声明的变量与 var 一样会被提升,唯一的区别是编译器不会为其初始化 undefined ,而是将初始化的时机放在了用户第一次为变量赋值的时候。

js
let a
console.log(a)
a = 1

上述代码中,从 let a 开始到 a = 1 的这段区域,就称为 暂时性死区

而为什么 var 声明的变量没有暂时性死区这一说,是因为当变量声明提升时,同时初始化了变量,这样导致暂时性死区一开始就结束了。

来源:https://www.freecodecamp.org/news/javascript-temporal-dead-zone-and-hoisting-explained/

关于中文社区都说 let/const 不存在声明提升的看法:

中文社区中,很多文章都说只有 var 才有变量提升,而 let/const 不存在变量提升,个人觉得这种说法是错误的。从设计的角度出发,var/let/const 都存在声明提升与暂时性死区,但是在 let/const 提升时不予以初始化这种方案,明显要比 let/const 区分对待来的简单得多。

声明提升

总结

  • 要想彻底弄懂变量提升,需先弄懂js的编译与执行原理。