深入 JS 之获取和执行 JS 代码

670 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

1.高级编程语言

JS 是一门高级编程语言,那什么是高级编程语言,什么又是低级编程语言呢?

从编程语言发展历史来说,可以划分为三个阶段:

  • 机器语言:1000100111011000,一些机器指令;
  • 汇编语言: mov ax, bx, 一些汇编指令
  • 高级语言: C, C++, Java, JavaScript, Python等;

但是计算机本身是不认识高级语言的,所以高级编程语言,最后都是需要编译成为 机器指令, 才能交由 CPU 执行

语言发展.png

那么是谁在帮助我们将 JS 代码编译成为机器指令, 然后运行呢?

其实答案大家应该都知道的, JS引擎 呗,哈哈, 其实我想说 V8 来着,大家估计也猜到是这个了,不过 V8Chrome 浏览器和我们的 Node 使用的,实际上 JS引擎 有很多种,例如

  1. SpiderMonkey: 第一款 JS引擎, 是JS 的作者: Brendan Eich 编写的
  2. Chakra:微软开发,用于IE浏览器
  3. JavaScriptCoreWebKit 中的 JavaScript 引擎,Apple 公司开发, 也是小程序用的 JS core
  4. V8Google 开发的强大 JavaScript引擎,也帮助 Chrome 从众多浏览器中脱颖而出;
  5. 等等...

好了,现在知道 js 是被谁执行的,那么来看一下,在浏览器中, js 是如何获取,并且在什么时候交给引擎执行的吧

2.在浏览器中 JS 的获取

大家都知道, JS 是在 HTMLscript 标签引入的, 浏览器的获取解析详情,详细笔记还在写

这边看下简版的

JS的获取.png

浏览器得到 HTML, 解析 DOM 节点,遇到 linkscript 分别获取不同资源,当浏览器获取到这三者之后, 就开始进行渲染和执行 JS

3.在浏览器中 JS 的执行时机

浏览器渲染机制.png

先声明,这是采用 webkit 浏览器内核的渲染机制图,不同的浏览器机制不同,不过大多概念差不多

在这个渲染机制图中, 我们的没有看到 JS 模块的执行, 这是为什么呢?

其实这里有,只是它的名字很出乎意料 DOM, 没错就是最上层的浅紫色的 DOM, 为什么呢? 其实最早 JS 在浏览器中,就是为了处理表单的,现在用于做 DOM 交互的,只是 Nodejs 赋予了 JS 更多能力, OK

知道了 JS 的执行位置,接下来说下执行时机, 什么时候执行了 JS 呢?

先说结论,

html 解析完毕,生成了 DOM tree , css 解析完毕,生成了 style rules 之后, 合并生成 render tee 的时候, 执行 JS代码

为什么会这样呢? 看下这个文章的实验,这里有代码和演示步骤,结果

关于 JS 与 CSS 是否阻塞 DOM 的渲染和解析

总结就是:

  1. CSS 不会阻塞 DOM 标签的解析,但是会阻塞 DOM 的渲染, 也就是会阻止生成 render tree 的生成, css 不执行解析完毕,就无法渲染页面
  2. JS 会阻塞 DOM 标签的解析,除非使用 deferasync 属性,标记为异步
  3. CSS 会阻塞 JS 的执行,也就是 CSS 获取完毕并解析之后, 才会执行 JS

4.在 V8 引擎中, JS 代码的执行

现在 JS 代码有了, 执行时机也确定, 那么下一步就是把 JS 代码交由 JS引擎 进行解释执行,这里以 V8 作为目标, 因为 Node 也是基于 V8 的,原理是一致的

对了,补充一下

浏览器引擎是浏览器引擎,并不是 JS的引擎, 浏览器引擎和 JS 引擎的关系, 举个例子

比如苹果的 webkit 内核

                   +----------+
                   +  WebKit  +
                   +----------+
                        |
           +-------------------------+
           |                         |
       +----------+              +----------+
       +  WebCore +              +  JsCore  +
       +----------+              +----------+

浏览器内核有两部分组成,

  • WebCore: 负责 HTML 解析、布局、渲染等等相关的工作;
  • JavaScriptCore: 解析、执行 JavaScript 代码;

这是 Webkit ,那么谷歌的 blink 呢?就是 JS 引擎部分,采用的 V8

看下 v8 的官方解析执行 js 的步骤 V8 引擎

v8.png

执行步骤如下

  1. Blink 将源码交给 V8引擎Stream 获取到源码并且进行编码转换;

  2. Scanner 会进行词法分析(lexical analysis),词法分析会将代码转换成 tokens

  3. 接下来 tokens 会被转换成 AST树,经过 ParserPreParser

    1. Parser 就是直接将 tokens 转成 AST树 架构;
    2. PreParser 称之为预解析,为什么需要预解析呢?
      • 这是因为并不是所有的 JavaScript 代码,在一开始时就会被执行。那么对所有的 JavaScript 代码进行解析,必然会影响网页的运行效率;
      • 所以 V8引擎 就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
    3. 生成 AST树 后,会被 Ignition 转成字节码(bytecode),之后的过程就是代码的执行过程(后续会详细分析)。

看完官方的介绍,来看看 coderwhy 老师讲解的版本,加上自己的理解,加深记忆和理解

v8的理解.png

ECMA 的规范定义,是需要实现一个全局对象 GlobalObject

我们先了解一个概念,叫全局对象。在 W3School 中也有介绍:

全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。

在顶层 JavaScript 代码中,可以用关键字 this 引用全局对象。因为全局对象是作用域链的头,这意味着所有非限定性的变量和函数名都会作为该对象的属性来查询。

例如,当 JavaScript 代码引用 parseInt() 函数时,它引用的是全局对象的 parseInt 属性。全局对象是作用域链的头,还意味着在顶层 JavaScript 代码中声明的所有变量都将成为全局对象的属性。

在浏览器中规范的实现,就是浏览器实现了 window, 而从这里可以看出来, JS 代码被执行前, JS引擎就会创建我们的 全局对象

  • 该对象 **所有的作用域(scope)**都可以访问;
  • 里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
  • 其中还有一个window属性指向自己;

那么先创建了全局对象, 然后进行机器指令的编译之后,就该是将我们的指令交由 CPU 执行了

执行了 JS 代码,接下来,我们就来深入学习下, JS 代码被执行的详细情况