當(dāng)我寫(xiě)一段js代碼并運(yùn)行時(shí),有三個(gè)非常重要的角色:引擎、 編譯器、 作用域
下面模擬一段js代碼運(yùn)行的過(guò)程
如下代碼
var a = 2
- 遇到var a 編譯器會(huì)詢(xún)問(wèn)作用域是否已經(jīng)有一個(gè)變量a存在于統(tǒng)一作用域下,如果有則忽略該聲明,繼續(xù)進(jìn)行編譯,,否則會(huì)要求作用域在當(dāng)前作用域聲明新的變量a
- 編譯器會(huì)為引擎生產(chǎn)運(yùn)行時(shí)所需要的代碼,用來(lái)處理a=2的賦值操作,引擎運(yùn)行時(shí),會(huì)先詢(xún)問(wèn)作用域是否有變量a,如果是,會(huì)引用這個(gè)變量,否則,引擎會(huì)繼續(xù)查找該變量。
- 如果引擎找到了a,就把2賦值給它。否則引擎就會(huì)丟出一個(gè)異常
詞法作用域
- let:可以將變量綁定在所在的作用域內(nèi)(通常是{....}內(nèi)部),也就是說(shuō)let為它聲明的變量隱式的劫持了所在的塊作用域
注意:使用顯式的代碼能讓代碼更加的清晰,如下
var a = 6
if (a) {
{ // <-- 顯式的塊
let boy = "Jack"
console.log(boy)
}
}
聲明提前的小細(xì)節(jié)
查看以下代碼
foo() //TypeError 此時(shí)只聲明了foo,并未定義foo是函數(shù)
bar() //ReferenceError 找不到bar
var foo = function bar() {
console.log("hello")
}
以上代碼會(huì)被理解為以下形式
var foo
foo()
bar()
foo = function () {
var bar = ..self..
}
注意:包括函數(shù)表達(dá)式的賦值操作在內(nèi)的賦值操作不會(huì)被提升
閉包
考慮如下代碼
function foo() {
var a = 2
function bar() {
console.log(a)
}
return bar
}
var baz = foo()
baz() //2 這就是典型的閉包了
我們打開(kāi)控制臺(tái)研究下這段代碼

看看綠色箭頭處,這就是閉包
注意:無(wú)論何種方式將內(nèi)部函數(shù)傳遞到所在的詞法作用域外,它都會(huì)持有對(duì)原始作用域的引用,無(wú)論在何處執(zhí)行這個(gè)函數(shù),都會(huì)使用閉包
閉包的本質(zhì)
將(訪(fǎng)問(wèn)它們各自詞法作用域的)函數(shù)當(dāng)作第一級(jí)的值類(lè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
這是典型的閉包應(yīng)用,在js中被稱(chēng)為模塊,最常見(jiàn)的實(shí)現(xiàn)模塊的方式通常被稱(chēng)為模塊暴露
模塊模式需要具備兩個(gè)必要條件
- 必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次(每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的模塊實(shí)例)
- 封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪(fǎng)問(wèn)或者修改私有的狀態(tài)
模塊模式的功能
命名將要作為公共API返回的對(duì)象
var foo = (function CoolModule(id) {
function change() {
publickAPI.identify = identify2
}
function identify1() {
console.log(id)
}
function identify2() {
console.log(id.toUpperCase())
}
var publickAPI = {
change: change,
identify: identify1
}
return publickAPI
})("foo module")
foo.identify() //foo module
foo.change()
foo.identify() //FOO MODULE
通過(guò)模塊化實(shí)例的內(nèi)部保留對(duì)公共API對(duì)象的內(nèi)部引用,可以從內(nèi)部對(duì)模塊實(shí)例進(jìn)行修改,包括添加或刪除方法和屬性,以及修改它們的值
現(xiàn)代化的模塊機(jī)制
大多數(shù)模塊依賴(lài)加載器/管器本質(zhì)上都是將這種模塊定義封裝進(jìn)一個(gè)友好的API
考慮如下代碼:
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)
}
function get() {
return modules[name]
}
return {
define: define,
get: get
}
})()