编译原理

尽管 JavaScript 经常被归类为“动态”或“解释执行”的语言,但实际上它是一门编译语言。JavaScript 引擎进行的编译步骤和传统编译语言非常相似,但有些地方可能比预想的要复杂。

传统编译流程:

  • 分词/此法分析(Tokenizing/Lexing)

    这个过程会将有字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token)。例如:var a = 2;这段程序通常会被分解成词法单元:vara=2;空格是否会被当成词法单元,取决于空格在这门语言种是否具有意义。

  • 解析/语法分析(Parsing)

    这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。

    var a = 2的 AST 为:

    VariableDeclaration
    --Identifier = a
    --AssignmentExpression
    ----NumericLiteral = 2
  • 代码生成

    将 AST 转换为可执行代码的过程被称为代码生成。这个过程与语言、目标平台等息息相关。简单来说就是将 AST 转换为一组机器指令,用来创建一个叫做 a 的变量(包括分配内存等),并将值 2 存储在 a 中。

JavaScript 的编译

JavaScript 的编译由 JavaScript 引擎来负责(包括执行)。编译通常由三个部分组成:

  • 引擎:从头到尾负责整个 JavaScript 的编译以及执行;
  • 编译器:负责语法分析以及代码生成;
  • 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

在我们看来var a = 2;这是一个普通的变量声明。而在 JavaScript 引擎看来这里有两个完全不同的声明:

  1. var a,编译器会寻找当前作用域中是否有同样的声明。如果有,则忽略该声明,并继续编译;否则它会在当前作用域(全局/函数作用域)的集合中声明一个新的变量,并命名为 a。
  2. 接下来编译器会为引擎生成运行时所需的代码,这些代码用来处理赋值(a = 2)操作。引擎会在当前作用域中查找变量 a。如果能找到,则为其赋值;如果找不到,则继续向上查找(作用域链)。

由于编译的第一步操作会寻找所有的var关键词声明,无论它在代码的什么位置,都会声明好。在代码真正运行时,所有声明都已经声明好了,哪怕它是在其他操作的下面,都可以直接进行。这就是var关键词的声明提升。

a = 2;
console.log(a);
var a;

LHS 和 RHS

编译器在编译过程的第二步生成了代码,引擎执行它时,就会查找变量 a 来判断它是否已经声明过。但引擎如何进行查找,影响最终查找的结果。

LHS 和 RHS 分别对应的是左侧查找与右侧查找。左右两侧分别代表一个赋值操作的左侧和右侧。也就说,当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。

例如:a = 2,这里进行的就是 LHS 查询。这里不关心 a 的当前值,只想找到 a 并为其赋一个值。

而:console.log(a),这里进行的是 RHS 查询。因为这里需要取到 a 的值,而不是为其赋值。

“赋值操作的左侧和右侧”并不一定代表就是=的左右两侧,赋值操作还有其他多种形式。因此,可以在概念上理解为“查询被赋值的目标(LHS)”以及”查询目标的值(RHS)“。

小测验:

寻找 LHS 查询(3处)以及 RHS 查询(4处)。

function foo(a) {
var b = a;
return a + b;
}
var c = foo(2);

LHS:

  • var c = foo(...):为变量 c 赋值
  • foo(2):传递参数时,为形参 a 赋值 2
  • var b = a:为变量 b 赋值

RHS:

  • var c = foo(...):查询foo()
  • var b = a:(为变量 b 赋值时)取得 a 的值
  • return a + b:取得 a 与 b(两次)

异常

通过详细的了解异常可以准确的确定发生的问题所在。

在 LHS 查询时,如果到作用域顶部还没有查询到声明,则作用域会热心的帮我们(隐式)创建一个全局变量(非严格模式下)。

而在 RHS 查询时,如果在作用域顶部还没有查询到声明,就会抛出一个 ReferenceError 异常。

在严格模式下,LHS 如果没有找到声明,引擎会抛出一个和 RHS 类似的 ReferenceError 异常。

