了解js的运行机制有助于我们在日常的工作中,写成高质量的代码,减少bug的产生,节约维护成本。也有助于我们通过造火箭的面试。了解JavaScript引擎。通过运行机制看作用域和作用域链。通过运行机制理解this的绑定和优先级。通过运行机制理解闭包。 二、渲染引擎 | JavaScript引擎(JavaScript Engine) 了解运行机制之前,我们先来搞清楚几个基本概念。 2.1 渲染引擎 渲染是根据描述或者定义构建一个数据模型,生成图形的过程。渲染引擎将页面资源(html、css、javaScript等)构建成可视化、可听化的多媒体结果。也就是我们看到的浏览器网页呈现。 2.2 JavaScript引擎 当我们在运行一段代码时,真正赋予这段代码生命的就是JavaScript引擎。JavaScript引擎是一个专门处理JavaScript脚本的虚拟机,一般会附带在网页浏览器中。JavaScript引擎从头到尾负责整个JavaScript程序的编译和执行过程。 2.2.1 JavaScript引擎有许多种 最为大家熟知的无疑是V8引擎,他用于Chrome浏览器和Node中。V8?—?开源,由 Google 开发,用 C ++ 编写。Rhino?—?由 Mozilla 基金会管理,开源,完全用 Java 开发。SpiderMonkey?—?是第一个支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用。JavaScriptCore —?开源,以Nitro形式销售,由苹果为Safari开发。KJS?—?KDE 的引擎,最初由 Harri Porten 为 KDE 项目中的 Konqueror 网页浏览器开发。Chakra (JScript9)?—?Internet Explorer。Chakra (JavaScript)?—?Microsoft Edge。Nashorn, 作为 OpenJDK 的一部分,由 Oracle Java 语言和工具组编写。JerryScript?—? 物联网的轻量级引擎。 2.3 渲染引擎和JavaScript引擎的关系 渲染引擎通过调用接口来处理JavaScript的逻辑。JavaScript通过桥接接口来访问渲染引擎的DOM等元素。 三、JavaScript运行时(JavaScript Runtime) 如果想让一段JavaScript代码真正的运气起来,单单靠JavaScript引擎是不够的,JavaScript Engine的工作是编译并执行 JavaScript 代码,完成内存分配、垃圾回收等,但是缺乏与外部交互的能力。 比如单靠一个V8引擎是无法进行ajax请求、设置定时器、响应事件等操作的,这就需要JavaScript运行时(JavaScript Runtime)的帮助,它为 JavaScript 提供一些对象或机制,使它能够与外界交互。 比如,虽然Chrome和node都是用了V8引擎,但是他们的运行时却不同,比如process、fs浏览器都无法提供。 一段javaScript代码的运行我们可以分为两个阶段。 四、JavaScript运行的两个阶段 4.1 编译阶段分词/词法分析解析/语法分析预编译(代码生成、解释阶段) 4.2 执行阶段JavaScript并非是简单的一行一行解释执行代码,而是将JavaScript划分为一块一块的可以执行代码块进行执行。JavaScript中代码块又分为三种。 4.2.1 代码块全局可以执行代码。函数可执行代码。Eval可执行代码。 接下里我们主要说说,JavaScript的执行阶段。 五、JavaScript执行 JavaScript既是编译语言,又是解释语言。JavaScript引擎实际上在执行代码前仅几微秒就编译了代码。 称为JIT(及时编译)。它本身是一个很大的话题。但是现在,我们可以跳过编译背后的理论,而只关注执行阶段,这仍然很有趣。 JavaScript引擎,编译和解释我们的JavaScript代码。JavaScript引擎其实也包含了很多较小的部分,这些较小的部分,分工合作来保证JavaScript的运行。全局内存(Global Memory)调用堆栈(Call Stack)执行上下文等等其他组件 5.1 全局内存(Global Memory) 先看一段代码 var num = 2; function pow(num) { return num * num; } 复制代码 看到这段代码,大家思考一下会发生什么。可能大家已经想到JavaScript引擎,在执行到第一行代码时就立刻讲引用放入全局内存(Global Memory)。全局内存是JavaScript引擎保存变量和什么函数的地方。当引擎读取以上代码时,全局内存将填充两个绑定: 上面的代码不会执行,接下来我们尝试执行函数。 5.2 调用栈(Call Stack) var num = 2; function pow(num) { return num * num; } pow(num); 复制代码 当我们执行函数的时,JavaScript引擎会用到调用堆栈(Call Stack)。调用堆栈是一个堆栈类的数据结构,意味着它是先进后出的执行方式。如果是多个函数,将依次进栈,先进后出。 打开浏览器控制台,然后查看“来源”标签。您将看到一些框,其中一个更有趣的名称是Call Stack。 当代码块在执行时,JavaScript引擎会创建一个执行上下文,已作为代码运行的基础运行环境。 六、执行上下文 在"4.2.1代码块",有三种代码块,分别对应三种执行上下文全局可以执行代码 => 全局执行上下文。函数可执行代码 => 函数执行上下文。Eval可执行代码 => Eval执行上下文。 6.1 全局执行上下文 基础执行上下文,一个程序只有一个全局执行上下文,任何不在函数内部的代码都在全局执行执行上下文。全局执行上下文只要做两件事情:创建一个全局的 window 对象(浏览器的情况下)。置 this 的值等于这个全局对象。 6.2 函数执行上下文 如果我们的函数有一些嵌套变量或一个或多个内部函数怎么办? var num = 2; function pow(num) { var a = 1, b = 2, c = 3; function add(a, b, c) { return a + b + c; } } 复制代码 每当一个函数被调用时,都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建。 6.3 Eval执行上下文 执行在 eval 内部的代码也会有它属于自己的执行上下文,请不要、不要、不要轻易使用它。 执行上下文也分为创建和执行阶段。在创建阶段就非常有意思了。 七、执行上下文的创建 执行上下文的创建阶段主要做了三件事:决定this的绑定。创建词法环境。创建变量环境。 7.1 this绑定 在创建可执行上下文的时候,根据代码的执行条件,来判断分别进行默认绑定、隐式绑定、显示绑定等。 7.1.1 this绑定分类普通函数的调用:this指向window(浏览器环境)。对象方法的调用:this指向调用对象。(隐式绑定)构造函数:this指向构造函数实例。apply、call、bind:this指向绑定值。(显示绑定)箭头函数this:this指向外层第一个普通函数调用的this。(默认绑定) 7.1.2 this绑定优先级 this绑定也是有优先级的,优先级规则如下:函数是否存在new绑定调用:如果是的话this绑定到新创建的对象上。函数是否通过apply、call、bind显示绑定:如果是,this绑定到指定对象上。函数是否在对象方法隐式调用:如果是的话,this绑定到调用对象。如果上面三条都不满足的话:在严格模型下,this绑定到undefined,在非严格模式下,this绑定到全局对象上。 7.2 词法环境 词法环境是JavaScript引擎内部用来跟踪标识符和特定变量之间的映射关系。词法环境是Js作用域的实现机制。如果之前了解过作用域概念的话,和词法环境是类似的(ES6之后作用域概念变为词法环境概念)。 作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。 ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6的到来,为我们提供了‘块级作用域’,可通过新增命令let和const来体现。 7.2.1 词法环境分类全局环境:全局环境的外部环境引用是 null,它拥有内建的对象 Object/Array/等、环境记录器内的原型函数、定义的全局变量。模块环境:模块环境的外部环境引用是全局环境(window,浏览器环境),它拥有模块顶级声明的绑定、模块显式导入的绑定。函数环境:函数环境外部引用可以是其他函数环境,也可以是全局环境。它拥有声明变量和函数。 7.2.2 词法环境组成外部环境的引用(outer Lexical Environment):指它可以访问其父级词法环境(即作用域)。环境记录器 (Environment Record):存储变量和函数声明的实际位置。(声明式环境记录器,对象式环境记录器是两个比较主要环境记录器)。 词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,这些引用串联起来一直指向全局的词法环境,因此形成了作用域链。 词法环境中含有外部词法环境的引用,我们可以通过这个引用获取外部词法环境的变量、声明等,因此形成了闭包。 7.3 变量环境 查看大量资料都没有详细的记录变量环境。 ES5标准文档中规定,执行环境包括:词法环境、变量环境、this绑定。其中执行环境的词法环境和变量环境组件始终为词法环境对象。当创建一个执行环境时,其词法环境组件和变量环境组件最初是同一个值。在该执行环境相关联的代码的执行过程中,变量环境组件永远不变,而词法环境组件有可能改变。 变量环境的不变和词法环境的可能改变都是指引用的改变,规范12.10和12.14两部分的内容提到了词法环境在with以及catch语句块中会改变。 八、JavaScript的执行过程总结 九、预览整体过程,本文只是讲了一下部分 JavaScript既是编译语言,又是解释语言。但是JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一边执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度。
|