Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【进阶1-1期】理解JavaScript 中的执行上下文和执行栈 #12

Open
yygmind opened this issue Dec 4, 2018 · 30 comments
Open

Comments

@yygmind
Copy link
Owner

yygmind commented Dec 4, 2018

本期的主题是调用堆栈,本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,文末点击查看全部文章。

如果觉得本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。


执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。

执行上下文的类型

执行上下文总共有三种类型

  • 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。

  • 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。

  • Eval 函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用。

执行栈

执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。

首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。

根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中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

执行上下文的创建

执行上下文分两个阶段创建:1)创建阶段; 2)执行阶段

创建阶段

  • 1、确定 this 的值,也被称为 This Binding

  • 2、LexicalEnvironment(词法环境) 组件被创建。

  • 3、VariableEnvironment(变量环境) 组件被创建。

直接看伪代码可能更加直观

ExecutionContext = {  
  ThisBinding = <this value>,     // 确定this 
  LexicalEnvironment = { ... },   // 词法环境
  VariableEnvironment = { ... },  // 变量环境
}
This Binding
  • 全局执行上下文中,this 的值指向全局对象,在浏览器中this 的值指向 window 对象,而在nodejs中指向这个文件的module对象。

  • 函数执行上下文中,this 的值取决于函数的调用方式。具体有:默认绑定、隐式绑定、显式绑定(硬绑定)、new绑定、箭头函数,具体内容会在【this全面解析】部分详解。

词法环境(Lexical Environment)

词法环境有两个组成部分

  • 1、环境记录:存储变量和函数声明的实际位置

  • 2、对外部环境的引用:可以访问其外部词法环境

词法环境有两种类型

  • 1、全局环境:是一个没有外部环境的词法环境,其外部环境引用为 null。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。

  • 2、函数环境:用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。

直接看伪代码可能更加直观

