JS 语句
JS 语句有哪些特别之处,首先我们要看一个不太常见的例子, 向你介绍 语句执行机制涉及的一种基础类型:
Completion 类型
Completion Record 表示一个语句执行完之后的结果,它有三个字段:
[type] 表示完成的类型,有 break continue return throw 和 normal 几种类型;
[value] 表示语句的返回值,如果语句没有,则是 empty;
[target] 表示语句的目标,通常是一个 JavaScript 标签
JS正是依靠语句的 Completion Record 类型,方才可以在语句的复杂嵌套结构中,实现各种控制
1、普通语句执行后,会得到 [type] 为 normal 的 Completion Record,JS引擎遇到这样的 Completion Record,会继续执行下一条语句。
2、普通语句中,仅表达式语句执行后,会产生 [value]
用浏览器控制台执行代码,可以看到:声明语句返回undefined,表达式语句返回value
语句块
语句块就是拿大括号括起来的一组语句,它是一种语句的复合结构,可以嵌套
语句块本身并不复杂,先来看个普通语句的语句块
{
var i = 1; // normal
i ++; // normal
console.log(i) //normal
} // normal 这个语句块也是normal
我们看到,在一个 block 中,如果每一个语句都是 normal 类型,那么它会顺次执行。接下来我们加入 return 试试看。
{
var i = 1; // normal
return i; // return
i ++;
console.log(i)
} // return 这个语句块是return,非 normal
这个结构就保证了非 normal 的完成类型可以穿透复杂的语句嵌套结构,产生控制效果。
控制型语句
控制类语句分成两部分
一类是对其内部造成影响,如 if、switch、while/for、try
另一类是对外部造成影响,如 break、continue、return、throw
这两类语句的配合,会产生控制代码执行顺序和执行逻辑的效果,这也是我们编程的主要工作
带标签的语句
任何 JavaScript 语句是可以加标签的,在语句前加冒号即可,如:加了abcd标签
大部分时候,这个东西类似于注释,没有任何用处。唯一有作用的时候是:与完成记录类型中的 target 相配合,用于跳出多层循环。
outer: while(true) {
inner: while(true) {
break outer;
}
}
// break/continue 语句如果后跟了关键字,会产生带 target 的完成记录
// 一旦完成记录带了 target,那么只有拥有对应 label 的循环语句会消费它
JS语法
先说下源文件,JS 有两种源文件,一种叫做脚本,一种叫做模块。
脚本是可以由浏览器或者 node 环境引入执行的,而模块只能由 JavaScript 代码用 import 引入执行。
从概念上,可认为脚本具有主动性的 JavaScript 代码段,是控制宿主完成一定任务的代码;
而模块是被动性的 JavaScript 代码段,是等待被调用的库。
区别仅仅在于是否包含 import 和 export。
import 声明有两种用法
一个是直接 import 一个模块
另一个是带 from 的 import,它能引入模块里的一些信息
import "mod"; //引入一个模块
import v from "mod"; //把模块默认的导出值放入变量v
直接 import 一个模块,只是保证了这个模块代码被执行,引用它的模块无法获得它的任何信息
带 from 的 import 意思是引入模块中的一部分信息,可以把它们变成本地的变量
函数体
执行函数的行为通常是在 JavaScript 代码执行时,注册宿主环境的某些事件触发的,而执行的过程,就是执行函数体(函数的花括号中间的部分)。
我们先看一个例子,感性地理解一下:
setTimeout(function(){
console.log("go go go");
}, 10000)
这段代码通过 setTimeout 函数注册了一个函数给宿主,当一定时间之后,宿主就会执行这个函数。宿主会为这样的函数创建宏任务。
可以认为,宏任务中可能会执行的代码包括“脚本 (script)”、“模块(module)”和“函数体(function body)”
预处理
JavaScript 执行前,会对脚本、模块和函数体中的语句进行预处理。预处理过程将会提前处理 var、函数声明、class、const 和 let 这些语句,以确定其中变量的意义。
1、var 声明**
永远作用于脚本、模块和函数体这个级别,在预处理阶段,不关心赋值的部分,只管在当前作用域声明这个变量。
var a = 1
function foo() {
console.log(a) //undefind
var a = 2
}
foo()
这段代码声明了一个脚本级别的 a,又声明了 foo 函数体级别的 a
但是预处理过程在执行之前,所以有函数体级的变量 a,就不会去访问外层作用域中的变量 a 了,而函数体级的变量 a 此时还没有赋值,所以是 undefined
接着看这段代码:
var a = 1;
function foo() {
console.log(a);
if(false) {
var a = 2;
}
}
foo();
这段代码比上一段代码 多了一段 if,我们知道 if(false) 中的代码永远不会被执行,但是预处理阶段并不管这个,var 的作用能够穿透一切语句结构,它只认脚本、模块和函数体三种语法结构。所以这里结果跟前一段代码完全一样,我们会得到 undefined。
2、function 声明**
在全局(脚本、模块和函数体),function 声明表现跟 var 相似,不同之处在于,function 声明不但在作用域中加入变量,还会给它赋值。
看这段代码
console.log(foo) //[Function: foo]
function foo() {}
在foo声明之前,打印函数 foo,可以发现,已经是函数 foo 的值了
接着看
console.log(foo) //undefined
if (true) {
function foo() {}
}
这段代码得到 undefined,这说明 function 在预处理阶段仍然发生了作用,在作用域中产生了变量,没有产生赋值,赋值行为发生在了执行阶段。所以没报错,但值为未定义
3、class 声明
class 声明在全局的行为跟 function 和 var 都不一样,在 class 声明之前使用 class 名,会抛错:
console.log(c);
class c{
}
看上去很像是 class 没有预处理,但是实际上并非如此
class 的声明作用不会穿透 if 等语句结构,所以只有写在全局环境才会有声明作用,这样的 class 设计比 function 和 var 更符合直觉,而且在遇到一些比较奇怪的用法时,倾向于抛出错误。按照现代语言设计的评价标准,及早抛错是好事
网友评论