本文共 1090 字,读完只需 4 分钟

概述

JavaScript 是函数式编程语言,作用域也是以函数为单位,那么,这些函数代码块是怎么样的顺序进行的呢, JS 的可执行代码又分为 3 种,不同类型的代码有不一样的执行环境。本文就梳理有关 JS 执行上下文(execution context),也叫执行环境的知识。

活动的执行代码的上下文在语言底层逻辑上构成一个执行上下文栈(excution context stack),我们知道,栈有两个行为,压入栈和弹出栈,并且有后进先出的特点, 最先进入的项会在栈底最后弹出。

不同执行环境的有其相应的变量对象(Variable Object),某个执行环境的所有可执行代码都执行完毕后,该环境中的变量对象和函数定义会被清除。

函数代码会在执行完后清除变量占用的内存,全局代码则会在关闭环境,比如关闭浏览器后清除。

一、代码类型

JS 中可执行的代码可分为三种类型:

  1. 全局代码
  2. 函数代码
  3. Eval 代码

全局代码

在 web 浏览器中,全局执行环境是 window 对象,所有的全局变量和函数都是作为 window 的属性和方法存在。

全局代码的执行上下文栈可以表示为:

ECStack = [
    globalContext
]

函数代码

当执行函数代码时,函数代码上下文被压入到执行上下文栈中。函数代码的执行环境中,有自己的内部的定义的变量和声明。

function foo1() {
    var name1 = "a";
    console.log(name1)
}

function foo2(){
    var name2 = "b";
    console.log(name2)
}

foo1();
foo2();

上面的执行上下文可以表示为:

ECStack = [
    globalContext
];

ECStack.push(<foo1> functionContext);
ECStack.pop();
ECStack.push(<foo2> functionContext);
ECStack.pop();

一个函数,可能有多个执行上下文,每个函数的调用都会产生新的上下文。

function foo() {
    ...
}

foo("a");
foo("b");
foo("c");

eval 代码

eval 关键字接受一个字符串作为参数,并将其作为书写文字上下文的代码。

function foo(str, a) {
    eval( str );  // 声明一个新变量
    console.log(a, b);
}
var b = 123;

foo("var b = 456 ", 123) // 123, 456

以上代码引用《你不知道的 JavaScript》的例子,eval 函数中的字符串,被当做可执行代码,最后在 foo 函数中声明了 b 变量,并覆盖了 foo 函数外部的 b 变量。

eval 函数和 JS 的词法作用域的行为,会造成变量环境和执行上下文的混乱,尽量别使用它。

二、执行上下文栈

前面其实已经提到很多关于执行上下文栈的内容,简而言之,JS 在遇到全局代码,函数代码,eval 代码时,会创建相应的执行上下文,不同的上下文,有其变量对象(variable object: VO)和作用域。

看这一段代码:

function func3() {
    console.log('fun3')
}

function func2() {
    func3();
}

function func1() {
    func2();
}

func1();

以上代码引用自冴羽的github,代码顺序用执行上下文栈来表示就是:

ECStack.push(globalContext);

// 调用 func1(), 进入 func1 执行环境
ECStack.push(<func1> functionContext);

// func1中调用了 func2,进入 func2 的执行上下文
ECStack.push(<func2> functionContext);

// func2 调用 func3, 进入 func3 的执行上下文
ECStack.push(<func3> functionContext);

// func3 执行完毕,退出 func3 执行环境
ECStack.pop();

// func2 执行完毕,退出 func2 执行环境
ECStack.pop();

// func1 执行完毕,退出 func1 执行环境
ECStack.pop();

函数代码执行完毕后,执行上下文栈底只剩下全局代码上下文 globalContext, 直到关闭浏览器才会彻底清空执行上下文栈。

总结

代码在执行时会创建由不同作用域构成的作用域链作用域链保证 JS 中的变量和函数都能够有序地访问和执行。

如果是函数代码,则将其 活动对象 作为变量对象,活动对象最开始时只包含一个对象,即 arguments 对象。

后面的文章会介绍变量对象(VO)和作用域链的具体内容,敬请期待吧。

博客内容源自个人公众号,专注分享原创文章,欢迎关注。

图片描述


种瓜南山下
305 声望18 粉丝

南瓜