GlobalExectionContext = {  // 全局执行上下文
  LexicalEnvironment: {    	  // 词法环境
    EnvironmentRecord: {   		// 环境记录
      Type: "Object",      		   // 全局环境
      // 标识符绑定在这里 
      outer: <null>  	   		   // 对外部环境的引用
  }  
}

FunctionExectionContext = { // 函数执行上下文
  LexicalEnvironment: {  	  // 词法环境
    EnvironmentRecord: {  		// 环境记录
      Type: "Declarative",  	   // 函数环境
      // 标识符绑定在这里 			  // 对外部环境的引用
      outer: <Global or outer function environment reference>  
  }  
}
变量环境

变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。

在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储**函数声明和变量( letconst绑定,而后者仅用于存储变量( 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 的情况下)或保持未初始化(在 letconst 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 letconst 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。

执行阶段

此阶段,完成对所有变量的分配,最后执行代码。

如果 Javascript 引擎在源代码中声明的实际位置找不到 let 变量的值,那么将为其分配 undefined 值。

参考

理解 Javascript 执行上下文和执行栈

进阶系列目录

  • 【进阶1期】 调用堆栈
  • 【进阶2期】 作用域闭包
  • 【进阶3期】 this全面解析
  • 【进阶4期】 深浅拷贝原理
  • 【进阶5期】 原型Prototype
  • 【进阶6期】 高阶函数
  • 【进阶7期】 事件机制
  • 【进阶8期】 Event Loop原理
  • 【进阶9期】 Promise原理
  • 【进阶10期】Async/Await原理
  • 【进阶11期】防抖/节流原理
  • 【进阶12期】模块化详解
  • 【进阶13期】ES6重难点
  • 【进阶14期】计算机网络概述
  • 【进阶15期】浏览器渲染原理
  • 【进阶16期】webpack配置
  • 【进阶17期】webpack原理
  • 【进阶18期】前端监控
  • 【进阶19期】跨域和安全
  • 【进阶20期】性能优化
  • 【进阶21期】VirtualDom原理
  • 【进阶22期】Diff算法
  • 【进阶23期】MVVM双向绑定
  • 【进阶24期】Vuex原理
  • 【进阶25期】Redux原理
  • 【进阶26期】路由原理
  • 【进阶27期】VueRouter源码解析
  • 【进阶28期】ReactRouter源码解析

交流

进阶系列文章汇总:https://github.com/yygmind/blog,内有优质前端资料,欢迎领取,觉得不错点个star。

我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!

@LeeRayno
Copy link

我在网上看到另外一篇文章,没有题主讲的深,细,但是关于执行上下文这块要更加通俗易懂一点js引擎的执行过程(一)

@5201314999
Copy link

我最近也在准备跳槽,楼主,除了面试题有没有什么其他的分享一下,对于普通人怎么进大公司的一些建议

@yygmind
Copy link
Owner Author

yygmind commented Dec 27, 2018

我在网上看到另外一篇文章,没有题主讲的深,细,但是关于执行上下文这块要更加通俗易懂一点js引擎的执行过程(一)

这篇也挺好的,搭配着看,理解就深了

@yygmind
Copy link
Owner Author

yygmind commented Dec 27, 2018

我最近也在准备跳槽,楼主,除了面试题有没有什么其他的分享一下,对于普通人怎么进大公司的一些建议

社招主要是项目经验 + 牢固的基础知识 + 对框架核心源码的理解。这几点比较重要

@javin9
Copy link

javin9 commented Dec 28, 2018

讲的不够细致,我看了原文才明白,最好把原文first 和second那个例子,也弄过来

@yygmind
Copy link
Owner Author

yygmind commented Dec 28, 2018

讲的不够细致,我看了原文才明白,最好把原文first 和second那个例子,也弄过来

已更新

@icantunderstand
Copy link

感谢分享

@zhangzs000
Copy link

ES6的执行上下文创建阶段,let/const被绑定到词法环境中,var被绑定到变量环境中?

@MBearo
Copy link

MBearo commented Mar 7, 2019

感谢楼主的文章!非常好奇文章里的相关知识是在哪里找到的呢?ESMA的规范么?

@hanqizheng
Copy link

变量提升那一块看不太懂QAQ

@zhangyl52
Copy link

支持下。

1 similar comment
@zhangyl52
Copy link

支持下。

@tyust512
Copy link

那个外部环境变量outer应该是放在LexicalEnvironment下, 而不是EnvironmentRecord下吧.
http://www.ecma-international.org/ecma-262/5.1/#sec-10 上这么写的

@tolerance-go
Copy link

或保持未初始化(在 let 和 const 的情况下)

这句话怎么理解呀

function f () { console.log(a); let a; } 
f()

还是会报错

@tyust512
Copy link

或保持未初始化(在 let 和 const 的情况下)

这句话怎么理解呀

function f () { console.log(a); let a; } 
f()

还是会报错

let const生命的变量就不会被提升仅此而已. 记住就行, 不需要什么理解吧

@tyust512
Copy link

有兴趣的去查看 这个图片 https://i.imgur.com/YKnvR8y.png

@gdwenjun
Copy link

执行栈看这个图片

@MBearo
Copy link

MBearo commented Mar 29, 2019

@tyust512 大佬这张图是哪里来的呀

@Chorer
Copy link

Chorer commented Apr 8, 2019

在ES6中,this binding好像并入了词法环境中,也就是执行上下文的创建阶段实际是:
1.词法环境
2.变量环境
作者原文已经修改了,https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0
但是掘金的译文目前没有更正。

@FE-Sadhu
Copy link

我也感觉和1-2所讲有些不同,前辈

@WUSO01
Copy link

WUSO01 commented Jul 3, 2019

变量提升的原因没看懂,讲的太含糊了

@tyust512
Copy link

tyust512 commented Jul 3, 2019

@tyust512 大佬这张图是哪里来的呀
不好意思了,我都是积累着的, 也忘记了到底从哪来的了, 反正图和tc39规范是一致的. 感觉从规范上去理解这些, 记住就行.

@TongDaDa
Copy link

感觉 Eval 函数执行上下文直接说动态上下文比较好

@wind4gis
Copy link

现在es6的词法环境除了global、function之外还有一个module词法环境,楼主可以加上去
附上es6规范
http://ecma-international.org/ecma-262/6.0/#sec-lexical-environments

@baiyunshenghaishang
Copy link

词法环境部分,第一段伪代码,少了一个大括号,导致EnvironmentRecord和outer并不是同级的。下面链接的译文也少了括号,英文原文是有的

@preflower
Copy link

preflower commented Mar 11, 2020

ES2018里,This Binding已经被归类于LexicalEnvironment了,这点希望了解一下。

@simon9211
Copy link

上下文讲的就是GO和AO吧 不够通俗

@itagan
Copy link

itagan commented Jul 4, 2020

我在网上看到另外一篇文章,没有题主讲的深,细,但是关于执行上下文这块要更加通俗易懂一点js引擎的执行过程(一)

这篇也挺好的,搭配着看,理解就深了

结合 极客时间的浏览器原理专栏,还是不错的,讲的比较接近

@coymaple
Copy link

coymaple commented Sep 2, 2020

变量提升那里“环境”指的是什么环境?未说明执行上下文和变量提升有什么关系。

@freedomlovey
Copy link

或保持未初始化(在 let 和 const 的情况下)

这句话怎么理解呀

function f () { console.log(a); let a; } 
f()

还是会报错
报错是因为出现了暂时性死区,let 和 const 声明的变量,只能在声明之后使用

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests