編譯,顧名思義,就是源代碼執行前會經歷的過程,分三個步驟,javascript
- 分詞/詞法分析,將咱們寫的代碼字符串分解成多個詞法單元
- 解析/語法分析,將詞法單元集合生成抽象語法樹(AST)
- 代碼生成,抽象語法樹(AST)轉換成可執行代碼的過程
Tip1
:js在語法分析和代碼生成階段有對運行性能進行優化,對冗餘元素進行優化java
Tip2
:js的編譯過程不是發生在構建以前,而是代碼執行以前閉包
理解做用域,首先知道三劍客,分別是app
- 引擎:負責整個代碼編譯及執行的過程
- 編譯器: 負責詞法分析、語法分析、代碼生成
- 做用域:負責維護與收集全部聲明的標識符,保證當前執行代碼對這些標識符的訪問權限
舉例子,加深印象,對於var a = 2
,三劍客如何協同工做,
編譯器進行分詞、語法分析,而後要代碼生成時,遇到 var a
,問一下當前做用域集合「你有沒有這個名稱的變量呀?」,做用域若是說有,那麼忽略聲明,繼續編譯,若是說沒有,那麼就要要求做用域收集一下,而且給它個名字a
,而後編譯器就生成了代碼,引擎準備來執行了,先問當前做用域集合,「你這裏有a這個變量嗎?」,有引擎就拿來用,沒有就繼續找該變量,要麼找到,就給它附個值2
,沒有那就給你報個錯!函數
- LSH查詢,通俗解釋就是找到所聲明變量,而且對其賦值的行爲
- RSH查詢,通俗解釋就是查找聲明的變量
當一個塊或是函數嵌套在另一個塊或函數時,就會產生做用域嵌套,因而在當前做用域找不到某個變量時,引擎會往外層嵌套做用域繼續查找,直達到最外層做用域(全局做用域)爲止,也就是所謂的做用域鏈啦!性能
所謂的詞法做用域,就是定義在詞法階段(詞法分析)的做用域,由你寫代碼時將變量和塊做用域寫在哪裏來決定的。優化
做用域查找會在找到第一個匹配的標識符時中止,不會繼續往上層做用域查找,這就會產生遮蔽效應
。調試
- eval函數,修改詞法做用域
- with關鍵字,建立詞法做用域
致使性能降低的緣由,前面咱們提過,在解析/語法分析、生成代碼階段,咱們會對代碼進行優化,剔除冗餘元素,可是當使用eval/with時,全部優化變得沒有意義,由於存在不可預見性,不知道修改和建立的詞法做用域是什麼?因此會致使性能降低。code
函數表達式:function
爲第一個詞,那麼就是一個函數聲明,不然就是一個函數表達式
匿名函數表達式:沒有函數名,匿名函數在棧追蹤這種不會顯示有意義的函數名,使得調試困難
IIFE: 當即執行函數表達式((function() { ... }())
、(function(){ ... })()
)事件
let
爲其聲明的變量隱式地去劫持了所在的塊級做用域,不會在塊級做用域中進行提高【變量提高】Demo:
with關鍵字爲塊級做用域、{...}爲塊級做用域,用完即銷燬const
常量,不可修改!
任何聲明在某個做用域(函數做用域和塊級做用域)的變量,都是屬於這個做用域。
每一個做用域都會進行提高操做。
函數聲明會被提高,函數表達式不會提高,變量聲明提高的過程當中,函數會優先!
閉包,有權訪問另一個函數的變量標識符的函數,比較常見的一個閉包問題,就是for循環。
for(var i = 1; i <= 5; i++) { setTimeout(function() { console.log(i); }, i*1000) }
會發現每一次輸出的都是6
,爲啥勒?全部的回調函數回在循環結束後纔會執行(事件循環)。因此每次都是輸出一個6來。
解決辦法:
一、IIFE,每一個迭代儲存i
的值
for(var i = 1; i <= 5; i++) { (function(i) { var j = i; setTimeout(function() { console.log(j); }, j*1000); })(i) }
二、IIFE,將i
入參修改爲j
for(var i = 1; i <= 5; i++) { (function(j) { setTimeout(function() { console.log(j); }, j*1000); })(i) }
三、建立閉包的塊做用域
for(var i = 1; i <= 5; i++) { let j = i; setTimeout(function() { console.log(j); }, j*1000); }
四、最優
for(let i = 1; i <= 5; i++) { setTimeout(function() { console.log(i); }, i*1000) }
另一個閉包經常使用的場景,就是模塊暴露了,在這裏提供一個現代模塊機制的實現方式,你們能夠細細品嚐,
var MyMoudles = (function Manger() { var modules = {}; function define(name, deps, impl) { for (var i = 0; i < deps.length; i++) { deps[i] = modules[deps[i]]; } modules[name] = impl.apply(impl, deps); } function get(name) { return modules[name]; } })()
調用Demo
:
MyMoudles.define('bar', [], function() { function hello(who) { return "Let me introduce: " + who; } return { hello: hello } }) MyMoudles.define('foo', ['bar'], function(bar) { var hungry = 'hippo'; function awesome() { console.log(bar.hello(hungry).toUpperCase()); } return { awesome: awesome } }) var bar = MyMoudles.get('bar'); var foo = MyMoudles.get('foo'); console.log(bar.hello('hippo')); // Let me introduce: hippo foo.awesome();
ES6
提供了全新的模塊機制,基於函數的模塊(如上述現代模塊機制)並非一個能被靜態識別的模式(編譯器沒法識別),它們的API語義只有等到代碼運行時纔會考慮進來,而ES6
模塊就是一個能被靜態識別的模式,就是說API在編譯階段就會檢查API成員是否存在。