JavaScript高级程序设计第四版学习记录-第三章3.1-3.3 语法 关键字保留字 声明变量

第三章 语言基础

ECMA-262 第 5 版(ES5)定义的 ECMAScript,是目前为止实现得最为广泛(即受浏览器支持最好)的一个版本。第 6 版(ES6)在浏览器中的实现(即受支持)程度次之。到 2017 年底,大多数主流浏览器几乎或全部实现了这一版的规范。为此,本章接下来的内容主要基于 ECMAScript 第六版 ES6。

3.1 语法

3.1.1 区分大小写

ECMAScript中一切都区分大小写,包括 变量、函数名、操作符等。

3.1.2 标识符

标识符即为 变量、函数、属性或函数参数等 的名称。

ECMAScript 标识符使用驼峰大小写形式,即第一个单词的首字母小写,后面每个单词的首字母大写。

标识符要求以字母、数字、下划线_、美元符号$ 构成且必须以字母、下划线_、美元符号$ 开头。

关键字、保留字、truefalsenull 不能作为标识符。

3.1.3 注释

// 单行注释
/*
多行注释
*/

3.1.4 严格模式

ES5增加了严格模式(strict mode)的概念。

严格模式是一种不同的JavaScript解析和执行模型,ES3的一些不规范写法在严格模式下会被处理,对于不安全的活动抛出错误。

严格模式会影响JavaScript执行的很多方面,所有现代浏览器都支持严格模式。

  • 对整个脚本启用严格模式在脚本开头加一行预处理指令,任何支持的JavaScript引擎看到这行预处理指令都会切换到严格模式。
    采取这种语法形式以避免破坏ES3的语法
    "use strict";
    
  • 单独指定一个函数在严格模式下执行,把预处理指令加到函数体开头
    function doSomething() { 
     "use strict"; 
     // 函数体 
    }
    

3.1.5 语句

ECMAScript 中的语句以分号结尾。省略分号意味着由解析器确定语句在哪里结尾。

let sum = a + b // 没有分号也有效,但不推荐
let diff = a - b; // 加分号有效,推荐

即使语句末尾的分号不是必需的,也应该加上。
多条语句可以合并到代码块中。代码块由一个左花括号({)标识开始,一个右花括号(})标识结束:

if (test) { 
 console.log(test); 
}

3.2 关键字与保留字

关键字有特殊用途,不能用作标识符或属性名。
ES6规定的所有关键字如下:

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

规范中描述了一组未来的保留字,同样不能用作标识符或属性名,虽然保留字在语言中没有特定用途,但保留给将来做关键字用。
ES6为将来保留的所有词汇:
始终保留:

enum

严格模式下保留:

implements	package		public 
interface	protected	static 
let 		private

模块代码中保留

await

3.3 变量声明

ECMAScript 变量是松散类型的,变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符
有 3 个关键字可以声明变量:varconstlet。其中,var 在ECMAScript 的所有版本中都可以使用,而 constlet 只能在 ECMAScript 6 及更晚的版本中使用。
在这里插入图片描述

3.3.1 var 关键字

总结
函数级作用域
作用域提升
重复声明不报错
全局声明的变量成为windows对象的属性

  1. 定义变量: var 关键字 + 变量名(标识符)
    ES 实现变量名初始化,可以同时定义变量并设置其值。

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

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

    message 变量是在函数内部使用 var 定义的,调用 test() 会创建这个变量并给message赋值。调用之后变量随即被销毁。
    (2)在函数内定义变量时省略 var 操作符,可以创建一个全局变量,但不推荐,很难维护。

    function test() { 
     message = "hi"; // 全局变量
    } 
    test(); 
    console.log(message); // "hi"
    
  3. var 声明提升
    (1)使用 var 关键字声明的变量会自动提升到函数作用域顶部

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

    上面不报错, 是因为上面等价于, 把var 声明提升到作用域顶部(var age , 只声明, 赋值操作还是在后面, 所以打印默认值undefined)

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

    (2)反复多次使用 var 声明同一个变量也没有问题

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

3.3.2 let 声明 (重点关注与var的区别)

