浏览器解析html原理视频 (js浏览器缓存机制)

作者 | Elya

转载请注明出处:https://www.toutiao.com/i6794055913318646284/

接上文浏览器原理系列 - JS执行上下文详解(一):功能域,我们提到了功能域以及ES6是如何通过变量环境和词法环境来同时支持变量提升和块级功能域,本文我们聊聊功能域链。

二、功能域链

在开始之前我们来看下面这段代码:

function bar() {
  console.log(myName);
}

function foo() {
  var myName = "Tom";

  bar();

}

var myName = "Linda";

foo();

这段代码的执行结果是打印 ​Linda​ 还是 ​Tom​ 呢?我相信等你看完本文会知道答案的。

2.1 功能域链

在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 ​outer​。

当一段代码使用了一个变量时,JavaScript 引擎首先会在“当前的执行上下文”中查找该变量。

比如上面那段代码在查找 myName 变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找。为了直观理解,你能够看下面这张图:

浏览器原理,js执行上下文功能域

从图中能够看出,bar 函数和 foo 函数的 outer 都是指向全局上下文的,这也就意味着如果在 bar 函数或者 foo 函数中使用了外部变量,那么 JavaScript 引擎会去全局执行上下文中查找。我们把这个查找的链条就称为功能域链

现在我们知道变量是通过功能域链来查找的了,不过还有一个疑问没有解开,foo 函数调用的 bar 函数,那为什么 bar 函数的外部引用是全局执行上下文,而不是 foo 函数的执行上下文?

要回答这个问题,你还需要知道什么是词法功能域。这是因为在 JavaScript 执行过程中,其功能域链是由词法功能域决定的。

2.2 词法功能域

词法功能域就是指功能域是由代码中函数声明的位置来决定的,所以词法功能域是静态的功能域,通过它就能够预测代码在执行过程中如何查找标识符。

我们看看下面这张图:

浏览器原理,js执行上下文功能域

图片来自网络

从图中能够看出,词法功能域就是根据代码的位置来决定的,其中 main 函数包含了 bar 函数,bar 函数中包含了 foo 函数,因为 JavaScript 功能域链是由词法功能域决定的,所以整个词法功能域链的顺序是:foo 函数功能域—>bar 函数功能域—>main 函数功能域—> 全局功能域

直白地说,词法功能域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。

我们已经在全局功能域和函数级功能域分析了功能域链,那接下来我们再来看看块级功能域中变量是如何查找的?

2.3 块级功能域中的变量查找

来看下面这段代码:

function bar() {

  var myName = "Firefox";

  let test1 = 100;

  if (1) {

    let myName = "Chrome";

    console.log(test);

  }

}

function foo() {

  var myName = "Safari";

  let test = 2;

  {

    let test = 3;

    bar();

  }

}

var myName = "IE";

let test = 1;

foo();

要想得出其执行结果,那接下来我们就得站在功能域链和词法环境的角度来分析下其执行过程。下图是我画的执行到 ​console.log(test)​ 时调用栈的情况:

浏览器原理,js执行上下文功能域

现在需要打印出来变量 test,那么就需要查找到 test 变量的值,其查找过程我已经在上图中使用序号 1、2、3、4、5 标记出来了。首先是在 bar 函数的执行上下文中查找,但因为 bar 函数的执行上下文中没有定义 test 变量,所以根据词法功能域的规则,下一步就在 bar 函数的外部功能域中查找,也就是全局功能域。

最后能够得出打印结果是1。