无论是 LHS 还是 RHS 都是查询一个引用,而没有查询到对应的引用时,就会得到(引用)ReferenceError 异常。

接下来,如果 RHS 查询到了一个变量,但是我们尝试对这个变量的值进行不合理的操作。例如对一个非函数进行函数调用,或者对对象中不存在的属性进行引用。那么引擎会抛出另外一个异常,叫做 TypeError。

闭包

闭包是基于词法作用域书写代码时所产生的自然结果。闭包的主要定义:

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

JavaScript 使用的是词法作用域模型,另一种作用域模型是动态作用域。

仔细来看,闭包的主要定义有:

  • 函数记住并可以访问所在的词法作用域
  • 在当前词法作用域之外执行也能继续访问所在的词法作用域

来看一个例子:

function foo() {
const a = 123; function bar() {
console.log(a);
} bar();
}
foo();

这段代码看起来好像符合闭包的一部分定义,虽然bar()函数并没有脱离当前的词法作用域执行。但是它依然记住了foo()的词法作用域,并能访问。

它确实满足闭包定义的一部分(很重要的一部分),从技术上讲,也许是,但并不能完全断定这就是闭包。通常我们所见到的与认为闭包的情况就是满足所有定义的时候:

function foo() {
const a = 321; function bar() {
console.log(a);
} return bar;
}
// 同理
// foo()();
const baz = foo()
baz();

因为垃圾收集机制,当一个函数执行结束后,通常它的整个内部作用域会被销毁。当我们的foo()函数执行结束后,看上去它的内容不会再被使用,所以很自然的考虑会被回收。

但闭包的神奇之处就在这里,它会阻止这一切的发生。当barreturn出去之后,在其词法作用域的外部依然能够访问foo()的内部作用域。bar依然持有对该作用域的引用,这个引用就叫作闭包。

这也是经常见到说闭包会影响性能的主要原因。某些情况下,它确实会影响到性能,例如过度多的返回本不需要的函数,甚至是嵌套。这会导致本不需要的作用域没有被回收。

常见的闭包

上述将一个函数return出来的案例是最常见的闭包案例。但在我们的代码中,也有些其他非常常见的闭包。不过平时可能没有太过去注意它。

先来回顾一下定义:

无论通过何种手段将内部函数传递到词法作用域之外,它都会保留对改内部词法作用域的引用,无论在何处执行这个函数都会使其闭包。

function waitAMinute(msg: string) {
setTimeout(() => {
console.log(msg);
}, 1000);
}
waitAMinute('嘤嘤嘤');
function btnClick(selector: string, msg: string) {
$(selector).click(() => {
alert(msg);
});
}
btnClick('#btn_1', 'hah');
btnClick('#btn_2', 'got you');