总结
块级作用域
不能重复声明
不能提升, 有 暂时性死区 TDZ
混用var和let 重复声明也会报错
全局声明不是windows对象的属性

  1. let 声明的范围是块作用域 即{}内部,而 var 声明的范围是函数作用域
    块作用域是函数作用域的子集,因此适用于var的作用域 > 适用于let的作用域。
    在这里插入图片描述
    在这里插入图片描述

  2. let 不允许在同一个块作用域内重复声明, var允许.
    let 的重复声明会报错 SyntaxError
    在这里插入图片描述
    对声明冗余报错不会因混用 let 和 var 而受影响。这两个关键字声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在。

    var name; 
    let name; // SyntaxError 
    let age; 
    var age; // SyntaxError
    
  3. let的暂时性死区
    let 声明的变量不会在作用域中被提升, 而使用 var 关键字声明的变量会自动提升到函数作用域顶部(详见3.3.1 var).

    let声明之前的执行瞬间, 被称为"暂时性死区"(temporal dead zone), 在此阶段引用后面才声明的变量报错ReferenceError

    在解析代码时, javascript引擎会注意到块后面的 let 声明, 但在let声明之前不能以任何形式引用未声明的变量.
    在这里插入图片描述

  4. 全局声明 ( var √, let ×)
    var 声明的 变量, 会成为 window 对象的属性, let 不会.

    但 let 声明的变量仍然是在全局作用域中发生, 相应的变量会在页面的生命周期内存续, 因此为避免SyntaxError, 必须确保页面内不会重复声明同一个变量.
    在这里插入图片描述

  5. 条件声明 (let ×)
    在使用 var 声明变量时,由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明。因为 let 的作用域是块,所以不可能检查前面是否已经使用 let 声明过同名变量,同时也就不可能在没有声明的情况下声明它。
    因此, 对于 let 这个新的 ES6 声明关键字,不能依赖条件声明模式

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

    <script> 
     var name = 'Nicholas'; 
     let age = 26; 
    </script> 
    <script> 
     // 假设脚本不确定页面中是否已经声明了同名变量
     // 那它可以假设还没有声明过
     var name = 'Matt'; 
     // 这里没问题,因为可以被作为一个提升声明来处理
     // 不需要检查之前是否声明过同名变量
     let age = 36; 
     // 如果 age 之前声明过,这里会报错
    </script> 
    

    在这里插入图片描述

    使用 try/catch 语句或 typeof 操作符也不能解决,因为条件块中 let 声明的作用域仅限于该块。

    <script> 
     let name = 'Nicholas'; 
     let age = 36; 
    </script> 
    <script> 
     // 假设脚本不确定页面中是否已经声明了同名变量
     // 那它可以假设还没有声明过
     if (typeof name === 'undefined') { 
     let name; 
     } 
     // name 被限制在 if {} 块的作用域内
     // 因此这个赋值形同全局赋值
     name = 'Matt'; 
     try { 
     console.log(age); // 如果 age 没有声明过,则会报错
     } 
     catch(error) { 
     let age;
     } 
     // age 被限制在 catch {}块的作用域内
     // 因此这个赋值形同全局赋值
     age = 26; 
    </script>
    
  6. for 循环中的let 声明
    let 出现之前,for 循环定义的迭代变量会渗透到循环体外部:
    在这里插入图片描述
    改成使用 let 之后,这个问题就消失了,因为迭代变量的作用域仅限于 for 循环块内部
    在这里插入图片描述
    在使用 var 的时候,最常见的问题就是对迭代变量的奇特声明和修改.
    在这里插入图片描述

    在退出循环时, 迭代变量保存的是导致循环结束的值 5, 在之后执行setTimeout逻辑时, 所有的 i 都是一个变量, 输出同一个最终数值.

    关于setTimeout方法, 参见链接 setTimeout方法
    setTimeout返回一个表示计数器编号的整数值,将该整数传入clearTimeout函数,就可以取消对应的定时器。
    var id1 = setTimeout(f,1000);
    setTimeoutl返回的整数值是连续的(一定环境下,比如浏览器控制台,或者js执行环境等),也就是说,第二个setTimeout方法返回的整数值,将比第一个的整数值大1。

    setTimeout指定的代码,必须等到本次执行的所有代码都执行完,才会执行。 在这里插入图片描述

    在使用 let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量, 每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
    在这里插入图片描述
    let 声明, 每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-infor-of循环.

    另, 正常for循环无超时逻辑时, 正常执行
    在这里插入图片描述

3.3.3 const

总结:
声明同时必须初始化
初始化后不可修改
不能修改仅限于变量的引用,
不能用于声明会递增的迭代变量

const 的行为与 let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且初始化后不可修改, 尝试修改 const 声明的变量会导致运行时错误。

const age = 26; 
age = 36; // TypeError: 给常量赋值
// const 也不允许重复声明
const name = 'Matt'; 
const name = 'Nicholas'; // SyntaxError 
// const 声明的作用域也是块
const name = 'Matt'; 
if (true) { 
 const name = 'Nicholas'; 
} 
console.log(name); // Matt

const 声明的限制只适用于它指向的变量的引用, 换句话说,如果 const 变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const 的限制。

const person = {}; 
person.name = 'Matt'; // ok

JavaScript 引擎会为 for 循环中的 let 声明分别创建独立的变量实例,虽然 const 变量跟 let 变量很相似,但是不能用 const 来声明迭代变量(因为迭代变量会自增)

for (const i = 0; i < 10; ++i) {} // TypeError:给常量赋值

不过,如果你只想用 const 声明一个不会被修改的 for 循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对 for-of 和 for-in 循环特别有意义:

let i = 0; 
for (const j = 7; i < 5; ++i) { 
 console.log(j); 
} 
// 7, 7, 7, 7, 7 
for (const key in {a: 1, b: 2}) { 
 console.log(key); 
} 
// a, b 
for (const value of [1,2,3,4,5]) { 
 console.log(value); 
} 
// 1, 2, 3, 4, 5

3.3.4 声明风格及最佳实践

  1. 不使用var
    let 和 const 声明变量, 变量有明确的作用域/声明位置/不变的值
  2. const 优先, let 次之
    使用 const 声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用 const 来声明变量,只在提前知道未来会有修改时,再使用 let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值