引擎
從頭到尾負(fù)責(zé)整個(gè) JavaScript 程序的編譯及執(zhí)行過程伴箩。JavaScript 引擎不會(huì)有大量的(像其他語言編譯器那么多的)時(shí)間用來進(jìn)行優(yōu)化启涯,因?yàn)榕c其他語言不同困肩,編譯過程不是發(fā)生在構(gòu)建之前的。大部分情況下編譯發(fā)生在代碼執(zhí)行前的幾微秒的時(shí)間內(nèi)
編譯器
負(fù)責(zé)語法分析及代碼生成等臟活累活拗窃。
作用域
負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查詢瞎领,并實(shí)施一套非常嚴(yán)格的規(guī)則泌辫,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問權(quán)限。作用域共有兩種主要的工作模型九默。 詞法作用域與動(dòng)態(tài)作用域
LHS 與 RHS
變量出現(xiàn)在賦值操作的左側(cè)時(shí)進(jìn)行 LHS 查詢震放,出現(xiàn)在右側(cè)時(shí)進(jìn)行 RHS 查詢
var a = 2;
- 遇到 var a,編譯器會(huì)詢問作用域是否已經(jīng)有一個(gè)該名稱的變量存在于同一個(gè)作用域的集合中荤西。如果是澜搅,編譯器會(huì)忽略該聲明,繼續(xù)進(jìn)行編譯;否則它會(huì)要求作用域在當(dāng)前作用域的集合中聲明一個(gè)新的變量邪锌,并命名為 a勉躺。
- 接下來編譯器會(huì)為引擎生成運(yùn)行時(shí)所需的代碼,這些代碼被用來處理 a = 2 這個(gè)賦值操作觅丰。引擎運(yùn)行時(shí)會(huì)首先詢問作用域饵溅,在當(dāng)前的作用域集合中是否存在一個(gè)叫作 a 的變量。如果是妇萄,引擎就會(huì)使用這個(gè)變量;如果否蜕企,引擎會(huì)繼續(xù)查找該變量
function foo(a) { // NOTE: 這里有個(gè)LHS a=2
console.log( a ); // RHS
}
foo( 2 );
區(qū)別:
非嚴(yán)格模式下
- RHS 查詢?cè)谒星短椎淖饔糜蛑斜閷げ坏剿璧淖兞浚婢蜁?huì)拋出 ReferenceError 異常
- LHS 查詢時(shí)冠句,如果在頂層(全局作用域)中也無法找到目標(biāo)變量轻掩,全局作用域中就會(huì)創(chuàng)建一個(gè)具有該名稱的變量,并將其返還給引擎懦底,前提是程序運(yùn)行在非 “嚴(yán)格模式”下唇牧。 undefined
a = 2 // RIGHT
"use strict"
a = 2 // WRONG: ReferenceError
嚴(yán)格模式
- LHS 查詢失敗時(shí),并不會(huì)創(chuàng)建并返回一個(gè)全局變量聚唐,引擎會(huì)拋出同 RHS 查詢失敗時(shí)類似的 ReferenceError 異常丐重。
- RHS 查詢找到了一個(gè)變量,但是你嘗試對(duì)這個(gè)變量的值進(jìn)行不合理的操作杆查, 比如試圖對(duì)一個(gè)非函數(shù)類型的值進(jìn)行函數(shù)調(diào)用扮惦,或著引用 null 或 undefined 類型的值中的 屬性,那么引擎會(huì)拋出另外一種類型的異常亲桦,叫作 TypeError崖蜜。
"use strict";
false.true = ""; //TypeError
(14).sailing = "home"; //TypeError
"with".you = "far away";
編譯流程
- 分詞/詞法分析(Tokenizing/Lexing) 這個(gè)過程會(huì)將由字符組成的字符串分解成(對(duì)編程語言來說)有意義的代碼塊,
- 解析/語法分析(Parsing) 生成 抽象語法樹(AST)
- 生成代碼
提升
這意味著無論作用域中的聲明出現(xiàn)在什么地方客峭,都將在代碼本身被執(zhí)行前首先進(jìn)行處理豫领。 可以將這個(gè)過程形象地想象成所有的聲明(變量和函數(shù))都會(huì)被“移動(dòng)”到各自作用域的 最頂端,這個(gè)過程被稱為提升桃笙。
只有聲明本身會(huì)被提升,而賦值或其他運(yùn)行邏輯會(huì)留在原地沙绝。
a = 2;
var a;
console.log( a ); // 2
console.log( a ); // undefine
var a = 2;
箭頭函數(shù)
箭頭函數(shù)在涉及 this 綁定時(shí)的行為和普通函數(shù)的行為完全不一致搏明。它放棄了所 有普通 this 綁定的規(guī)則鼠锈,取而代之的是用當(dāng)前的詞法作用域覆蓋了 this 本來的值。
閉包
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2
以下代碼的區(qū)別
- 盡管循環(huán)中的五個(gè)函數(shù)是在各個(gè)迭代中分別定義的星著, 但是它們都被封閉在一個(gè)共享的全局作用域中购笆,因此實(shí)際上只有一個(gè) i
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
- IIFE 只是一 個(gè)什么都沒有的空作用域。它需要包含一點(diǎn)實(shí)質(zhì)內(nèi)容才能為我們所用虚循。
for (var i = 1; i <= 5; i++) {
(function () {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})();
}
- 在迭代內(nèi)使用 IIFE 會(huì)為每個(gè)迭代都生成一個(gè)新的作用域同欠,使得延遲函數(shù)的回調(diào)可以將新的
作用域封閉在每個(gè)迭代內(nèi)部,每個(gè)迭代中都會(huì)含有一個(gè)具有正確值的變量供我們?cè)L問
for (var i = 1; i <= 5; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})(i); // <= 注意這里
}
模塊
- 為創(chuàng)建內(nèi)部作用域而調(diào)用的一個(gè)包裝函數(shù)
- 包裝函數(shù)的返回值必須至少包括一個(gè)對(duì)內(nèi)部函數(shù)的引用横缔,這樣就會(huì)創(chuàng)建涵蓋整個(gè)包裝函數(shù)內(nèi)部作用域的閉包铺遂。
function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join(" ! "));
}
return {
doSomething: doSomething, doAnother: doAnother
};
}
var foo = CoolModule(); foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
ES6 的模塊沒有“行內(nèi)”格式,必須被定義在獨(dú)立的文件中(一個(gè)文件一個(gè)模塊)茎刚。瀏覽 器或引擎有一個(gè)默認(rèn)的“模塊加載器”(可以被重載襟锐,但這遠(yuǎn)超出了我們的討論范圍)可 以在導(dǎo)入模塊時(shí)異步地加載模塊文件。
示例代碼
var MyModules = (function Manager() {
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); // 依賴傳入
console.log(modules[name].hello)
/**
* modules 的結(jié)構(gòu)
* {
* foo: { hello: function}
* bar: { awesome: function}
* }
*/
}
function get(name) {
return modules[name];
}
return {
define: define,
get: get
};
})();
MyModules.define("bar", [], function () {
function hello(who) {
return "Let me introduce: " + who;
}
return {
hello: hello
};
});
MyModules.define("foo", ["bar"], function (bar) {
var hungry = "hippo";
function awesome() {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
};
});
var bar = MyModules.get("bar");
var foo = MyModules.get("foo");
console.log(bar.hello("hippo")); // Let me introduce: hippo
foo.awesome(); // LET ME INTRODUCE: HIPPO