JS引擎线程的执行过程的三个阶段(一)
浏览器首先按顺序加载由<script>标签分割的js代码块,加载js代码块完毕后,立刻进入以下三个阶段,然后再按顺序查找下一个代码块,再继续执行以下三个阶段,无论是外部脚本文件(不异步加载)还是内部脚本代码块,都是一样的原理,并且都在同一个全局作用域中。
JS引擎线程的执行过程的三个阶段:
- 语法分析
- 预编译阶段
- 执行阶段
一. 语法分析
分析该js脚本代码块的语法是否正确,如果出现不正确,则向外抛出一个语法错误(SyntaxError),停止该js代码块的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入预编译阶段。
下面阶段的代码执行不会再进行语法校验,语法分析在代码块加载完毕时统一检验语法。
二. 预编译阶段
1. js的运行环境
全局环境(JS代码加载完毕后,进入代码预编译即进入全局环境)
函数环境(函数调用执行时,进入该函数环境,不同的函数则函数环境不同)
eval(不建议使用,会有安全,性能等问题)
每进入一个不同的运行环境都会创建一个相应的执行上下文(Execution Context),那么在一段JS程序中一般都会创建多个执行上下文,js引擎会以栈的方式对这些执行上下文进行处理,形成函数调用栈(call stack),栈底永远是全局执行上下文(Global Execution Context),栈顶则永远是当前执行上下文。
2. 函数调用栈/执行栈
调用栈,也叫执行栈,具有LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。
当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。
- var 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');
- // Inside first function
- // Inside second function
- // Again inside first function
- // Inside Global Execution Context
3. 执行上下文的创建
执行上下文可理解为当前的执行环境,与该运行环境相对应,具体分类如上面所说分为全局执行上下文和函数执行上下文。创建执行上下文的三部曲:
创建变量对象(Variable Object)
建立作用域链(Scope Chain)
确定this的指向
3.1 创建变量对象
创建arguments对象:检查当前上下文中的参数,建立该对象的属性与属性值,仅在函数环境(非箭头函数)中进行,全局环境没有此过程
检查当前上下文的函数声明:按代码顺序查找,将找到的函数提前声明,如果当前上下文的变量对象没有该函数名属性,则在该变量对象以函数名建立一个属性,属性值则为指向该函数所在堆内存地址的引用,如果存在,则会被新的引用覆盖。
检查当前上下文的变量声明:按代码顺序查找,将找到的变量提前声明,如果当前上下文的变量对象没有该变量名属性,则在该变量对象以变量名建立一个属性,属性值为undefined;如果存在,则忽略该变量声明
函数声明提前和变量声明提升是在创建变量对象中进行的,且函数声明优先级高于变量声明。具体是如何函数和变量声明提前的可以看后面。
创建变量对象发生在预编译阶段,但尚未进入执行阶段,该变量对象都是不能访问的,因为此时的变量对象中的变量属性尚未赋值,值仍为undefined,只有进入执行阶段,变量对象中的变量属性进行赋值后,变量对象(Variable Object)转为活动对象(Active Object)后,才能进行访问,这个过程就是VO –> AO过程。
3.2 建立作用域链
通俗理解,作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
可以通过一个例子简单理解:
- var num = 30;
- function test() {
- var a = 10;
- function innerTest() {
- var b = 20;
- return a + b
- }
- innerTest()
- }
- test()
在上面的例子中,当执行到调用innerTest函数,进入innerTest函数环境。全局执行上下文和test函数执行上下文已进入执行阶段,innerTest函数执行上下文在预编译阶段创建变量对象,所以他们的活动对象和变量对象分别是AO(global),AO(test)和VO(innerTest),而innerTest的作用域链由当前执行环境的变量对象(未进入执行阶段前)与上层环境的一系列活动对象组成,如下:
- innerTestEC = {
- //变量对象
- VO: {b: undefined},
- //作用域链
- scopeChain: [VO(innerTest), AO(test), AO(global)],
- //this指向
- this: window
- }
深入理解的话,创建作用域链,也就是创建词法环境,而词法环境有两个组成部分:
- 环境记录:存储变量和函数声明的实际位置
- 对外部环境的引用:可以访问其外部词法环境
词法环境类型伪代码如下:
- // 第一种类型: 全局环境
- GlobalExectionContext = { // 全局执行上下文
- LexicalEnvironment: { // 词法环境
- EnvironmentRecord: { // 环境记录
- Type: "Object", // 全局环境
- // 标识符绑定在这里
- outer: <null> // 对外部环境的引用
- }
- }
- // 第二种类型: 函数环境
- FunctionExectionContext = { // 函数执行上下文
- LexicalEnvironment: { // 词法环境
- EnvironmentRecord: { // 环境记录
- Type: "Declarative", // 函数环境
- // 标识符绑定在这里 // 对外部环境的引用
- outer: <Global or outer function environment reference>
- }
- }
在创建变量对象,也就是创建变量环境,而变量环境也是一个词法环境。在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量( let
和 const
)绑定,而后者仅用于存储变量( var
)绑定。
如例子:
- 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",
- // 标识符绑定在这里
- a: < uninitialized >,
- b: < uninitialized >,
- multiply: < func >
- }
- outer: <null>
- },
- VariableEnvironment: {
- EnvironmentRecord: {
- Type: "Object",
- // 标识符绑定在这里
- c: undefined,
- }
- 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>
- }
- }
变量提升的具体原因:在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined
(在 var
的情况下)或保持未初始化(在 let
和 const
的情况下)。所以这就是为什么可以在声明之前访问 var
定义的变量(尽管是 undefined
),但如果在声明之前访问 let
和 const
定义的变量就会提示引用错误的原因。此时let 和 const处于未初始化状态不能使用,只有进入执行阶段,变量对象中的变量属性进行赋值后,变量对象(Variable Object)转为活动对象(Active Object)后,let
和const
才能进行访问。
关于函数声明和变量声明,这篇文章讲的很好:https://github.com/yygmind/blog/issues/13
另外关于闭包的理解,如例子:
- function foo() {
- var num = 20;
- function bar() {
- var result = num + 20;
- return result
- }
- bar()
- }
- foo()
浏览器分析如下:
chrome浏览器理解闭包是foo,那么按浏览器的标准是如何定义闭包的,总结为三点:
在函数内部定义新函数
新函数访问外层函数的局部变量,即访问外层函数环境的活动对象属性
新函数执行,创建新的函数执行上下文,外层函数即为闭包
3.3 this指向
比较复杂,后面专门弄一篇文章来整理。
第三个阶段请看 JS引擎线程的执行过程的三个阶段(二)
文章参考:
https://github.com/yygmind/blog/issues/12
https://heyingye.github.io/2018/03/19/js引擎的执行过程(一)
https://heyingye.github.io/2018/03/26/js引擎的执行过程(二)
https://github.com/yygmind/blog
JS引擎线程的执行过程的三个阶段(一)的更多相关文章
- JS引擎线程的执行过程的三个阶段(二)
继续JS引擎线程的执行过程的三个阶段(一) 内容, 如下: 三. 执行阶段 1. 网页的线程 永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进事件队列,等待JS引 ...
- JS的解析与执行过程
JS的解析与执行过程 全局中的解析和执行过程 预处理:创建一个词法环境(LexicalEnvironment,在后面简写为LE),扫描JS中的用声明的方式声明的函数,用var定义的变量并将它们加到预处 ...
- 对象回收过程?线程池执行过程? map原理?集合类关系?synchronized 和 volatile ? 同一个类的方法事务传播控制还有作用吗?java 锁
1. 对象回收过程? 可达性分析算法: 如果一个对象从 GC Roots 不可达时,则证明此对象不可用. 通过一系列称为GC ROOTS的对象作为起点,从这些起点往下搜索,搜索走过的路径 称为引用链 ...
- JS的解析与执行过程—全局预处理阶段之全局词法环境对象
问题:有如下代码 var a = 1; function pop() { alert(a); var a = 5; } pop();//执行结果,弹出undefined 这段代码的执行结果为undef ...
- JS的解析与执行过程—函数预处理
声明:之所以分为全局预处理与函数预处理,只是为了理解方便,其实在实际运行中二者是不分先后的. 函数预处理阶段与全局预处理的差别: 函数每调用一次,就会产生一个LexicalEnviroment对象,在 ...
- JS的解析与执行过程—全局预处理阶段之命名冲突的处理策略
有如下代码: <body> <script> alert(f); function f() { console.log("fff"); } var f = ...
- 【JS】js引擎执行过程
概述 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍了语法分析和预编译阶段,那么我们先做个简单概括,如下: 语法分析: 分别对加载完成的代码块进行语法检验,语法正 ...
- (转载)js引擎的执行过程(二)
概述 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍了语法分析和预编译阶段,那么我们先做个简单概括,如下: 语法分析: 分别对加载完成的代码块进行语法检验,语法正 ...
- (转载)js引擎的执行过程(一)
概述 js是一种非常灵活的语言,理解js引擎的执行过程对我们学习javascript非常重要,但是网上讲解js引擎的文章也大多是浅尝辄止或者只局部分析,例如只分析事件循环(Event Loop)或者变 ...
随机推荐
- 如何在浏览器中输入(myeclipse创建的项目的)地址访问JSP页面
如何在浏览器中输入(myeclipse创建的项目的)地址访问JSP页面 可以在Tomcat项目里面查看你的JSP页面在哪里,具体的路径为: tomcat--work--localhost--项目名称, ...
- python多线程和多进程使用
# 多线程 from concurrent.futures import ThreadPoolExecutor # 多进程 from concurrent.futures import Process ...
- Scala语言笔记 - 第一篇
目录 Scala语言笔记 - 第一篇 1 基本类型和循环的使用 2 String相关 3 模式匹配相关 4 class相关 5 函数调用相关 Scala语言笔记 - 第一篇 最近研究了下scala ...
- centos7zabbix-agen安装
安装包下载地址:http://www.zabbix.com/download.php 下载对应rpm包 http://repo.zabbix.com/zabbix/ wget http://r ...
- 如何安装并且使用jmeter进行简单的性能测试
Jmeter 介绍 Jmeter 是一款使用Java开发的,开源免费的,测试工具, 主要用来做功能测试和性能测试(压力测试/负载测试). 而且用Jmeter 来测试 Restful API, 非常 ...
- MyEclipse 10 报错记录
1. js文件:右键 >> MyEclipse >> Exclude From Validation 2. Servlet 警告:Window ==> Preferenc ...
- 对JS闭包和函数作用域的问题的深入讨论,如何理解JS闭包和函数作用域链?
首先先引用<JavaScript权威指南>里面的一句话来开始我的博客:函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的. 因此,就出现了如下的几串代码: ...
- JVM,Tomcat与OSGi类加载机制比较
首先一个思维导图来看下Tomcat的类加载机制和JVM类加载机制的过程 类加载 在JVM中并不是一次性把所有的文件都加载到,而是一步一步的,按照需要来加载. 比如JVM启动时,会通过不同的类加载器加载 ...
- [Swift]LeetCode2. 两数相加 | Add Two Numbers
You are given two non-empty linked lists representing two non-negative integers. The digits are stor ...
- [Swift]LeetCode84. 柱状图中最大的矩形 | Largest Rectangle in Histogram
Given n non-negative integers representing the histogram's bar height where the width of each bar is ...