JavaScript高级程序设计4th第三章总结
一、语法
1. ECMAScript区分大小写
2. 标识符
变量、函数、属性或函数参数的名称:
- 第一个字符必须是一个字母、下划线(_)或美元符号($);
- 剩下的其他字符可以是字母、下划线(_)、美元符号或数字。
最佳实践:ECMAScript标识符推荐使用驼峰大小写形式: 1 myCar maxOfNums
注意:关键字、保留字、true、false 和 null 不能作为标识符。
3. 注释
1 // 单行注释
2
3 /*
4 * 多行注释
5 */
4. 严格模式
ECMAScript增加了严格模式的概念。严格模式是一种不同的JavaScript解析和执行模型,ECMAScript3一些不规范写法在该模式下会被处理,对于不安全的的行为会抛出错误。
1 // 启用严格模式
2 "use strict"
5. 语句
①ECMAScript语句以分号结尾。省略分号意味着又解析器确定语句在哪里结尾。为了避免输入内容不完整或者在某些情况下提高性能,推荐加上分号。
②对if语句而言,最佳实践是始终在控制语句中使用代码块,可以让内容更加清晰,修改代码时也可以避免出错:
1 // 有效,容易导致错误,应该避免
2 if (test)
3 console.log(test);
4 // 推荐
5 if (test) { console.log(test);
6 }
二、关键字与保留字
break | case | catch | class | const | continue |
do | else | export | extends | finally | for |
in | instanceof | new | return | super | switch |
typeof | var | void | while | with | yield |
debugger | default | function | if | import | this |
throw | delete | try |
enum |
implements | interface | package |
public | let | protected |
static | private |
await |
注意:关键字和保留字不能作为标识符
三、变量
ECMAScript变量是松散类型的,即变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。
声明变量的关键字: var:ECMAScript所有版本;let const:ECMAScript 6 及之后版本
1. var关键字
var message;
// message变量可以保存任何类型的值;
// 在不初始化的情况下,变量message保存undefined。
var report = "hi";
// report被定义为一个保存字符串值的变量,report并不会被标识为字符串类型;
// report可以改变保存的值,或改变值的类型(有效,但不推荐)。
① var 声明作用域
使用var操作符定义的变量会成为包含它的函数的局部变量。
1 function test() {
2 var message = "hi"; //局部变量
3 }
4 test();
5 console.log(message); //error
/*
* 在函数test()内定义的变量message,在执行完test()时(函数退出时)被销毁
*/
9
10 function test() {
11 message = "hi"; //全局变量
12 }
13 test();
14 console.log(message); //error
/*
* 省略var,则message被定义为全局变量,变量可以在函数外部被访问,不推荐
* 在局部作用域中定义的全局变量很难维护,也容易造成局部/全局变量判断困惑。严格模式下,会抛出ReferenceError
*/
② var 声明提升
提升(hoist),即把所有变量声明都拉到函数作用域的顶部。(反复多次使用var声明同一个变量也没有问题)
1 function foo() {
2 console.log(age);
3 var age = 26;
4 }
5 foo(); // undefined
6 // 等同于以下代码
7 function foo() {
8 var age;
9 console.log(age);
10 age = 26;
11 }
12 foo(); // undefined
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
}
console.log(age); // ReferenceError: age 没有定义
// 块作用域是函数作用域的子集
区别之二是let不允许同一个块作用域中出现冗余声明。
注意:对声明冗余报错不会因混用let和var而受影响,如下
var name;
let name; // SyntaxError
let age;
var age; // SyntaxError
①暂时性死区
区别之三是let声明的变量不会在作用域中被提升。
解析代码时,JavaScript引擎虽然会注意到块后的let的声明,但是此时不能以任何方式引用未声明的变量,否则将抛出ReferenceError。在let声明之前的执行瞬间称为“暂时性死区”。
// name 会被提升
console.log(name); // undefined
var name = 'Matt';
// age 不会被提升
console.log(age); // ReferenceError: age 没有定义
let age = 26;
②全局声明
与var不同,使用let在全局作用域中声明的变量不会成为window对象的属性。但let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。重复声明会导致SyntaxError。
var name = 'Matt';
console.log(window.name); // Matt
let age = 26;
console.log(window.age); // undefined
③条件声明
在使用var声明变量时,由于hoist,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。而let得作用域时块,故不能检查前面是否已经使用let声明过同名变量,即只能在没有声明过该变量的情况下声明它。
这里的条件声明具体指的是:1.可以条件声明:使用var时,不必在意变量是否被声明过,直接假设为没有被声明过来处理(重新var声明)。2.不能条件声明:使用let时,不能假设没有被声明过因为此时let没有hoist。
④for循环中的let声明
使用var在for循环中声明迭代变量,for循环定义的迭代变量会渗透到循环体外部;而let则不会,此时迭代变量的作用域仅限于for循环块内部。
对迭代变量的奇特声明和修改(使用setTimeout超时查看):
for(var i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出 5、5、5、5、5
for(let i = 0; i < 5; ++i) {
setTimeout(() => console.log(i), 0)
}
// 输出 0、1、2、3、4
使用var,在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑是,所有的i都是一个变量,故输出的都是同一个最终值。
而使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例。
3. const声明
- const的行为与let基本相同,唯一一个重要区别是「用const声明变量是必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误」。
- const声明的限制只适用于它指向的变量的引用。
- 与let不同,即使JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,而且const和let很相似,但也不能用const来声明迭代变量(迭代变量会自增)。
- 用const声明一个不会被修改的for循环变量是可以的。即每次迭代只是创建一个新变量。这对for-of和for-in循环特别有意义,参考下图。
// 1.声明的变量不能被改变
for (const i = 0; i < 10; ++i) {} //TypeError
// 2. 可以声明不被修改的for循环变量
let i = 0;
for (const j =7; i < 5; ++i) {
console.log(j);
}
// 7,7,7,7,7
// 3. const在for-in中的应用
for (const key in {a: 1, b: 2}) {
console.log(key);
}
// a, b
// 4. const在for-in中的应用
for (const key of [1,2,3,4,5]) {
console.log(key);
}
// 1,2,3,4,5
- 对为什么const可以在for-in和for-of中使用,而不能在for循环使用的理解:
- let
for (let i = 0; i < 5; i++) {
console.log(i);
}
// 0,1,2,3,4
// 相当于
let i = 0;
while(i < 5) {
console.log(i);
i++;
}
// 0,1,2,3,4
// for循环只初始化一次变量,每一次循环都是在同个块级作用域中进行
- const for-in
for (const key in {a: 1, b: 2}) {
console.log(key);
}
// a,b
// 遍历的是对象的属性值,相当于
var a = {a: 1, b: 2};
var b = Object.keys(a).entries();
while(true)
{
const c = b.next();
if(c.done) break;
const key = c.value[1];
console.log(key);
}
// a,b
// 即,for-in循环会为每一次循环都会产生一个块级作用域去完成该变量的行为
- const for-of
for (const key of [1,2,3,4,5]) {
console.log(key);
}
// 1,2,3,4,5
// 相当于
var a = [1,2,3,4,5];
var b = a.entries();
while(true) {
const c = b.next();
if (c.done) break;
const key = c.value[1];
console.log(key);
}
// 与for-in一样,for-of是严格的迭代语句,每次循环都会产生一个块级作用域,故const不会重复赋值
4. 声明风格及最佳实践
①不使用var
②const优先,let次之
四、数据类型
ECMAScript的类型系统是松散的,它有6种数据类型(原始类型):Undefined、Null、Boolean、Number、String和Symbol(符号,是ES6新增的),1种复杂数据类型:Object(无序名值对的集合)。和其他高级语言如C++不一样,ECMAScript中不能定义自己的数据类型,即没有XX类型的变量之分。
1.typeof操作符
- 为什么要有typeof?
ECMAScript松散的类型系统
- typeof操作符可能返回的字符串值有:“undefined”(表示值未定义)、“boolean”(值为布尔值)、“string”(值为字符串)、“number”(值为数值)、“object”(表示值为对象(非函数)或null)、“function”(值为函数)、symbol(值为符号)。
- 【注意】
typeof ("hello")
正确吗?
typeof是操作符,而非函数,故不需要参数,但可以使用参数。
- 为什么typeof null返回的是“object”?
特殊值null被认为是一个空对象的引用。
- 为什么typeof操作符的返回值会有“function”?
函数在ECMAScript中被认为是对象,并不是一种数据类型。但是由于函数有自己特殊的属性,因此有必要通过typeof来区分函数和其他对象。
2. Undefined 类型
- 什么情况下,变量的值是Undefined类型的?Undefined类型有哪些值?
当使用var或者let声明了变量却没有初始化时,该变量的值是undefined类型的;Undefined类型只有一个值:undefined。
- 包含undefined值的变量和未定义变量是有区别的:
let message;
// let age; 没有被声明过
console.log(message); // "undefined"
console.log(age); // 报错,Uncaught ReferenceError: age is not defined
- typeof与undefined的奇怪之处:对未声明的变量使用typeof操作符和调用delete是不会报错的(其中,在严格模式下这样使用delete会报错)。
typeof age
// "undefined"
let message; typeof message;
// "undefined"