javascript运行机制

写在后面

看上面两段代码,管制台上会输入什么?

function foo() {    var a = 2;    this.bar();}function bar() {    console.log('this.a', this.a);      console.log('a', a);}foo();
function foo() {    var a = 2;    bar();}function bar() {    console.log('this.a', this.a);      console.log('a', a);}foo();

答案是:

两段代码后果雷同

this.a undefinedReferenceError: a is not defined

如果你答对了,祝贺你曾经对作用域、this 有肯定意识,如果你想更深刻理解代码执行过程中产生了什么,请往下看。

浏览本文,你将搞清楚以下概念:

  1. this 到底是什么
  2. 执行上下文 Execution Context
  3. 调用栈 Context Stack
  4. 作用域链 Scope Chain
  5. 变量对象 Virable Object(VO) 和 流动对象 Active Object(AO)
  6. 词法环境 Lexical Environment 和 变量环境 Variable Environment

浏览本文前,你须要以下常识储备

  1. var函数申明 具备晋升的个性,而 const let 函数表达式 没有
  2. js 中作用域的只有 全局作用域函数作用域,ES6 中新增了块级作用域
  3. 每个函数在执行时会创立一个流动对象,记录函数调用地位、变量等等

1. 什么是执行上下文

当一个函数被调用时,会创立一个流动记录。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用办法、传入的参数等信息。这个流动记录又称为执行上下文、执行环境

小结:执行上下文和执行环境是一个概念,记录了很多信息

1.1 执行上下文 / 执行环境的分类

  • 全局执行上下文 / 全局执行环境
  • 函数执行上下文 / 函数执行环境

1.2 执行栈

在剖析执行上下文内容之前,还须要理解执行栈

在咱们的代码执行之前,引擎初始化一个执行栈,初始化结束后,将全局执行环境压入执行栈,当每当一个函数开始执行,就创立一个函数执行环境压入栈,函数执行结束后,将该函数执行环境弹出,敞开浏览器时,弹出全局执行环境

function foo(i) {  if (i < 0) return;  console.log('begin:' + i);  foo(i - 1);  console.log('end:' + i);}foo(2);/*begin:2begin:1begin:0end:0end:1end:2*/

图片及代码来自 https://juejin.im/post/5c2052...

想有更多了解,请拜访 https://www.cnblogs.com/wangf...

2. 执行环境的创立与激活

执行环境有两个阶段

  1. 创立阶段
  2. 激活 / 执行阶段

ES3 执行环境生成标准

  • 创立阶段

    • 创立作用域链 Scope Chain
    • 创立变量对象 Variable Object
    • 为 this 赋值
  • 激活 / 执行阶段

    • 实现变量调配,变 VO 为 AO,执行代码

ES6 执行环境生成标准

  • 创立阶段

    • 为 this 赋值
    • 创立词法环境 Lexical Environment
    • 创立变量环境 Variable Environment
  • 执行阶段

    • 实现变量调配,执行代码

3. ES3 中的 Execution Context

3.1 作用域链 Scope Chain

我的了解是,生成Scope Chain时,会扫描函数作用域,留神要与执行上下文辨别,函数中拜访父级作用域的操作,就是顺着Scope Chain实现的。举个例子

var a = 1;function foo() {    var a = 2;    bar();}function bar() {      console.log('a', a);}foo();

打印后果如何?

1

为什么是这个后果?

在执行 bar() 时,生成的执行上下文中没有变量 a 存在,于是,顺着 scope chain 向上寻找父级作用域,于是,找到了 bar 父级作用域 window 的变量 a,而不是 foo 作用域中的变量 a

当初你大略明确作用域是如何在函数执行时起作用的了吧

兴许你对作用域的了解产生波动,请拜访 深刻了解javascript原型和闭包(12)——简介【作用域】

Scope Chain 连贯的是作用域,而不是执行上下文!
PS:这里的作用域在《你不晓得的js》中被叙述为词法作用域,对以上打印后果有质疑的同学,能够检索下动静作用域。也能够翻阅《你不晓得的js 上卷》第58 59页

3.2 变量对象 VO,流动对象 AO

  • 变量对象:存储 js 执行上下文中的函数标志符、形参 argument、变量申明,这个对象在 js 环境下是不可拜访的。
  • 流动对象:存储变量对象 VO 中的申明、形参 argument、函数标识符,与 VO 不同的是,AO 是“激活版”的 VO,其存储的内容是能够被拜访到的
