对于js编译基础的理解(持续更新)
对于JavaScript的学习:js是一种发展了数十年的语言,学习js的路程感觉并不像是模块化的知识学习,层层嵌套,需要反复学习理解。
毒汤:走弯路是必然的,没有谁可以通过捷径走向成功
前言
现在要探讨的东西有点基础,但是越基础的东西往往越容易让我们理解一些复杂的程序的运行…
提示:以下是本篇文章正文内容,下面案例可供参考
一、首先得理解作用域
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
1. 编译的过程
程序中的代码执行一般分为三个步骤:分词/词法分析、解析/语法分析以及代码生成。
-
分词/词法分析
例如var a = 2;
,这个过程是对字符串的分解:var
、a
、=
、2
、;
(空格是否会被当做词法单元,取决于空格是否有意义),这些代码块被称为词法单元(token)。
词法分析这些词法单元的主要差异在于这些 token 的状态,比如a
为一个有状态的词法单元,调用有状态的解析规则,则这个过程就是词法分析。 -
解析/语法分析
这个过程是将词法单元流转换成了一个由元素逐级嵌套所组成的代表程序语法结构的树。
这个树被称为抽象语法树(Abstract Syntax Tree,AST)。 -
代码生成
将AST转换为可执行代码的过程称为代码生成。
比如var a = 2
,所执行的代码为:创建一个叫做a
的变量,并将2
这个值存储在a
中。
2. 理解作用域
- 2.1 引擎
负责整个JavaScript程序的编译及执行过程。 - 2.2 编译器
负责语法分析以及代码生成。 - 2.3 作用域
负责收集并维护所有声明的标识符(变量)组成的一系列查询,确定当前执行的代码对这些标识符的访问权限。
举个例子:
console.log(a);
var a = 2;
因为变量提升,相当一部分人会认为结果为 2
,实际上是 undefined
。
借此来理解一下作用域:
首先引擎会扫描代码,因为变量提升,第一步先执行 var a
,这时 a
并没有被赋值,此时执行 console.log(a);
打印的结果是 undefined
。然后才会进行 a = 2
赋值操作。
实际的执行过程等同于:
var a;
console.log(a); // undefined
a = 2;
局部变量与全局变量
- 局部变量:
1、在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)
2、局部变量只能在函数内部使用,在局部作用域中可以访问到全局变量。
3、在函数内部 var 声明的变量就是局部变量;
4、函数的形参实际上就是局部变量; - 全局变量
1、在全局作用域下声明的变量叫做 全局变量(在函数外部定义的变量)
2、全局变量在全局(代码的任何位置)下都可以使用;全局作用域中无法访问到局部作用域中的变量。
3、全局变量第一种创建方式:在全局作用域下 var声明的变量是全局变量
4、全局变量第二种创建方式:如果在函数内部,没有使用 var关键字声明直接赋值的变量也属于全局变量。(不建议使用)
局部作用域与全局作用域
- 局部作用域
1、在函数内部就是局部作用域,这个代码的名字只在函数的内部起作用
2、调用函数时创建函数作用域,函数执行完毕之后,函数作用域销毁;
3、每调用一次函数就会创建一个新的函数作用域,它们之间是相互独立的。 - 全局作用域
1、直接编写在<script>..</script>
标签之中的JS代码;
2、编写在一个单独的 JS 文件中的;
3、全局作用域在页面打开时创建,页面关闭时销毁;
4、在全局作用域中有一个全局对象 window(代表的是一个浏览器的窗口,由浏览器创建),可以直接使用。
函数作用域与块级作用域
- 函数作用域
序号 | 条件及情况 (具体示例可以参考: 链接) |
---|---|
1 | 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁 |
2 | 每调用一次,函数就会创建一个新的函数作用域,它们之间是互相独立的 |
3 | 在函数作用域中,可以访问到全局作用域的变量 在全局作用域中,无法访问到函数作用域的变量 |
4 | 当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用 |
5 | 如果没有则向上一级作用域中寻找(作用域链),直到找到全局作用域 |
6 | 如果全局作用域中依然没有找到,则会报错 ReferenceError |
7 | 在函数作用域也有声明提前的特性: 使用var关键字声明的变量,会在函数中所有的代码执行之前被声明 函数声明也会在函数中所有的代码执行之前执行 |
8 | 在函数中,不使用var声明的变量都会成为全局变量 |
9 | 定义形参就相当于在函数作用域中声明了变量 |
- 块作用域
1、什么是块作用域?参考小黄书中的例子:
// 循环体
for (var i = 0; i<10; i++) {
console.log(i);
}
// try/catch 语句
try {
undefined(); // 执行一个非法操作来制造一个异常
}
catch (err) {
console.log(err); // 能够正常执行
}
console.log(err); // ReferenceError: err not found
2、在ES6中引入的 let 关键字,用来在任意代码块中声明变量。比如:
if (..) {
let a = 2;
} // 这里会声明一个劫持了 if 的{..} 块的变量,并且将这个变量添加到这个块中。
3、上面的情况属于 let 为其声明的变量隐式地劫持了所在的作用域块,同样还有显式的块(在声明中任意位置使用 { .. } 来为 let 绑定块):
if (true) {
{ // 显式的块
let a = 2;
console.log(a); //2
}
}
console.log(a); // Uncaught ReferenceError: a is not defined
/*
在这个例子中,在if内部显式地创建一个块,如需对其进行重构,
整个块都可以方便地移动而不会对 if 声明的位置和语义产生任何影响。
*/
4、块作用域一般指变量和函数不仅可以属于所处的作用域,也可以属于某个代码块(通常指 { .. } 内部)
- 函数不是唯一作用域单元,还有块作用域
{ .. }
。 - 有些人认为块作用域不应该完全作为函数作用域的替代方案。两种功能应该同时存在,开发者可以并且应该更具需要选择使用何种作用域,创造可读、可维护的优良代码。 – 《你不知道的JavaScript》
总结
理解编译的过程,有助于后面在学习函数、闭包、原型等复杂概念的时候,大致可以明白其中的运行过程。
我喜欢读别人写的文章,不仅仅是因为文章的价值。更重要的是读取不同人对于同一种技术的理解。在与多种思想交流以后,如果能够产生自己的思维火花,这样会让我受益良多。
然后我会试着写出自己的理解,一家之言,并非权威,只希望其中的一些个人见解能对读者有所帮助!当然也欢迎讨论!