ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

《JavaScript高级程序设计》(第4版)阅读笔记(五)

2021-01-13 23:32:21  阅读:24  来源: 互联网

标签:变量 作用域 JavaScript 笔记 关键字 let var 程序设计 声明


继续分享《高程四》第三章的内容。

3.2 关键字与保留字
 

ECMA-262描述了一组保留的关键字,这些关键字有特殊用途,比如表示控制语句的开始和结束,或者执行特定的操作。按照规定,保留的关键字不能用作标识符或属性名。ECMA-262第6版规定的所有关键字如下:
 

break do in typeof
case else instanceof var
catch export new void
class extends return while
const finally super with
continue for switch yield
debugger function this
default if throw
delete import try

 规范中也描述了一组未来的保留字,同样不能用作标识符或属性名。虽然保留字在语言中没有特定用途,但它们是保留给将来做关键字用的

以下是ECMA-262第6版为将来保留的所有词汇。
 

始终保留:
enum
严格模式下保留:
implements package public
interface protected static
let private
模块代码中保留:
await

保留字不能用作标识符,但现在还可以用作对象的属性名。一般来说,最好还是不要使用关键字和保留字作为标识符和属性名,以确保兼容过去和未来的ECMAScript版本。

3.3 变量

ECMAScript变量是松散类型的(也称弱类型、动态类型),意思是变量可以用于保存任何类型的数据(在《你不知道的JavaScript》(中)介绍过,JavaScript中的“变量”是没有类型的,“值”才是有类型的)每个变量只不过是一个用于保存任意值的命名占位符

有3个关键字可以声明变量: var 、 const 和 let 。其中, var 在ECMAScript的所有版本中都可以使用,而 const 和 let 只能在ECMAScript 6及更晚的版本中使用。

 

3.3.1 var 关键字
 

要定义变量,可以使用 var 操作符(注意 var 是一个关键字),后跟变量名(就是标识符),如:

var message;

不初始化的情况下,变量会保存一个特殊值undefined

声明变量的时候,也可以用“=”对它初始化(就是定义变量的同时也设置它的值),如:

var message = "hi";

变量声明后可以被多次赋值,由于JavaScript是弱类型语言,所以可以给变量赋与之前类型不同的值。这样虽然合法,但不推荐。


1. var 声明作用域

使用var关键字声明的变量作用域只有两种:“函数” 或者 “全局”。

假如你是在函数体的内部使用var声明变量,那么这就是函数内的局部变量,函数执行完毕后就被销毁,在函数外调用它会报错(变量未被定义)。

假如你是在函数体外使用var声明变量,那么这是全局变量。在任何地方都可以使用。

假如局部变量和全局变量同名了,那么遵循就近原则,能使用局部变量就不使用全局变量。

另外有一个点需要注意,假如声明变量的时候省略了var关键字,那么即使是在函数内部,声明的也是全局变量(这是隐式全局变量,可以被delete关键字删除)

虽然可以通过省略 var 操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略 var 是不是有意而为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError

 我就见过这种套路的面试题,比如给出类似下面的代码:

var a = b = 10;

注意这里的a是正常声明的全局变量,b是一个暗戳戳的隐式全局变量,它们都被赋值为10。

如果需要定义多个变量,可以在一条语句中用逗号分隔每个变量(及可选的初始化):

var message = "hi",
found = false,
age = 29;

上面是定义了3个变量,由于JavaScript是弱类型语言,所以可以这样一起定义多个用不同类型初始化的变量。插入换行和空格缩进并不是必需的,但这样有利于阅读理解

在严格模式下,不能定义名为 eval 和 arguments 的变量,否则会导致语法错误。(因为eval和arguments都有特殊含义,eval是一个函数,可以立即执行传入的JavaScript语句,arguments表示函数中的实参数据)
 

2.声明提升

 

使用var声明的变量在预编译的阶段有声明提升,就是会将所有变量的声明提升到当前作用域的顶部(注意是当前作用域,函数里的局部变量是提升到函数的顶部,全局作用域是提升到全局的顶部,如果页面上有多个<script>,全局变量的声明也不会提升到上一个<script>)声明提升仅仅只是提升了声明,并没有提升初始化的操作,也就是说,从第一行开始就可以使用这些变量了,但只有变量初始化之后的代码才可以拿到它的值。

