以 V8 的角度看 JS
很长一段时间我都在尝试构筑一套足以自洽的 JS
世界观,试图找到一些更为底层的东西串联整个 JS
,一如“分子”之于物理、“元素”之于化学、“细胞”之于生物。
但遗憾的是,遍观众多 JS
书籍,于我所求所言甚少;翻过万千网上博文,同样大多语焉不详。 而试图翻阅 V8
源码又能力不足无疾而终,在这条探索的路上一直显得迟钝且力不从心。
好在,走过一些弯路经过一些兜转,终于能画出大体模型。 于心甚慰。 不求概念最为精准,胜在足以自洽。
浏览器如何执行 js 代码
一段 JS
代码的执行,需要“翻译”成机器能够识别、直接执行的字节码。
在这期间,需要通过 词法分析 将其转成一段段 token,再经过语法分析转成 AST(抽象语法树)。 再经过语义分析转成字节码,之后便能被机器识别直接执行。 由 JS
代码到字节码这段过程称之为『编译』。 而执行上下文的产生就是在 AST 生成之后。
由此连接到执行上下文与作用域
作用域
基于词法作用域模型,说白了就是 一套规则,规定变量和函数的可执行范围。有两个重要特点
- 由代码书写位置决定。
- 内层函数总能访问到外层函数中的变量。
作用域分为三类:
- 全局作用域
- 函数作用域
- 块作用域: let 或 const 加上
{}
构成的代码块。 其声明的变量在执行过程中存储在执行上下文的词法环境中。 ES6 的块作用域解决了**『变量提升』**的弊端。
由此连接到执行上下文。
执行上下文:
ES5 中,由 this
(值为调用当前上下文的对象的引用。) + 词法环境 + 语法环境组成。
- 语法环境:var 声明的变量与函数存储其中, 加上外部的引用 outer
- 词法环境:与语法环境并无二致,唯一区别是其中存储以 let const 等声明的变量。
let 与 const 是 ES6 的东西,ES5 的执行上下文为什么会定义 词法环境? 不知道, 找不到答案。
由此连接到块级作用域变量的存储。
作用域与执行上下文区别:
函数执行上下文是在调用函数时, 函数体代码执行之前创建,函数调用结束时就会自动释放。因为不同的调用可能有不同的参数: 而 JavaScript 采用的是词法作用域,fn 函数创建的作用域在函数定义时就已经确定了
简言之:
- 作用域是一套规则,包含可使用的变量。
- 执行上下文是具体的 key 与 value。
作用域链:
作用域层层嵌套,形成的关系叫做作用域链,作用域链也就是查找变量的过程。
查找变量的过程:当前作用域 => 上一级作用域 => 上一级作用域 .... => 直到找到全局作用域 => 还没有,报错。
执行上下文栈 || 调用栈:
以栈 (先进后出) 的形式管理执行上下文。
执行过程中,全局执行上下文被压入栈底。执行完成,则函数执行上下文退出。 如果出现无限递归时,则会出现爆栈。 解决方案可以是尾调用或尾递归。
二者某种程度上都是执行外部函数后,当前外部函数被推出执行栈。注意,闭包的概念可以联系至此。 闭包返回了一个函数,算是尾调用
执行上下文栈由 JS 引擎线程维护,从而联系到消息队列、实现循环以及其他线程乃至进程乃至各进程之间的协作。
闭包:
因词法作用域“内层函数总能访问到外层函数中的变量。”的规则产生。 当外部函数执行完成并 return 了一个内部函数,当前外部函数本应推出执行栈,但由于内部函数中存在对外部函数变量的引用,故调用栈就出现了内部函数总是背负着的对外部函数变量引用的“背包”,这背包,就是闭包。可联系到柯里化、模块化方案。
this:
在执行过程中确定,每个执行上下文都绑定一个 this
。总而言之,谁调用当前上下文,则 this
指向谁。 箭头函数的 this
指向最近的非箭头函数的 this
。
视觉上移,稍微宏观一些。
V8
如何借助消息队列和事件循环处理统筹任务。宏任务与微任务,以及微任务的出现解决了什么问题。Javascript
是一门单线程、异步、非阻塞、解析类型脚本语言。 何为单线程、异步、非阻塞、解析类型? 之后我会逐一缕清
骨架大体如是。之后再慢慢结合细节填充。
后续文章主要参考 浏览器工作原理与实践