大家好,金三银四马上也快到了,总据说行情不好,面试不好面,不过如同也没什么太大关系,该换新工作就换,只有准备充分还怕它什么行情不好。笔者呢最近也有想法所以再回顾JavaScript常识时,又看到了JavaScript的执行上下文

那么这篇文章呢一小部分内容是我本人的一些了解。

大部分内容来自[译] 了解 JavaScript 中的执行上下文和执行栈

原文地址:Understanding Execution Context and Execution Stack in Javascript

例题

大家先来看一道较为简单的题,看下是否能看进去后果

var a = 10;function fn(b) {  b = 20;  console.log(a, b);}function fn1() {  a = 100;  fn(a);}fn(200); //输入后果fn1(); // 输入后果

大家能够看进去输入后果是什么吗?

如果你曾经算进去的话,那么阐明你对执行上下文还是有一些了解的,欢送持续往下看加深印象

如果你没算进去或者输入后果与你算的不相符,那也先不要焦急,先看下边内容,看完后再回来算

执行上下文

概念

大家都晓得,JavaScript代码的在运行的时候都是自上而下按程序执行的,然而呢理论并非是一行一行的执行,那大家有没有理解过它在执行代码的时候做过哪些筹备,做过哪些事件,比方代码解析、调配内容都是在哪解决的,那这个中央呢就是执行上下文,是筹备工作的所在环境

执行上下文类型

执行上下文呢有三种类型,别离是

  • 全局执行上下文
  • 函数执行上下文
  • 还有就是eval函数执行上下文

那么咱们持续,执行上下文呢是在代码编译阶段创立的,来看看执行上下文的生命周期

执行上下文生命周期

  • 创立阶段
  • 执行阶段

创立阶段

执行上下文的创立阶段具体做了什么事呢,又分为三局部

ExecutionContext = {  ThisBinding = <this value>,  LexicalEnvironment = { ... },  VariableEnvironment = { ... },}
确定this指向

在全局执行上下文中,this指向的是全局对象

在函数执行上下文中,this指向取决于该函数是如何被调用的

看下这个demo

const obj = {  fn: function(){    console.log(this)  }}obj.fn(); //fn: f();const func = obj.fn;func(); // Window
词法环境

官网的 ES6 文档把词法环境定义为

词法环境是一种标准类型,基于 ECMAScript 代码的词法嵌套构造来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的援用内部词法环境的空值组成。

简略来说词法环境是一种持有标识符—变量映射的构造。(这里的标识符指的是变量/函数的名字,而变量是对理论对象[蕴含函数类型对象]或原始数据的援用)。

当初,在词法环境的外部有两个组件:(1) 环境记录器和 (2) 一个外部环境的援用

  1. 环境记录器是存储变量和函数申明的理论地位。
  2. 外部环境的援用意味着它能够拜访其父级词法环境(作用域)。

词法环境有两种类型:

  • 全局环境(在全局执行上下文中)是没有外部环境援用的词法环境。全局环境的外部环境援用是 null。它领有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比方 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
  • 函数环境中,函数外部用户定义的变量存储在环境记录器中。并且援用的外部环境可能是全局环境,或者任何蕴含此外部函数的内部函数。

环境记录器也有两种类型(如上!):

  1. 申明式环境记录器存储变量、函数和参数。
  2. 对象环境记录器用来定义呈现在全局上下文中的变量和函数的关系。

简而言之,

  • 全局环境中,环境记录器是对象环境记录器。
  • 函数环境中,环境记录器是申明式环境记录器。

留神 — 对于函数环境申明式环境记录器还蕴含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length

抽象地讲,词法环境在伪代码中看起来像这样:

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>  }}

留神 — 只有遇到调用函数 multiply 时,函数执行上下文才会被创立。

可能你曾经留神到 letconst 定义的变量并没有关联任何值,但 var 定义的变量被设成了 undefined

这是因为在创立阶段时,引擎查看代码找出变量和函数申明,尽管函数申明齐全存储在环境中,然而变量最后设置为 undefinedvar 状况下),或者未初始化(letconst 状况下)。

这就是为什么你能够在申明之前拜访 var 定义的变量(尽管是 undefined),然而在申明之前拜访 letconst 的变量会失去一个援用谬误。

这就是咱们说的变量申明晋升。

执行阶段

这是整篇文章中最简略的局部。在此阶段,实现对所有这些变量的调配,最初执行代码。

留神 — 在执行阶段,如果 JavaScript 引擎不能在源码中申明的理论地位找到 let 变量的值,它会被赋值为 undefined

执行栈

那根据上述执行上下文的了解,那咱们晓得在执行代码中会有很多的执行上下文,那么执行上下文是怎么确定执行程序的。

执行上下文寄存的地位就是在执行上下文栈,也叫调用栈。具备LIFO(Last In First Out后进先出,也就是先进后出)的个性。

那咱们来看下之前的例题,来剖析下

var a = 10;function fn(b) {  b = 20;  console.log(a, b);}function fn1() {  a = 100;  fn(a);}fn(200); //输入后果fn1(); // 输入后果
  1. 首先进入全局执行环境,创立全局执行上下文环境并退出栈中
  2. fn()函数被调用,进入对应的函数执行环境,创立函数执行环境并退出栈
  3. 执行 console.log(a, b);代码
  4. console.log(a, b);代码出栈
  5. fn()函数执行结束后出栈
  6. fn1()函数被调用,进入对应的函数执行环境,创立函数执行环境并退出栈
  7. 持续fn()函数被调用,进入对应的函数执行环境,创立函数执行环境并退出栈
  8. 执行 console.log(a, b);代码
  9. console.log(a, b);代码出栈
  10. fn()函数执行结束后出栈
  11. fn1()函数出栈
  12. 全局执行上下文出栈

题解

那咱们再来剖析下例题的答案

var a = 10;function fn(b) {  b = 20;  console.log(a, b);}fn(200);

在执行fn函数时,此fn流动对象为

AO : {  a: 10,  b: 20,  arguments: {0 : 20, length:0} }

所以此时输入后果为10,20

持续看

var a = 10;function fn(b) {  b = 20;  console.log(a, b);}function fn1() {  a = 100;  fn(a);}fn1();

在执行fn1函数时,此fn1流动对象为

AO : {  a: 100,  fn: reference to function fn(){}  arguments: {length: 0} }

在继续执行fn函数时,此fn流动对象为

AO : {  a: 100,  b: 20,  arguments: {0 : 20, length:0} }

所以此时输入后果为100,20

结语

如果感觉此文的大屏数据交互方式对你帮忙的话,请不吝点个赞,反对一下