JavaScript高级程序设计学习(2)

本文介绍了ECMAScript中变量声明的规则,重点讲解了var、let和const的差异,包括大小写敏感性、标识符规范、作用域、提升机制、块级作用域以及const的初始化要求。提倡使用let和const提升代码质量和可维护性,尤其是const的优先原则。
摘要由CSDN通过智能技术生成

三,语言基础

3.1 语法

3.1.1 区分大小写

首先要知道的是,ECMAScript 中一切都区分大小写。无论是变量、函数名还是操作符,都区分大小写。换句话说,变量 test 和变量 Test 是两个不同的变量。

3.1.2 标识符

可以理解成变量、函数、属性或函数参数的名称。标识符可以由一或多个下列字符组成:

  • 第一个字符必须是一个字母、下划线(_)或美元符号($);
  • 剩下的其他字符可以是字母、下划线、美元符号或数字。

按照惯例,ECMAScript 标识符使用驼峰大小写形式

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

规范中也描述了一组未来的保留字,同样不能用作标识符或属性名。虽然保留字在语言中没有特定用途,但它们是保留给将来做关键字用的(关键字储备)。
始终保留:
enum
严格模式下保留:
implements package public
interface protected static
let private

模块代码中保留:
await

3.3 变量

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

3.3.1 var 关键字

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

var message;

这行代码定义了一个名为 message 的变量,可以用它保存任何类型的值。不初始化的情况下,变量会保存一个特殊值 undefined 。当然也可以同时定义变量并设置它的值

var message = "hi";

1. var 声明作用域
使用 var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

function test() {
var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错!

有一个小知识点,就是在函数内部定义变量的时候省略var操作符,可以创建一个全局变量,当然这里只是记录这个知识点,本人不会去这么写代码,之前在某个源码内见过这种写法。
2. var 声明提升
使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部:

function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined

之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码:

function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined

所谓的提升,也就是把所有变量声明都拉到函数作用域的顶部。在后面会详细讨论到变量提升问题。

3.3.2 let 声明

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

if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age 没有定义

let 也不允许同一个块作用域中出现冗余声明。这样会导致报错,和const类似。
JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,而这是因为同一个块中没有重复声明,只要在同一个块中没有冗余的声名,就没问题。
有一个知识点,对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。

var name;
let name; // SyntaxError

let age;
var age; // SyntaxError

我们有个先入为主的概念,就是var声明可以冗余声明,但这里有个问题就是一旦var和let声明混用,那么一定不可以冗余声明。

1. 暂时性死区
let 与 var 的另一个重要的区别,就是 let 声明的变量不会在作用域中被提升。在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此
阶段引用任何后面才声明的变量都会抛出 ReferenceError。从编码的角度来说,的确这样更遵循编码的逻辑,我们最好要对声明的变量有一个明确的认知和准备,而不是直接通过关键字声明在那里。
2. 全局声明
与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声明的变量则会)。
3. 条件声明
这里光看名字有点抽象,就是判断是否之前声明过,来进行后面的操作,比方说我var声明了一个变量,我可以通过if语句,或者try,catch语句来判断该变量的值是否为undefined,如果是的话,那么就是声明一个,但使用let声明这是不可能的,因为任何条件判断,他都有块,而let声明有块级作用域,所以let是无法进行条件声明的。
4. for 循环中的 let 声明
这是一个经典的问题,在 let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:

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

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

for (let i = 0; i < 5; ++i) {
// 循环逻辑
}
console.log(i); // ReferenceError: 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 都是同一个变量,因而输出的都是同一个最终值。而在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。

这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-in 和 for-of循环。
这里面引申出一个问题,这个问题在书内是没有提及的,是有关for循环,块级作用域,let,var冗余声明的问题。

    for (let i = 0; i < 3; i++) {
        let i = 1
    }

思考一下,上述的代码会不会报错。
答案是不会报错,因为for循环括号内声明的i其实是一个父级作用域,而循环体内的i是子级作用域,两个i并不在同一个作用域内,根据上面的知识点,不在同一个作用域内,是可以通过let冗余声明的。一般的误区是大家会认为循环条件内的i和循环体内的i都属于同一块级作用域,这是不对的。
再来看一下下面的一段代码

    for (let i = 0; i < 3; i++) {
        var i = 1
    }

这段代码是否会报错呢?
答案是会,这个地方也困绕了我很久,因为我们都知道var声明是没有块级作用域的,所以这里存在变量提升,var声明的i应当属于全局作用域。这里需要理解一下上面说的暂时性死区的问题。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。这样是不是恍然大悟了,在循环条件内的i由于是let声明,所以形成了块级作用域,又因为条件死区的原因,他绑定了块级作用域,又因为在子作用域内用var声明了i没有块级作用域,所以此时循环体内var声明的i和条件语句内用let声明的i在同一个作用域,在同一个作用域内,let和var不可以冗余声明,所以出现了报错的问题!(由于网上相关的这部分的解答资料相对较少,属于本人思考理解,如有错误,请见谅)。

3.3.3 const声明

const 的行为与 let 基本相同(块级作用域,暂时死区,无变量提升),唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。

3.3.4 声明风格及最佳实践

1. 不使用 var
有了 let 和 const,大多数开发者会发现自己不再需要 var 了。限制自己只使用 let 和 const
有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。
2. const 优先,let 次之
使用 const 声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用 const 来声明变量,只在提前知道未来会有修改时,再使用 let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值