js 执行流程
- 编译/解析阶段: js 执行前会有一个解析过程,会检查语法,创建全局执行上下文,并对函数进行预编译,初始化一些代码执行时所用到得变量进行变量提升.当访问一个变量时,会到当前执行上下文中的作用域链上查找.
- 执行阶段: 按照代码顺序依次执行同步任务,同步任务完成后按照流程执行异步任务
js 为什么要有变量提升
-
优点:
- 提高性能: 解析和预编译过程中的声明提升可以提高性能,让函数可以在执行时预先为变量分配栈空间
- 容错性更好: 声明提升还可以提高 JS 代码的容错性,使一些不规范的代码也可以正常执行
-
在什么阶段进行的变量提升: js 执行流程的编译阶段
变量提升(var let const function class)
暂时性死区:当程序的控制流程在新的作用域(module function 或 block 作用域)进行实例化时,在此作用域中用 let/const 声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定, 所以是不能被访问的,如果访问就会抛出 uninitialized 错误。因此,在这运行流程进入作用域提升变量,到执行声明变量这行代码之间的这一段时间,就称之为暂时死区。
- ps:
-
ES5 中作用域有:全局作用域 函数作用域
-
ES6 中增加的块级作用域(块作用域由 { } 包括,if 语句和 for 语句里面的{ }也属于块作用域)
-
变量提升,具名创建的函数(不包含通过 var 声明的匿名函数等)会优先于 var let const 之前进行变量提升
javascript 复制代码 function fn(){ console.log(a) var a=2; function a(){} console.log(a) } fn(1) // 变量提升后 function fn(){ function a(){} var a; console.log(a) // fn a=2; console.log(a) // 2 } fn(1)
- 总结:
- var 定义的变量,没有块级作用域特性。变量提升后,在执行上下文的变量环境下初始化值为 undefined
- let 定义的变量,具有块级作用域特性。变量提升后,在执行上下文的词法环境下初始化值为未初始化 uninitialized。不可声明多次同变量名称的变量。
- const 用来定义常量,具有块级作用域特性。变量提升后,在执行上下文的词法环境下初始化值为未初始化 uninitialized。const 声明时必须赋值。const 赋值后的基本类型变量不可能修改、复杂类型不可修改的是其存储在栈中的指针。在一个全局环境下(即在一个 html 文件下的所有 js 代码中)不能声明多次
- class 创建类,具有块级作用域特性。变量提升后,在执行上下文的词法环境下初始化值为未初始化 uninitialized
- function 具名函数创建下,没有块级作用域特性。变量提升后,在执行上下文的变量环境下初始化值为 undefined
执行上下文
-
ps: 创建执行上下文的过程中,也是指定此执行上下文 this 的过程(因为箭头函数不会创建执行上下文,所以其没有 this 指向,会沿用上层的 this 指向)
-
执行上下文的类型
- 全局执行上下文 -- 任何不在函数内部的代码都在全局上下文中。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。一个程序中只会有一个全局执行上下文。
- 函数执行上下文 -- 每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建,它会按定义的顺序(后进先出)执行一系列步骤。
- Eval 函数执行上下文 -- 执行在 eval 函数内部的代码也会有它属于自己的执行上下文
执行栈 : 是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储/管理代码运行时创建的所有执行上下文。
例如 : 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。
sql
复制代码
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
-
创建执行上下文流程 : 有两个阶段 创建阶段 和 执行阶段
-
在 JavaScript 代码执行前,执行上下文将经历创建阶段。在创建阶段会发生三件事:
- this 值的决定,即我们所熟知的 This 绑定。
- 创建词法环境组件。
- 创建变量环境组件。
ini 复制代码 ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }yaml 复制代码 let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30); // ----------- 执行上下文的样子 -------------- GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // 在这里绑定标识符 let const被放在词法环境下,与var不同,所以其会出现暂时性死区,因为开始的变量提升let const的值是未初始化 a: < uninitialized >, b: < uninitialized >, } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // 在这里绑定标识符 var被放在变量环境下,所以变量提升后的初始化值是undefined c: undefined, multiply: < func > } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // 在这里绑定标识符 Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // 在这里绑定标识符 g: undefined }, outer: <GlobalLexicalEnvironment> } }
-