从输入URL到页面加载的过程(9) - js 解析过程

Posted by cody1991 on July 24, 2020

from 从输入 URL 到页面加载的过程?如何由一道题完善自己的前端知识体系!

作为进阶回头再来看看:从输入 URL 到页面加载完成的过程中都发生了什么事情?

js 解析过程

js 的解释阶段

js 是解释性语言,不需要提前编译,由解释器实时运行

引擎对 js 的处理过程简单如下:

  • 读取代码,进行词法分析 Lexical analysis,然后把代码分解成词元 token
  • 对词元进行语法分析 parsing,把代码整理成语法树 syntax tree
  • 使用翻译器 translator 把代码转为字节码 bytecode
  • 使用字节码解释器 bytecode interpreter 把字节码转成机器码

为了提升运行速度,现在浏览器一般使用即时编译 JIT-Just In Time compiler,即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存起来 inline cache,这样整个运行速度就快起来了

浏览器策略也不同,有些浏览器省略了字节码的过程,直接转为机器码 (chrome 的 v8)

总结起来,核心的 jit 编译器将源码编译成机器码运行

js 预处理阶段

正式处理 js 前还有个预处理的阶段,比如变量提升,分号补全

预处理会做一些事情保证后续正常运行,下面举一些例子

分号补全

js 运行是需要分号的,但是我们经常会写没有分号的语句,也能正常运行

因为 js 解释器有一个 Semicolon Insertion 规则,按照规则补全分号

比如有下面几个规则

  • 当有换行符(包括含有换行符的多行注释),并且下一个 token 没法跟前面的语法匹配时,会自动补分号。
  • 当有 } 时,如果缺少分号,会补分号。
  • 程序源代码结束时,如果缺少分号,会补分号。

有个经典例子

1
2
3
4
5
6
function b() {
  return;
  {
    a: "a";
  }
}

补全成了

1
2
3
4
5
6
function b() {
  return;
  {
    a: "a";
  }
}

运行后就是 undefined 出错了

变量提升

包含变量和函数的提升

1
2
3
4
5
6
a = 1;
b();
function b() {
  console.log("b");
}
var a;

变量提升以后就是

1
2
3
4
5
6
function b() {
  console.log("b");
}
var a;
a = 1;
b();

js 执行阶段

整个执行阶段大概包括下面几个概念

  • 执行上下文,执行堆栈概念(如全局上下文,当前活动上下文)
  • VO(变量对象)和 AO(活动对象)
  • 作用域链
  • this 机制等

执行上下文解释

  • JS 有执行上下文
  • 浏览器首次载入脚本,它将创建全局执行上下文,并压入执行栈栈顶(不可被弹出)
  • 然后每进入其它作用域就创建对应的执行上下文并把它压入执行栈的顶部
  • 一旦对应的上下文执行完毕,就从栈顶弹出,并将上下文控制权交给当前的栈。
  • 这样依次执行(最终都会回到全局执行上下文)

譬如,如果程序执行完毕,被弹出执行栈,然后又没有被引用(没有形成闭包),那么这个函数中用到的内存就会被垃圾处理器自动回收

执行上下文与 VO,作用域链,this 的关系是:每一个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

执行上下文

VO 和 AO

VO 是执行上下文的属性(抽象概念),但是只有全局上下文的变量对象允许通过 VO 的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象)

AO(activation object),当函数被调用者激活,AO 就被创建了

可以理解为:

  • 在函数上下文中:VO === AO
  • 在全局上下文中:VO === this === global

总的来说,VO 中会存放一些变量信息(如声明的变量,函数,arguments 参数等等)

作用域链

它是执行上下文中的一个属性,原理和原型链很相似,作用很重要。

  1. 在函数上下文中,查找一个变量 foo
  2. 如果函数的 VO 中找到了,就直接使用
  3. 否则去它的父级作用域链中(parent)找
  4. 如果父级中没找到,继续往上找
  5. 直到全局上下文中也没找到就报错

作用域链

this 指针

this 是执行上下文环境的一个属性,而不是某个变量对象的属性

  • this 是没有一个类似搜寻变量的过程
  • 当代码中使用了 this,这个 this 的值就直接从执行的上下文中获取了,而不会从作用域链中搜寻
  • this 的值只取决中进入上下文时的情况

看下下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var baz = 200;
var bar = {
  baz: 100,
  foo: function () {
    console.log(this.baz);
  },
};
var foo = bar.foo;

// 进入环境:global
foo(); // 200,严格模式中会报错,Cannot read property 'baz' of undefined

// 进入环境:global bar
bar.foo(); // 100