JavaScript-编译与闭包的更多相关文章

  1. 让你分分钟学会Javascript中的闭包

    Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...

  2. javascript作用域和闭包之我见

    javascript作用域和闭包之我见 看了<你不知道的JavaScript(上卷)>的第一部分--作用域和闭包,感受颇深,遂写一篇读书笔记加深印象.路过的大牛欢迎指点,对这方面不懂的同学 ...

  3. Javascript中的闭包(转载)

    前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...

  4. 狗日的Javascript中的闭包

    前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它.下面是作者从作用域链慢慢讲到 ...

  5. 深入理解javascript原型和闭包 (转)

    该教程绕开了javascript的一些基本的语法知识,直接讲解javascript中最难理解的两个部分,也是和其他主流面向对象语言区别最大的两个部分--原型和闭包,当然,肯定少不了原型链和作用域链.帮 ...

  6. JavaScript葵花宝典之闭包

    闭包,写过JS脚本的人对这个词一定不陌生,都说闭包是JS中最奇幻的一个知识点,  虽然在工作中,项目里经常都会用到~  但是是不是你已经真正的对它足够的了解~~ 又或者是你代码中出现的闭包,并不是你刻 ...

  7. 深入理解javascript原型和闭包系列

    从下面目录中可以看到,本系列有16篇文章,外加两篇后补的,一共18篇文章.写了半个月,从9月17号开始写的.每篇文章更新时,读者的反馈还是可以的,虽然不至于上头条,但是也算是中规中矩,有看的人,也有评 ...

  8. 深入理解javascript原型和闭包(1)——一切都是对象

    “一切都是对象”这句话的重点在于如何去理解“对象”这个概念. ——当然,也不是所有的都是对象,值类型就不是对象. 首先咱们还是先看看javascript中一个常用的函数——typeof().typeo ...

  9. 深入理解javascript原型和闭包(2)——函数和对象的关系

    上文(理解javascript原型和作用域系列(1)——一切都是对象)已经提到,函数就是对象的一种,因为通过instanceof函数可以判断. var fn = function () { }; co ...

  10. 深入理解javascript原型和闭包(3)——prototype原型

    既typeof之后的另一位老朋友! prototype也是我们的老朋友,即使不了解的人,也应该都听过它的大名.如果它还是您的新朋友,我估计您也是javascript的新朋友. 在咱们的第一节(深入理解 ...

随机推荐

  1. Django-ORM-事务和锁

    一.锁 行级锁 返回一个锁住行,值到事物结束的查询集. 所有匹配的行将被锁住,直到事物结束,这就意味着可以通过锁防止数据被其他事务修改. 一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞直到锁被 ...

  2. 『心善渊』Selenium3.0基础 — 20、Selenium对Cookie的操作

    目录 1.Cookie介绍 2.Session介绍 3.Cookie工作原理图解 4.Cookie内容参数说明 5.Selenium操作Cookie的API 6.Selenium操作Cookie的示例 ...

  3. POJ 1220 大数字的进制转换,偷下懒,用java

    题意为进制转换,Java的大数类就像是作弊 import java.math.BigInteger; import java.util.Scanner; public class Main { pub ...

  4. 怎么用git将自己的源代码提交到git服务器上

    在git服务器上新建仓库 在本地初始化本地仓库 初始化 git init 添加远程仓库地址 git remote add origin XXX.git 同步 git pull origin maste ...

  5. salesforce零基础学习(一百零五)Change Data Capture

    本篇参考: https://developer.salesforce.com/docs/atlas.en-us.232.0.api_streaming.meta/api_streaming/using ...

  6. phpstorm之"Can not run PHP Code Sniffer"

    前言 其实我是不太愿意写这种工具使用博客的,因为实在没有营养,只是有些简单问题,搜索一番,却始终找不到答案,遂以博客记录下来,希望后面的人,可以省去搜索之苦. 相信你搜到这篇博客,肯定是已经安装好了P ...

  7. 18 shell 重定向以及文件描述符

    1.对重定向的理解 2.硬件设备和文件描述符 文件描述符到底是什么 3.Linux Shell 输出重定向 4.Linux Shell 输入重定向 5.结合Linux文件描述符谈重定向 6.Shell ...

  8. 22 shell组命令与子进程

    1.组命令 2.子进程 2.1 什么是子进程 2.2 创建子进程 2.3 子进程总结 3.如何检测子shell与子进程 1.组命令 组命令,就是将多个命令划分为一组,或者看成一个整体. 用法 区别 S ...

  9. B站蹦了,关我A站什么事?

    昨天的大瓜,B站蹦了,大伙都跳起来分析了一波异常原因,着实给大伙的秋招准备了一波热乎乎的素材!在大家都在关注 B站的时候, 我大A站终于要站起来了!!!经过多方网友的极力引流,我A站也蹦了- 紧急通知 ...

  10. cke编辑器插入&ZeroWidthSpace占位字符的问题记录

    背景 本博文主要记录在使用cke编辑器时,遇到的一系列的问题 问题1:在执行某些业务操作后,编辑器会偶现在页面头部或者尾部插入&ZeroWidthSpace占位符(编辑器好像就爱干这事~) 解 ...