JavaScript 编译器,引擎,作用域

前言

提起JavaScript ,大家第一反应:脚本语言、解释性执行等,和java、 C这种编译性语言搭不上边。然而,事实上它确实是一门编译语言。只是区别在于JS并不会像其他的编译语言一样进行提前编译,他的编译过程(通常)是在实际执行前进行的,而且也不会产生可移植的编译结果。

通常的编译过程,会做以下几个步骤:首先是分词与词法分析,把输入的字符串分解为一些对编程语言有意义的代码块(词法单元)。第二步解析与语法分析,这一步的操作高级了许多,会将上一步的词法单元集合分析并最终转换为一个由元素逐级嵌套所组成的代表了程序语法结构的树,称为 抽象语法树(Abstract Syntax Tree,AST)。第三步代码生成就是将上一步的AST转换为可执行代码。JavaScript引擎中的编译器做的事情与这个类似,但是因为JS引擎的编译过程就在代码执行前,对于“用户”来说是完全透明的。并且无法事先执行编译生成静态文件,因此JS的编译执行效率就要比一般静态语言敏感的多,故而也非常复杂。JS引擎在这一部分做了非常多的优化,一是针对语法分析和代码生成阶段进行优化(例如针对冗余元素进行优化等),目的是提高编译后的执行效率。二是针对编译过程进行优化(如JIT,延迟编译甚至重编译),目的是缩短编译过程,保证性能最佳。

介绍

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

执行

下面我们以一个最简单的例子来进行分析:

var a = 2
  1. 编译器出马,先进行词法分析,将该赋值操作拆分: var a;a=2;。第一步 var a,编译器可以处理,他会先询问变量管家:作用域,是否存在一个该名称的变量?若存在,继续编译;若不存在,通知作用域声明一个新变量,命名为a。
  2. 编译器继续为引擎进行代码生成,这些代码主要用来处理 a=2这个赋值操作。
  3. 引擎拿到可执行代码,然后询问作用域:当前有没有一个叫a的变量啊? 如果有:使用这个变量,赋值给他;如果没有就继续往上级作用域查找,如果到根作用域仍然找不到,引擎直接报错抛异常。

这儿引入个关于变量查找的概念:

  • LHS:赋值操作的左侧,试图查找到变量的容器本身,从而可以对其赋值,即找到复制操作的目标。
  • RHS:另外一种查找,可以简单理解为复制操作的右侧,其查找目标为取到目标的源值,即找到这个变量具体的值而非容器。
var a; // LHS 寻找a,未找到,通知作用域声明一个新变量,命名为a
a = 2; // LHS 找到a并给其赋值2
console.log(a); // RHS找到a的值2,并将其输出

有了上面的基础知识,我们把三兄弟的合作再细化一下,例子也升级一下,用上面赋值并输出的例子。

  1. 编译器:作用域,我需要对a进行LHS查找,你见过么?
  2. 作用域:我这找到根都没看到啊,要不咱声明一个吧!
  3. 编译器:好,建好了,那我生成代码了,引擎,给你你要的代码。
  4. 引擎:收到,咦,需要一个a啊,作用域,帮我LHS找一下有没有?
  5. 作用域: 找到了,编译器已经帮忙声明了。
  6. 引擎:好的,那我对它赋值。
  7. 引擎:作用域,不要意思,我碰到一个console,需要RHS引用
  8. 作用域: 找到了,是个内置对象,拿走不谢。
  9. 引擎: 好的作用域,对了能在帮我确认一下a的RHS么?
  10. 作用域:确认好了,没变,拿去用吧,他的值是2
  11. 引擎:好咧,我把2传递给log(..)

疑问:为什么要这么啰嗦的区分LHS和RHS?其实细心的话,你应该已经发现了,这两种查找有一个很重要的区别,即在变量未找到的时候的行为不同:

  • RHS未找到:引擎会抛出错误RefrenceError
  • LHS未找到:引擎(或引擎中的编译器)会帮你在顶层作用域声明一个具有该名称的变量。(严格模式除外)

其他

  • 词法作用域 :介绍作用域时,我们有讲过其根据一套规则来管理变量的查找与引用,词法作用域就是js使用的规则,在编译器进行词法化时,其会根据你写代码时将变量和块作用域写在哪里,来决定规则的内容。这其中又包含了块作用域这个概念,不展开讲,只要记住ES6之前没有块作用域,只有函数作用于,即:函数内部是一个独立的块作用域。(有个特例:catch语句块内也是独立的作用域)

  • 变量提升: 明白了编译器和引擎执行之间的分工,其实你应该就不会觉得变量提升是如此之诡异了,因为引擎拿到代码的时候,编译器已经做了一些转换(引擎旁白:这尼玛真怪不得我啊/(ㄒoㄒ)/~)。编译器干嘛要干这个事情?因为他要在第一步就找到所有的声明,并且用合适的作用域将他们关联起来,这也正是词法作用域的核心。表现为: 包括变量和函数在内的所有声明都会在当前块作用域内被首先处理,即类似于提升到最前面声明,但是复制处理操作因为是在执行阶段,因此编译阶段他们原地待命等待执行 。不如留两个练习?

//第一个练习:
a = 2
var a;
console.log(a);

//第二个练习:
console.log(a);
var a = 2;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容