「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
一直计划从头开始过一遍JavaScript,从数据类型,到原型链。然后ES6一直到ES2021。好好过一遍看看自己知道的以及不知道的。
既然是第一篇,那就从变量的定义开始吧。
如何定义一个变量呢?
在最初的js中我们都是使用 var来进行变量的定义,例如:
// 定义单个变量
var a
var str = ''
var num = 0
var bool = true
var obj = {}
var undef = undefinded
//同时定义多个变量
var str='',num=1
//当多个变量有相同的初始值时
var a = b =c = 'init'
console.log(a) // init
console.log(b) // init
console.log(c) // init
在使用 var进行数据定义时,常见的一个问题就是,var会有变量提升的问题,例如:
console.log(str) //undefinded
var str ='string'
这个时候我们会打印出来 undefined,此时是因为在浏览器解析中,会按照如下的方式来运行代码:
var str
console.log(str)
str = 'string'
因为在js中,如果我们定义了一个变量,但是没有给他赋值时,它的默认值就会是 undefined。
这里我们也可以看一下《你不知道的JavaScript》中,在作用域篇章中,解释数据定义时发生了什么。
当你看见 var a = 2; 这段程序时,很可能认为这是一句声明。但我们的新朋友引擎却不这么看。事实上,引擎认为这里有两个完全不同的声明,一个由编译器在编译时处理,另一个则由引擎在运行时处理。
下面我们将 var a = 2; 分解,看看引擎和它的朋友们是如何协同工作的。编译器首先会将这段程序分解成词法单元,然后将词法单元解析成一个树结构。但是当编译器开始进行代码生成时,它对这段程序的处理方式会和预期的有所不同。可以合理地假设编译器所产生的代码能够用下面的伪代码进行概括:“为一个变量分配内存,将其命名为 a,后将值 2 保存进这个变量。”然而,这并不完全正确。
事实上编译器会进行如下处理。
- 遇到 var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。
- 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量(查看 1.3节)。 如果引擎最终找到了 a 变量,就会将 2 赋值给它。否则引擎就会举手示意并抛出一个异常!
通过看上面这段文章,就大概能理解到为什么js会产生变量提升的问题。
说完 var之后,就该唠唠ES6出来的let和const了。
这俩玩意的使用方式跟var是类似的,就不重复说明了。但是只得关注的点是,const声明的变量是一个常量,可以理解为一直不会变的量。所以当我们有如下代码时就会报错:
> const example = '这是一个例子'
undefined
> example = '常识修改const'
Uncaught TypeError: Assignment to constant variable.
我们可以看到系统给了我们一个TypeError的报错。翻译过来就是我们给一个常量进行赋值了。所以const我们通常用来定义一些不会发生变化的数据。而且通常都会全部都用大写来给变量命名:const ONE_HOUR = 60 * 60 * 1000。
let和const的到来帮我们解决了变量提升的问题。现在当我们使用这两种方式进行变量的定义时,就不会跟上面var一样,打印出来 undefined,而是会直接报错来告知我们:
> console.log(str);let str = "string"
Uncaught ReferenceError: Cannot access 'str' before initialization
> console.log(str);const str = 'const'
Uncaught ReferenceError: Cannot access 'str' before initialization
ReferenceError是系统告诉我们的一个引用错误,意思是变量 str 在初始化定义之前是不能被使用的。产生这个报错的原因就是 暂时死区(temporal dead zone)。它使得在我们使用 let和const时,会像其他的编程语言一样,变量在未声明前是不允许使用的。
它俩的到来,使得我们减少了运行时错误,并且防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。