对于二者区别,上面将中伪代码展现

创立 VO 分三步

  1. 创立 argument 对象
  2. 扫描上下文中的函数申明,以函数名为 key,在堆中对应的地址为 value,记录在 VO 中(波及概念晋升、堆栈模型)。如果有两个同名函数,当前呈现的为准,VO 中后呈现的函数笼罩先呈现的函数
  3. 扫描上下文中 var 变量申明,在以变量名为 key,并初始化 value 为 undefined。若同 var 变量呈现第二个申明,则疏忽并持续扫描
// 以上能够了解function foo(){ console.log('foo申明第一次') }function foo(){ console.log('foo申明第二次') }foo()// 'foo申明第二次'
ES3 期间变量申明只有 var,还没有 const let

3.3 this

其实 this 的绑定规定很简略,this指向调用该函数的对象,如

var a = 1;var obj = {    a: 2,    foo: foo}function foo() {    console.log(this.a);}obj.foo(); // 2foo();

按规定,执行 obj.foo() 时,this 指向obj。执行 foo() 等价于 window.foo() ,所以 this 指向 window

当然,bind apply call 能够扭转函数外部的 this 指向,本文不做探讨。我想表明的是,this 很简略,只是执行函数时,生成执行上下文中的一个步骤。this 赋值很有法则,this指向调用该函数的对象,并不是什么牛鬼蛇神,只是一个小步骤

3.4 实战演习一波

function foo(param1, param2) {  var name = 'cregskin';  var age = 20;    function bar() {    var name = 'jellyFish'    var age = 20;  }    bar();}foo();
// 1. 初始化,创立全局执行上下文GlobalExecutionContext = {  ScopeChain: null,  VariableObject: { // 创立(js 不可拜访)    foo: pointer to foo  },  this: window}GlobalExecutionContext = {  ScopeChain: null,  ActiveObject: { // 激活(js 能够拜访)    foo: pointer to foo  },  this: window}// 2. 执行foo() ,创立 foo() 执行上下文fooExecutionContext = {  ScopeChain: Global{},  VariableObject: { // 创立(js 不可拜访)    name: undefined,    age: undefined,    bar: pointer to bar  }}fooExecutionContext = {  ScopeChain: Global{},  ActiveObject: { // 激活(js 能够拜访)    name: undefined,    age: undefined,    bar: pointer to bar  }}// 3. 执行 bar(),创立 bar() 执行上下文barExecutionContext = {  ScopeChain: foo{},  VariableObject: { // 创立(js 不可拜访)    name: undefined,    age: undefined  },  this: window}barExecutionContext = {  ScopeChain: foo{},  ActiveObject: { // 激活(js 能够拜访)    name: undefined,    age: undefined  },  this: window}

怎么样!是不是很通透了,我再插一句话

ES3 中只有全局作用域和函数作用域,执行上下文是函数执行时才生成的。请再次回顾一下,执行上下文的 VO AO 蕴含的内容,不就是函数作用域本身中的变量申明和函数申明吗!Scope Chain 连贯的作用域,不就是函数定义时的作用域嵌套关系吗!

执行上下文和作用域居然有如此高度的一致性!妙不可言

3.5 小结一下

列一下 ES3 中执行上下文的构造

  • Scope Chain
  • Variable Object / Active Object

    • var 变量申明
    • 函数申明
  • this

构造简略。上面我将介绍 ES5 中的执行上下文,大同小异

4. ES6 中的 Execution Context

4.1 词法环境 Lexical Environment

4.1.1 概念

官网 给出的概念是:词法环境是一种标准类型,基于 ECMAScript 代码的词法嵌套构造来定义标识符与特定变量和函数的关联关系。词法环境由环境记录(environment record)和可能为空援用(null)的内部词法环境组成。

词法环境在 js 中也有很多别称:词法作用域、作用域

是不是能串起来了!相比 ES3, ES6 中的词法环境作用域在命名上有更深的羁绊!

4.1.2 组成

  • Laxical Environment 词法环境

    • Environment Record 环境记录器

      • 变量申明
      • 函数申明
    • Outer 外部环境援用

类比一下之前讲到的 ES3 版本的执行上下文,你的脑中可能有大抵的对应关系了

