详解执行上下文、执行栈、this 指向

详解执行上下文、执行栈、this 指向

本文目录:

├── 一、什么是执行上下文 
├── 二、执行上下文的类型(3种)
│   ├── 1、全局执行上下文
│   ├── 2、函数执行上下文
│   ├── 3、Eval 全局执行上下文(不讨论)
├── 三、执行上下文的生命周期(3个阶段)
│   ├── 1、创建阶段
│   │	├── (1)、生成变量对象
│   │	├── (2)、建立作用域链
│   │	├── (3)、确定 this 指向
│   ├── 2、执行阶段
│   ├── 3、回收阶段
├── 四、执行上下文栈

一、什么是执行上下文

可能小伙伴们会觉得这个专业名词肯定夹杂着很多高深莫测的知识,但其实可以理解成通俗易懂的两句话。

  • JavaScript 中运行的任何代码都是在执行上下文中运行的。
  • 当前 JavaScript 代码被执行和解析时所在环境(抽象概念)。

是不是觉得很泛,但事实就是如此。

二、执行上下文的类型(3种)

1、全局执行上下文

不在任何函数中的代码都是全局执行上下文(巨大无比),它做两件事:

  • 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。
  • this 指针指向全局对象。一个程序中有且只有一个全局对象。

2、函数执行上下文

每个函数都拥有自己的执行上下文,只有在函数被调用的时候该执行上下文才会被创建。一个程序中可以有任意个函数执行上下文。

3、Eval 全局执行上下文(不讨论)

三、执行上下文的生命周期(3个阶段)

1、创建阶段

(1)、生成变量对象:
  • 建立 arguments 对象
  • 检测当前上下文变量声明和函数声明。变量提升,即将当前上下文环境中的变量先声明,并赋值为 undefind。待代码执行时才会有正确的赋值。当存在同名情况,函数声明的优先级会高于变量声明。
(2)、建立作用域链

在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量

(3)、确定 this 指向

this 的值是在执行的时候才能确认,定义的时候不能确认! (是不是一脸懵逼?看下下面的事例你就能明白)

// 事例一
function foo() {
  console.log(this);
}
foo();   // 这里输出的 this -> window

// 事例二
function foo() {
  console.log(this);
}
var obj = {fn: fn}
fn.foo(); // 这里输出的 this -> obj

// 事例三
function foo() {
  console.log(this);
}
var obj = {fn: fn}
var aa = obj.fn;
aa(); // 这里输出的 this -> window

// 事例四
var obj4 = {};
var fn = function () {
  console.log(this);
}
fn.call(obj4); // 这里输出的 this -> obj4

解释下上面的例子:

  • 事例一:foo() 为构造函数。
  • 事例二:foo() 作为一个对象的属性,作为一个对象的属性被调用this 指向 obj
  • 事例三:foo() 被赋值到另一个变量中,并没有作为一个对象的属性被调用,this 指向 window
  • 事例四:函数被 callapply 调用时,this 指向传入的第一个属性对象。

看到了吧,相同的代码在不同的情况下 this 取值是不同的,所以现在是不是理解 this 的值是在执行的时候才能确认,定义的时候不能确认! 这句话的含义。

2、执行阶段

变量赋值,函数引用,代码执行。

3、回收阶段

从执行上下文栈(下文会做解释)出栈,等待回收。

四、执行上下文栈

在执行 js 代码时,会有数不清的函数调用次数,会产生许多个上下文环境。这么多上下文环境该如何管理,以及如何销毁而释放内存呢?

这里就是 执行上下文栈-出栈入栈 来管理。下面列出一些关键点和代码事例来讲解,细看之后你就能明白,因为并不难。

  • 执行上下文栈可以类似于一个存储函数调用的栈结构,遵循先进后出的原则。
  • JavaScript 执行在单线程上,所有代码都是排队执行。
  • 处于活动状态的执行上下文环境只有一个,且位于栈顶。
  • 函数执行上下文执行完毕就会出栈,等待垃圾回收。

好了我们来看代码吧

1   var a  = 10;             // 1、进入全局上下文环境
2   var fn = function(d) {
3     var b = 2;
4     console.log(d + b);
5   }
6   var bar = function(x) { 
7     var c = 3;
8     fn(c + x);            // 3、进入 fn 函数上下文执行环境
9   }
10  bar(4);                 // 2、进入 bar 函数上下文执行环境

(1)、在代码执行前,首先创建全局上下文环境,声明变量,将全局执行上下文入栈,并将全局执行上下文设置为活动状态。

全局:

变量
aundefined
fnundefined
barundefined
thiswindow

(2)、在执行到第10行之前,上下文栈还是只有全局执行上下文,但上下文变量都在执行过程中被赋值。

全局:

变量
a10
fnfunction
barfunction
thiswindow

(3)、在执行到第10行时,调用 bar 函数,跳转到函数体内部,在执行函数体语句之前会创建一个新的 bar 函数执行上下文,并将此压入栈顶,设置为活动状态,全局执行上下文 就不再是活动状态了。

bar:

变量
cundefined
x4
arguments[4]
thiswindow

(4)、执行到第8行时,调用 fn 函数,跳转到函数体内部,在执行函数体语句之前会创建一个新的 fn 函数执行上下文,并将此压入栈顶,设置为活动状态,bar 函数执行上下文 就不再是活动状态了。

fn:

变量
bundefined
d7
arguments[7]
thiswindow

(5)、待 fn 函数执行完,fn 函数上下文环境出栈,并及时销毁,bar 函数上下文环境 重新回到活动状态。

(6)、待 bar 函数执行完,bar 函数上下文环境出栈,并及时销毁,全局上下文环境 重新回到活动状态。

(7)、待浏览器关闭,全局上下文环境 出栈,并销毁。

一整个理想的流程就完成了。是不是不会太难?如果遇到闭包的情况呢,要另外说明哈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值