JavaScript
代码的执行过程
(1)编译阶段由编译器完成,将代码翻译成可执行代码,这个阶段作用域规则会确定,编译阶段经历以下三个步骤:
分词/词法分析:将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(
token
);
解析/语法分析:将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树——“抽象语法树”(AST
);
代码生成:将AST
转换为可执行代码的过程被称为代码生成。
(2)执行阶段由引擎完成,主要任务是执行可执行代码,**执行上下文****在这个阶段创建**。
JavaScript
代码执行过程中的“演员”:
- 编译器:负责语法分析及代码生成等脏活累活;
- 引擎:从头到尾负责整个
JavaScript
程序的编译及执行过程; - 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。
预编译步骤
执行前先扫描一下整体语法语句,如果有语法错误,那么直接报错,程序停止执行,没有错误的话,开始逐行解释执行。
局部预编译的 4 个步骤:
- 创建 AO 对象(Activation Object)执行期上下文。
- 找形参和变量声明,作为 AO 对象的属性,值暂时给个 undefined
- 将实参值和形参统一。
- 在函数体里面找函数声明,值赋予函数体。
举个栗子:
1 | function fn(a) { |
1.预编译第 1 步:创建 AO 对象
1 | AO{ |
2.预编译第 2 步:找形参和变量声明,写入 AO 对象的属性,值暂时给个 undefined
1 | AO{ |
3.预编译第 3 步:将实参值和形参统一
1 | AO{ |
4.预编译第 4 步:在函数体里面找函数声明,写入 AO 对象方法。
1 | AO{ |
全局预编译的 3 个步骤:
创建 GO 对象(Global Object)全局对象。
找变量声明,将变量名作为 GO 属性名,值为 undefined
查找函数声明,作为 GO 属性,值赋予函数体
执行上下文
- 当 js 引擎第一次遇到要执行的代码的时候,首先会创建一个全局的执行上下文并压入当前执行栈,执行栈是一种拥有后进先出的数据结构的栈
- 此后,每当 JS 引擎要调用一个函数之前,就会进行局部预编译,创建一个新的执行上下文并压入栈顶
- 每当栈顶的执行上下文执行完毕,JS 引擎会将其从栈中弹出,控制流程到达下一个上下文。
对于每一个执行上下文都含有三个重要属性:变量对象,作用域链,this。
三种执行上下文类型
- 全局上下文
- 函数上下文
- eval 和 with 上下文
作用域概述:
- 全局作用域(Global Scope)
- 最外层函数和在最外层函数外面定义的变量拥有全局作用域
- 所有末声明直接赋值的变量自动声明为拥有全局作用域,like
a = 10
- 所有 window 对象的属性拥有全局作用域。
- 局部作用域(Local Scope), 只有局部代码才能访问到的作用域。
- 函数作用域
- 块级作用域
- 欺骗作用域
或者可分为
词法作用域和动态作用域
词法作用域由编译时函数声明的位置来确定,而动态作用域由执行时的位置来确定
欺骗作用域(已废弃不用)
- eal()和 with()方法
- 词法作用域完全由写代码期间函数声明的位置来定义。怎么样才能在运行时来修改(也就是欺骗)词法作用域
- 由于会导致性能问题已经被废弃 * 因为 js 解释器编译阶段会对性能进行优化,而使用 eval 或者 with 会破坏解释器对作用域的管理规则,解释器怕出问题就不会再优化,所以性能下降
eval
eval 方法会将它的参数(str)作为 JS 代码来执行,该方法内的变量的作用域是不一定的,
- eval 方法若写在函数内,则它是函数作用域;
- 写在全局作用域下, 它是全局作用域
要在函数内部使 eval 中变量拥有全局作用域的两个方法
- 使用
window.eval()
,但需要考虑浏览器兼容性,在 IE9 之前,它无效,还是局部 - 将
eval
赋值给一个全局变量,使之具有全局作用域,然后在函数中使用该变量with
with 可以接受一个对象作为参数,并凭空创建了一个全新的词法作用域,但这个块内部正常的 var 声明并不会被限制在这个块的作用域中,而是被添加到 with 所处的函数作用域中。
块级作用域
let、const 声明的变量拥有块级作用域
它们只在相应的**代码块(花括弧内)**中有效,在外部访问不到
作用域链:
概述
- 作用域链:根据名称查找变量(标识符名称)的一套规则。
- 作用域链的用途是保证执行环境有序访问它有权访问的变量和函数
作用域链创建和变量查找过程
- JS 引擎预编译到函数声明时,在函数对象内部生成一个只能由 JS 引擎访问到的属性
[[scope]]
,该属性保存该函数的作用域链,作用域链的头部存储这个函数生成的执行上下文AO,尾部存储 GO - 这个作用域链中记录了此函数内部所能访问到的所有数据。
- 其中链的**头部****是当前函数的活动对象**(AO),如果存在多个函数嵌套,接下来指向的是当前函数上层函数的活动对象(AO), 依次类推;
- 无论存不存在函数嵌套,作用域链**尾部****都是全局对象**。
- 当访问一个数据时,会沿着作用域链头部往后找,直到找到全局对象,如果都没有,将报错。
- 若找到第一个匹配的变量时,就停止查找,被称为遮蔽效应
- 函数内部作用域在最顶端,证明了函数可以访问外部的变量,而外部无法访问函数内部的变量
变量对象与活动对象:
- 变量对象:
- 编译时所创建,只有 JS 引擎能访问.
- 变量对象包括函数声明时的参数,内部声明的变量以及函数。
- 活动对象:
- 还是变量对象本身,只不过是因为处在执行阶段可以被访问所以成了活动对象
- 二者区别:为同一个对象处于不同程序运行时的两种称谓。(或者是处于执行上下文的不同生命周期)
参考:
评论