JS 核心之执行上下文详细解释

执行上下文

基本概念:

**执行上下文:**一个函数运行之前,创建的一块内存空间,空间中包含有该函数执行所需要的数据,为该函数执行提供支持。这块内存空间与包含的数据就是执行上下文(可以理解为一个对象)。

**执行上下文栈:**call stack,所有执行上下文组成的内存空间。

函数执行过程: JS 引擎每次执行栈顶的执行上下文。

全局执行上下文: 可以理解为所有代码的入口函数(main)所形成的执行上下文。

函数执行与入栈过程

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>

  <body>
    <script>
      console.log('global 1');

      function A() {
        console.log('A1');

        function B() {
          console.log('B');
        }

        B();
        console.log('A2');
      }
      A();
      console.log('global 2');
    </script>
  </body>
</html>

输出结果:

js 复制代码
global 1
A1
B
A2
global 2

执行上下文栈的变化过程:

注意点:

  1. 调用函数时会创建该函数的执行上下文(入栈)
  2. 函数调用完毕会销毁该函数的执行上下文(出栈)
  3. JS 引擎每次执行栈顶的执行上下文。

执行上下文包含的内容:

基础概念:

VO: variable object 中记录了该环境中所有声明的参数、变量和函数

GO: global object 全局执行上下文中的VO

AO: active object 当前正在运行的执行上下文中的VO

每次执行函数时,会创建一个新的执行上下文,执行上下文包含两部分内容,this 与 VO

this

this 的三种指向:

  1. 直接调用函数,this 指向全局对象
  2. 在函数外,this 指向全局对象
  3. 通过对象调用或 new 一个函数,this 指向调用的对象或新对象
vo

VO 是一个对象,记录该环境中所有声明的参数、变量和函数

函数执行过程与执行上下文栈的详细构造过程

注意点:

  1. 全局执行上下文在程序开始时默认初始化在栈底
  2. 函数调用时创建新的执行上下文
  3. 创建新的执行上下文时要确定执行上下文中的内容

将用以下实例代码说明执行上下文栈的变化过程:

html 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>

  <body>
    <script>
      console.log(this.name);
      var a = 2;
      function A() {
        this.abc = 123;

        function B() {
          console.log(this);
        }
        B();
      }
      var a = new A();

      console.log(a.abc);
    </script>
  </body>
</html>

第一步:初始化执行上下文栈与全局执行上下文,并确定全局执行上下文中的 this 与 VO 的值

初始化第一阶段:

运行至 12 行:this 指向 window,window.name 是浏览器自带的属性它的默认值就是空字符串 "",用来存储窗口名称(用于跨页面通信、iframe 标识)

运行至 13 行:a 的值变为 2

运行至 22 行,有新函数运行,创建新的执行上下文,进入第二步:

第二步:初始化函数 A 的执行上下文,确定其 this 与 VO

运行至 15 行:this 指向新创建的对象 a,将其中的 abc 属性赋值为 123

运行至 20 行,有新函数执行,创建新的执行上下文,进入第三步:

第三步:初始化函数 B 的执行上下文,确定其 this 与 VO

运行至 18 行,输出 window。

B()执行完毕,其上下文出栈,B()执行完毕,A()也执行完毕,至此 22 行 new A()执行完毕,给 a 赋值并将 A()上下文出栈

执行 24 行:a.abc 的值为 123,至此,全部运行结束。

VO 中的值确定的过程:

  1. 确定所有形参值以及特殊变量arguments
  2. 确定函数中通过var声明的变量,将它们的值设置为undefined,如果VO中已有该名称,则直接忽略(因为已经是 undefined 了)。
  3. 确定函数中通过字面量声明的函数(function 的方式,不是变量的方式),将它们的值设置为指向函数对象(函数本身就是个对象),如果VO中已存在该名称,则覆盖。

这里也牵扯到变量与函数的声明提升的问题:

  1. var 声明的变量会有声明提升,在 VO 创建时会初始化为 undefined。

  2. 函数也会有声明提升,也是在 VO 创建时会初始化。

这里也牵扯到变量重新声明的问题 :

var 可以重复声明同一变量,因为 VO 已有该名称,VO 初始化时会忽略后面的声明

js 复制代码
function test() {
  console.log(a); // VO初始化后 a 是 undefined
  var a = 100; // 按顺序执行,a 被赋值为 100
  console.log(a);
  var a = 200; // 按顺序执行,a 被赋值为 200
  console.log(a);
}

test(); // undefined 100 200

这里也牵扯到函数重新声明的问题:

函数重新声明,会被覆盖,因为 VO 中将这个函数指向了另一个函数对象。

js 复制代码
function test() {
  console.log(a);
  var a = 100;
  console.log(a);
  var a = 200;
  console.log(a);
}  
// 上方的 test 会被覆盖
function test() {
  console.log(a);
  var a = 300;
  console.log(a);
  var a = 400;
  console.log(a);
}

test(); // undefined 300 400

引出作用域链:

当一个执行上下文中的代码执行的时候,如果执行上下文中不存在某个属性,则会从之前的上下文寻找(往栈底找,直到找到全局上下文中,若全局上下文中都没有则报错)

题目:

js 复制代码
var foo = 1;
function bar() {
  console.log(foo);
  if (!foo) {
    var foo = 10;
  }
  console.log(foo);
}

bar();

结果:undefined 10

注意点:

var 没有块级作用域,故 bar()的 AO 中也包含 foo,初始化为 undefined

执行到第 4 行时,if 条件为 true,foo 被赋值为 10,故 7 行输出 10

若 5 行改为 let 则连续输出两次 1

题目:

js 复制代码
var a = 1;
function b() {
    console.log(a); 
    a = 10;
    return;
    function a() { }
}
b();
console.log(a); 

结果:function 1

注意点:

函数声明会被提前,故 b 函数中的 a 初始为 undefined,再后来是 function,再后来是 10

9 行的 a 是全局执行上下文中的 a

题目:

js 复制代码
var foo = 1;

function bar(a) {
  var a1 = a;
  var a = foo;
  function a() {
    console.log(a);
  }
  a1();
}

bar(3);

结果:1

注意 点:

严格按照赋值顺序来,优先级分别是,变量赋值 undefined,形参赋值,同名变量忽略,同名函数覆盖,然后在按照顺序执行代码。

相关推荐
donecoding1 小时前
Monorepo 里有 app 也有共享包,lerna 真的还需要吗?
前端·node.js·前端工程化
非凡ghost1 小时前
视频下载神器:直播回放、视频链接一键抓取,还能自动监听!
java·前端·javascript·音视频
用户游民1 小时前
Flutter GetX实现原理
前端·flutter
ZC跨境爬虫1 小时前
跟着 MDN 学 HTML day_25:(数字音频概念完全解析)
前端·ui·html·edge浏览器·媒体
镜宇秋霖丶2 小时前
常驻大哥24分法,记得看
前端·javascript·vue.js
心连欣2 小时前
跨越时代的对话:Vue 2 与 Vue 3 的终极对决与环境搭建指南
前端·javascript·vue.js
HYCS2 小时前
用pixijs实现fabricjs(一):FakeCanvasRenderingContext2D
javascript·webgl·canvas
Csvn2 小时前
Vue Router 实战
前端·vue.js
yqcoder2 小时前
JavaScript 内存揭秘:堆(Heap) vs 栈(Stack)
开发语言·javascript·ecmascript