上篇文章我们简单介绍了js的基本编译步骤,大致上分为这五步:
- 词法分析(Lexical Analysis) :这是将源代码分割成一个个的单词或称为标记(tokens)的过程。这一步通常由解析器(Parser)完成,解析器会按照词法规则,从左到右、从上到下扫描源代码,将其切割成一个个的标记。
- 语法分析(Syntax Analysis) :在这个阶段,解析器会将这些标记按照语法规则重新组合成语法树(Syntax Tree)或抽象语法树(Abstract Syntax Tree,AST)。语法树是源代码的抽象表示,它记录了源代码的结构和语义信息。
- 语义分析(Semantic Analysis) :在这个阶段,编译器会对语法树进行一系列的检查,以确保它是符合JavaScript语法的,并且所有的变量和函数在使用前都已经声明或定义。
- 抽象语法树优化:优化器会对语法树进行优化,以提高代码执行效率。这可能包括消除冗余的变量,合并重复的代码,或者优化特定的代码结构。
- 代码生成(Code Generation) :在生成阶段,编译器会将优化后的语法树转换为JavaScript字节码(Bytecode)或机器代码。在浏览器环境中,这个步骤通常生成的是JavaScript字节码,然后由JavaScript引擎(如V8,SpiderMonkey等)解释执行。
但是我们知道js是一门脚本解释型语言
- (编译型语言:在程序运行之前,源代码会被转换成机器代码 如二进制代码,然后直接由计算机硬件执行这些代码。例如,C、C++和Java等语言属于编译型语言。解释型语言:程序源代码在运行时被解释器逐行读取并转换为可执行指令。解释型语言如Python、JavaScript和Ruby等,需要解释器来执行代码。)
这些步骤并不是严格按照顺序执行的,并且可能会因为具体的编译器或解释器的实现而有所不同。例如,一些现代的JavaScript引擎(如V8)会执行即时编译(JIT),即在运行时对JavaScript代码进行编译优化。
我们由一串代码来引出今天的故事
很简单的一串代码,大家仔细想想js在编译的时候内存中发生了什么,我们先来引入几个概念--调用栈和执行栈与作用域链
调用栈(Call Stack) :
调用栈是用于存储函数调用的上下文信息的数据结构。在JavaScript中,每当一个函数被调用时,它的执行上下文会被推入调用栈。函数执行完毕后,它的执行上下文会从调用栈中弹出。调用栈遵循后进先出(LIFO)原则,这意味着最后一个被调用的函数会首先执行完毕并返回。
调用栈的主要作用是确保JavaScript代码按照正确的顺序执行,并且在函数之间正确地传递控制流。当函数执行完毕并返回时,它的执行上下文会从调用栈中弹出,控制权会返回到调用它的函数,继续执行其后的代码。
执行栈(Execution Stack) :
执行栈是调用栈的一种特殊形式,用于存储正在执行的函数或方法。在JavaScript中,执行栈与调用栈非常相似,都是用于存储函数调用的上下文信息。
作用域链(Scope Chain) :
作用域链是JavaScript中用于确定变量和函数访问权限的一种机制。在JavaScript中,每个函数都有一个与之关联的作用域链,这个作用域链由一系列的变量对象组成。作用域链的顶部是函数内部的变量对象,然后是外部函数的变量对象,依此类推,直到全局变量对象。
在函数内部,可以通过作用域链中的变量对象来访问变量的值。作用域链的作用是确定变量的访问权限和来源。在JavaScript中,变量的访问是通过作用域链从内到外进行搜索的,直到找到变量或到达作用域链的顶部(全局变量对象)。
此时的add作用域嵌套在全局作用域中所以add的outer指针指向全局执行上下文的词法环境,这就是作用域嵌套的原理,其实是outer指针指向父级的执行上下文
当add函数执行完毕后js引擎会自动把调用栈销毁避免栈溢出
看到这里我们来看看一串新代码,引入闭包的概念-闭包(Closure)--是JavaScript中的一个重要概念,它指的是一个函数有权访问另一个函数的作用域和变量的能力。
在JavaScript中,每个函数都有其自己的作用域。当函数在一个函数内部被定义时,内部的函数可以访问外部函数的变量,即使外部函数已经执行完毕,这些变量也不会被销毁,仍然可以被内部函数访问。这就是闭包的基本原理。
function a() {
function b() {
var num = 1
console.log(count);
}
var count = 2
return b
}
var c = a()
c();
a函数返回一个函数,所以c其实就是b函数,我们来看看这段代码的执行上下文
当a的调用栈被js引擎销毁的时候此时b的作用域链指向一个新的区域,这个区域的集合就是闭包,里面存储了我们需要的数据
因此闭包的概念就是:
即使外部函数已经执行完毕,但是内部函数引用了外部函数中的变量依然会保存在内存中,我们把这些变量的集合,叫做闭包
看到这大家有没有更深入的了解js函数的编译原理