ES3ES6
Scope ChainOuter
Viriable Object / Active ObjectEnvironment Record

哈哈好厉害,我第一看的时候都缕了良久

上面我将介绍Environment Record ,实质上与 VO AO 还是有一些区别的

留神 ES6 引入了块级作用域,const let 没有晋升,在这里是要有体现的

4.1.3 Environment Record 环境记录器

对应两种执行环境(全局执行环境、函数执行环境),环境记录器也有两种,

  • 申明式环境记录器(在函数执行环境中应用)

    • 贮存 const let 变量
    • 贮存 argument 参数
    • 贮存函数申明
  • 对象环境记录器(在全局执行环境中应用)

    • 变量申明
    • 函数申明
var 申明呢?有不少 var const let 混用的场景啊。

别急,下文中会介绍

4.1.4 Outer 外部环境援用

这个就不多介绍了,与 ES3 中 Scope Chain 一样。

作用是拜访父级作用域

4.2 Variable Environment 变量环境

还记得咱们之前埋的坑吗——var 变量存储在哪里?

4.2.1 组成

  • Variable Environment 变量环境

    • Environment Record 环境记录器(在函数执行环境中应用)

      • 存储 var 变量申明
    • Object environment records 对象环境记录器(在全局执行环境中应用)

      • 变量申明
      • 函数申明
在 ES6 中,词法环境变量环境的一个不同就是前者被用来存储函数申明和变量(letconst)绑定,而后者只用来存储 var 变量绑定。

4.3 实战演练

function foo() {    const a = 2;    this.bar();}function bar() {    console.log('this.a', this.a);      console.log('a', a);}foo();

创立作用域链之前须要理解一下作用域

深刻了解javascript原型和闭包(12)——简介【作用域】

步骤如下

看到这里,文章结尾的那两道题你应该能够解了。 我先给出执行环境的内容

// 1. 初始化,创立全局执行上下文GlobalExecutionContext = {    Outer: null,    LexicalEnvironment: { // 创立(js 不可拜访)      foo: pointer to foo      arguments: []    },    this: window  }  GlobalExecutionContext = {    Outer: null,    LexicalEnvironment: { // 激活(js 能够拜访)      foo: pointer to foo,      arguments: []    },    this: window  }    // 2. 执行foo() ,创立 foo() 执行上下文  fooExecutionContext = {    Outer: Global{},    LexicalEnvironment: { // 创立(js 不可拜访)      a: <undefined>, // 留神 const 没有变量晋升      bar: pointer to bar,      arguments: []    }  }  fooExecutionContext = {    Outer: Global{},    LexicalEnvironment: { // 激活(js 能够拜访)      a: <undefined>, // 留神 const 没有变量晋升      bar: pointer to bar,      arguments: []    },    this: window  }    // 3. 执行 bar(),创立 bar() 执行上下文  barExecutionContext = {    Outer: Global{},    LexicalEnvironment: { // 创立(js 不可拜访)      arguments: []    },    this: window  }  barExecutionContext = {    Outer: Global{},    LexicalEnvironment: { // 激活(js 能够拜访)      arguments: []    },    this: window  }

为不便浏览,我把代码拉下来

function foo() {    const a = 2;    this.bar();}function bar() {    console.log('this.a', this.a);      console.log('a', a);}foo();
  1. 创立全局执行环境
  2. 执行 foo(),创立 foo 函数执行环境
  3. 执行 bar(),创立 bar 函数执行环境

    1. 拜访 this.a => window.a,打印 undefined
    2. 拜访 a,bar 函数执行环境中找不到,于是顺着 Outer 向上寻找到 window 的执行环境,没有 a 的申明。抛出谬误 ReferenceError: a is not defined
留神,这里3中2说顺着 Outer 向上寻找到 window 的执行环境,指的是window的作用域和执行环境曾经高度一致了。 严格来说因该是顺着 Outer 向上寻找到 window 的词法作用域*

通透!

别忘点赞!????非常感谢


转载请表明出处!

Reference

傻傻分不清的javascript运行机制 - 掘金翻译打算

[[译] 了解 JavaScript 中的执行上下文和执行栈 - 掘金翻译打算](https://juejin.im/post/5ba321...

深刻了解javascript原型和闭包(完结)- 王福朋