你不知道的JavaScript

warning

ban

你不知道的JavaScript(上)

为什么读(解决那些问题)

JavaScript语言本质上有许多复杂的概念,但是却用一种看起来比较简单的方式体现出来,比如回调函数,因此JavaScript开发者通常只是简单的使用这些特性,并不会关心语言内部的实现原理

如果每次遇到JavaScript中出乎意料的行为时,你的反应就是把他加入黑名单,那么用不了多久就会把JavaScript语言真正的多样性全部排除

作用域是什么

储存和访问变量的值的能力将状态带给了程序,这些变量储存在哪里?程序如何找到它们,这个查找规则被称为作用域

编译原理

尽管通常将js归类为动态或解释执行语言,但事实上它是一门编译语言。与传统的编译语言不同,它不是提前编译的,编译结果不能再分布式系统中进行移植

传统语言的编译原理

  • 分词/词法分析将由字符组成的字符串分解为有意义的代码块,这些代码块被称为词法单元
  • 解析/语法分析 将词法单元转换为AST
  • 代码生成 将AST转换可执行的代码

JavaScript引擎要复杂的多(也会在执行前进行编译),不会有大量的时间来进行优化,因为JS编译过程不是发生在构建之前,而是发生在代码执行之前的几微秒

理解作用域

  • 引擎 从头到尾负责整个js程序的编译执行过程
  • 编译器 引擎的好朋友之一,负责语法分析代码生成等脏活累活
  • 作用域 引擎的另一位好朋友,负责收集维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限

var a = 2 解析过程

  • 编译器分解成词法单元
  • 词法单元解析成一个树结构
  • 编译器进行代码生成
    • var a 会询问作用域是否存在a变量,如果存在忽略该声明,继续进行编译,否在会要求作用域声明变量a
    • 编译器为引擎生成运行时所需代码,这些代码被用来处理 a = 2 这个赋值操作。引擎会问作用域是否存在a变量,存在的话使用,不存在,继续查找

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值

引擎如何在作用域中查找变量

变量出现在赋值操作左侧时进行 LHS查询,出现在右侧时进行RHS查询

RHS 查询与简单地查找某个变量的值并无二致,而 LHS 查询则是试图找到变量的容器本身,从而可以对其赋值。从这个角度说,RHS并不是真正意义上的‘赋值操作的左侧’,更准确的说是‘非右侧’

LHS 和 RHS的含义是”赋值操作的左侧或右侧“并不一定意味着就是 ”=赋值操作符的左侧或右侧“。赋值操作还有其他几种形式,因此在概念上最好讲其理解为 赋值操作的目标是谁(LHS),已经谁是赋值操作的源头(RHS)

补充问题

什么是js引擎

JavaScript引擎是一种解释和执行JavaScript代码的软件组件或程序。它是浏览器或其他JavaScript运行环境中的重要组成部分。JavaScript是一种脚本语言,最初设计用于在网页上添加交互性。JavaScript引擎负责解释和执行JavaScript代码,使其能够在用户的浏览器中运行。

不同的浏览器使用不同的JavaScript引擎。以下是一些主要的JavaScript引擎:

  • V8引擎: 由Google开发,最初用于Google Chrome浏览器,后来也被用于Node.js等环境。

  • SpiderMonkey: Mozilla Firefox浏览器使用的JavaScript引擎,由Mozilla开发。

  • Chakra引擎: 由Microsoft开发,最初用于Internet Explorer浏览器,后来被Edge浏览器采用。

  • JavaScriptCore(Nitro): Safari浏览器的JavaScript引擎,由WebKit项目提供。

  • Rhino: 一个由Java编写的JavaScript引擎,通常用于Java应用程序中。

这些引擎负责将JavaScript代码转换为机器代码或解释执行,以便在用户的设备上运行。随着JavaScript的发展,引擎不断优化以提高执行性能和支持新的语言特性。

什么是js编译器

JavaScript 是一种解释型语言,通常是通过浏览器中的 JavaScript 引擎逐行解释执行的。然而,随着 JavaScript 的发展,一些工具和技术已经出现,使得 JavaScript 也可以使用编译器进行一些优化。

在 JavaScript 中,有一种被称为“即时编译”(Just-In-Time Compilation,JIT Compilation)的技术,这是一种在运行时将 JavaScript 代码编译成机器码或其他中间代码的方法。这种技术使得 JavaScript 的执行速度得到提升,因为机器码的执行通常比解释器执行更为高效。

主要的 JavaScript 引擎,比如 V8(用于 Chrome 和 Node.js)、SpiderMonkey(用于 Firefox)、Chakra(用于 Edge)等,都使用了 JIT 编译技术。以下是 JIT 编译的基本工作原理:

  • 解释执行(Interpretation): 初始阶段,JavaScript 引擎会解释执行源代码。

  • 即时编译(JIT Compilation): 引擎会监测代码的执行情况,识别经常执行的代码段(热点代码),并将其编译成机器码。

  • 优化: JIT 编译器通常会应用一些优化技术,例如内联缓存、去除冗余代码等,以提高执行效率。

  • 执行机器码: 一旦代码被成功编译成机器码,后续的执行就会直接使用这些机器码,而不是再次解释源代码。

这种 JIT 编译的方式使得 JavaScript 在运行时能够获得接近原生代码的性能,同时保留了 JavaScript 动态性的特点。这也是为什么现代 JavaScript 引擎能够在性能上取得显著的提升的原因之一。

其他语言的编译器

编译器是一种将高级编程语言代码翻译为机器码或其他中间代码的工具。与JavaScript引擎不同,编译器通常在代码执行之前将整个程序编译为机器码,而不是逐行解释执行。这样的编译过程使得执行速度更快,因为代码已经被转换为计算机能够直接理解和执行的形式。

在不同的编程语言中,有各种不同类型的编译器。以下是一些常见的编译器类型:

  • 前端编译器: 将高级编程语言代码翻译为中间代码,而不生成最终的可执行文件。这些中间代码通常由解释器或后端编译器进一步处理。

  • 即时编译器(Just-In-Time Compiler,JIT): 在程序执行时将字节码或中间代码编译为机器码。JavaScript引擎中的V8就包含了JIT编译器。它在运行时动态地将JavaScript代码编译为机器码,以提高执行性能。

  • 静态编译器: 在运行之前将源代码完全编译为机器码。C和C++等语言通常使用静态编译器,生成可执行文件。

  • 解释器: 逐行解释执行源代码,而不事先生成机器码。很多脚本语言(如Python、Ruby)使用解释器。

编译器的目标是将源代码转换为能够在计算机上直接执行的形式,以提高程序的性能和效率。与JavaScript引擎不同,编译器通常在程序执行之前完成整个编译过程。