此外,反复多次使用 var 声明同一个变量也没有问题。
 

3.3.2 let 声明
 

let 跟 var 的作用差不多,但有着非常重要的区别。最明显的区别是, let 声明的范围是块作用域,而 var 声明的范围是函数作用域

if (true) {
var name = 'Matt';
console.log(name); // Matt
}console.log(name); // Matt
if (true) {
let age = 26;
console.log(age); // 26
}c
onsole.log(age); // ReferenceError: age没有
定义

(name是var声明的变量,并没有块级作用域,所以在代码块外面也可以正常使用,而age是let声明的变量,在代码块以外就无法使用了)
块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let 。

let 也不允许同一个块作用域中出现冗余声明——多次声明同一个变量,不管是先var再let,还是先let再var,都是冗余声明。这样会导致报错。(当然,在不同的作用域中声明了同名变量,是可以的,注意,不是同一层代码块就不是同一个作用域了)

 

1. 暂时性死区


let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升

// name会被提升
console.log(name); // undefined
var name = 'Matt';
// age不会被提升
console.log(age); // ReferenceError:age没有定义 
let age = 26;

在解析代码时,JavaScript引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量在let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead
zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError 。

2. 全局声明


与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性( var 声明的变量则会)。

不过, let 声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。
 

3. 条件声明


在使用 var 声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。(也就是说,想用if来判断,条件成立后再来用var声明其实是判断了个寂寞,因为声明语句只要写了,就会被提前,就会先声明好)

因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的情况下声明它(也就是说,因为let声明的变量作用域是代码块,所以在判断之前声明的变量实际上与判断后声明的变量并不在同一个作用域,所以这样判断也是判断了个寂寞)使用 try / catch 语句或 typeof 操作符也不能解决,因为条件块中 let 声明的作用域仅限于该块。

为此,对于 let 这个新的ES6声明关键字,不能依赖条件声明模式

注意  不能使用 let 进行条件式声明是件好事,因为条件声明是一种反模式,它让程序变得更难理解。如果你发现自己在使用这个模式,那一定有更好的替代方式。

4. for 循环中的 let 声明

在 let 出现之前, for 循环定义的迭代变量会渗透到循环体外部:

for (var i = 0; i < 5; ++i) {
// 循环逻辑
}
console.log(i); // 5

因为var声明的变量没有块级作用域,所以i是全局变量,出了代码块还是有效的,所以会输出5。

改成使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部:

for (let i = 0; i < 5; ++i) {
// 循环逻辑
}
console.log(i); // ReferenceError: i没有定义

现在,i在块外面不可用。

在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改:(也是面试题的一个老考点了)

for (var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 你可能以为会输出0、1、2、3、4
// 实际上会输出5、5、5、5、5

之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有的 i 都是同一个变量,因而输出的都是同一个最终值。

(我的理解是,每次循环,都会开启一个定时器,定时器在0毫秒后会到时,但是里面的回调函数是异步代码,所以要等所有同步代码执行完毕才轮得到它们。等同步代码执行完毕后,就会有5个这样的定时器,并且i从0变成了5。这个时候再来输出i,结果就是5了。因为i是全局变量,所以它们输出的是相同的值。)

而在使用 let 声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的
值,也就是循环执行过程中每个迭代变量的值。

for (let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 会输出0、1、2、3、4

(我的理解是,这看起来只有一个花括号,只有一个代码块。但是每次循环,都是运行在一个新的代码块里的,每次用的i都是只属于这个代码块里的i,所以最后要执行回调函数,输出这些i的时候,输出的是每个花括号自己的i的值,所以输出的各不相同)
这种每次迭代声明一个独立变量实例的行为适用于所有风格的for 循环,包括 for-in 和 for-of 循环。
 


 

标签:变量,作用域,JavaScript,笔记,关键字,let,var,程序设计,声明
来源: https://blog.csdn.net/sfyjknvcx/article/details/112439340

专注分享技术,共同学习,共同进步。侵权联系[admin#icode9.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有