- 對閉包的理解
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中變量的函數(shù)馋吗,創(chuàng)建閉包的最常見的方式就是在一個(gè)函數(shù)內(nèi)創(chuàng)建另一個(gè)函數(shù)焕盟,創(chuàng)建的函數(shù)可以訪問到當(dāng)前函數(shù)的局部變量。
閉包有兩個(gè)常用的用途宏粤;
閉包的第一個(gè)用途是使我們在函數(shù)外部能夠訪問到函數(shù)內(nèi)部的變量脚翘。通過使用閉包,可以通過在外部調(diào)用閉包函數(shù)绍哎,從而在外部訪問到函數(shù)內(nèi)部的變量来农,可以使用這種方法來創(chuàng)建私有變量。
閉包的另一個(gè)用途是使已經(jīng)運(yùn)行結(jié)束的函數(shù)上下文中的變量對象繼續(xù)留在內(nèi)存中崇堰,因?yàn)殚]包函數(shù)保留了這個(gè)變量對象的引用沃于,所以這個(gè)變量對象不會(huì)被回收。
比如海诲,函數(shù) A 內(nèi)部有一個(gè)函數(shù) B繁莹,函數(shù) B 可以訪問到函數(shù) A 中的變量,那么函數(shù) B 就是閉包特幔。
function A() {
let a = 1
window.B = function () {
console.log(a)
}
}
A()
B() // 1
復(fù)制代碼
在 JS 中咨演,閉包存在的意義就是讓我們可以間接訪問函數(shù)內(nèi)部的變量。經(jīng)典面試題:循環(huán)中使用閉包解決 var 定義函數(shù)的問題
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
復(fù)制代碼
首先因?yàn)?setTimeout 是個(gè)異步函數(shù)蚯斯,所以會(huì)先把循環(huán)全部執(zhí)行完畢薄风,這時(shí)候 i 就是 6 了,所以會(huì)輸出一堆 6拍嵌。解決辦法有三種:
第一種是使用閉包的方式
for (var i = 1; i <= 5; i++) { ;(function(j) { setTimeout(function timer() { console.log(j) }, j * 1000) })(i)}
復(fù)制代碼
在上述代碼中遭赂,首先使用了立即執(zhí)行函數(shù)將 i 傳入函數(shù)內(nèi)部,這個(gè)時(shí)候值就被固定在了參數(shù) j 上面不會(huì)改變撰茎,當(dāng)下次執(zhí)行 timer 這個(gè)閉包的時(shí)候嵌牺,就可以使用外部函數(shù)的變量 j,從而達(dá)到目的。
第二種就是使用 setTimeout 的第三個(gè)參數(shù)逆粹,這個(gè)參數(shù)會(huì)被當(dāng)成 timer 函數(shù)的參數(shù)傳入募疮。
for (var i = 1; i <= 5; i++) {
setTimeout(
function timer(j) {
console.log(j)
},
i * 1000,
i
)
}
復(fù)制代碼
第三種就是使用 let 定義 i 了來解決問題了,這個(gè)也是最為推薦的方式
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, i * 1000)
}
復(fù)制代碼
- 對作用域僻弹、作用域鏈的理解
1)全局作用域和函數(shù)作用域
(1)全局作用域
最外層函數(shù)和最外層函數(shù)外面定義的變量擁有全局作用域
所有未定義直接賦值的變量自動(dòng)聲明為全局作用域
所有window對象的屬性擁有全局作用域
全局作用域有很大的弊端阿浓,過多的全局作用域變量會(huì)污染全局命名空間,容易引起命名沖突蹋绽。
(2)函數(shù)作用域
函數(shù)作用域聲明在函數(shù)內(nèi)部的變零芭毙,一般只有固定的代碼片段可以訪問到
作用域是分層的,內(nèi)層作用域可以訪問外層作用域卸耘,反之不行
2)塊級作用域
使用ES6中新增的let和const指令可以聲明塊級作用域退敦,塊級作用域可以在函數(shù)中創(chuàng)建也可以在一個(gè)代碼塊中的創(chuàng)建(由{ }包裹的代碼片段)
let和const聲明的變量不會(huì)有變量提升,也不可以重復(fù)聲明
在循環(huán)中比較適合綁定塊級作用域蚣抗,這樣就可以把聲明的計(jì)數(shù)器變量限制在循環(huán)內(nèi)部侈百。
作用域鏈:
在當(dāng)前作用域中查找所需變量,但是該作用域沒有這個(gè)變量翰铡,那這個(gè)變量就是自由變量钝域。如果在自己作用域找不到該變量就去父級作用域查找,依次向上級作用域查找锭魔,直到訪問到window對象就被終止例证,這一層層的關(guān)系就是作用域鏈。
作用域鏈的作用是保證對執(zhí)行環(huán)境有權(quán)訪問的所有變量和函數(shù)的有序訪問迷捧,通過作用域鏈织咧,可以訪問到外層環(huán)境的變量和函數(shù)。
作用域鏈的本質(zhì)上是一個(gè)指向變量對象的指針列表党涕。變量對象是一個(gè)包含了執(zhí)行環(huán)境中所有變量和函數(shù)的對象烦感。作用域鏈的前端始終都是當(dāng)前執(zhí)行上下文的變量對象巡社。全局執(zhí)行上下文的變量對象(也就是全局對象)始終是作用域鏈的最后一個(gè)對象膛堤。
當(dāng)查找一個(gè)變量時(shí),如果當(dāng)前執(zhí)行環(huán)境中沒有找到晌该,可以沿著作用域鏈向后查找肥荔。
- 對執(zhí)行上下文的理解
- 執(zhí)行上下文類型
(1)全局執(zhí)行上下文
任何不在函數(shù)內(nèi)部的都是全局執(zhí)行上下文,它首先會(huì)創(chuàng)建一個(gè)全局的window對象朝群,并且設(shè)置this的值等于這個(gè)全局對象燕耿,一個(gè)程序中只有一個(gè)全局執(zhí)行上下文。
(2)函數(shù)執(zhí)行上下文
當(dāng)一個(gè)函數(shù)被調(diào)用時(shí)姜胖,就會(huì)為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文誉帅,函數(shù)的上下文可以有任意多個(gè)。
(3)eval函數(shù)執(zhí)行上下文
執(zhí)行在eval函數(shù)中的代碼會(huì)有屬于他自己的執(zhí)行上下文,不過eval函數(shù)不常使用蚜锨,不做介紹档插。 - 執(zhí)行上下文棧
JavaScript引擎使用執(zhí)行上下文棧來管理執(zhí)行上下文
當(dāng)JavaScript執(zhí)行代碼時(shí),首先遇到全局代碼亚再,會(huì)創(chuàng)建一個(gè)全局執(zhí)行上下文并且壓入執(zhí)行棧中郭膛,每當(dāng)遇到一個(gè)函數(shù)調(diào)用,就會(huì)為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文并壓入棧頂氛悬,引擎會(huì)執(zhí)行位于執(zhí)行上下文棧頂?shù)暮瘮?shù)则剃,當(dāng)函數(shù)執(zhí)行完成之后,執(zhí)行上下文從棧中彈出如捅,繼續(xù)執(zhí)行下一個(gè)上下文棍现。當(dāng)所有的代碼都執(zhí)行完畢之后,從棧中彈出全局執(zhí)行上下文镜遣。
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
//執(zhí)行順序
//先執(zhí)行second(),在執(zhí)行first()
復(fù)制代碼
- 創(chuàng)建執(zhí)行上下文
創(chuàng)建執(zhí)行上下文有兩個(gè)階段:創(chuàng)建階段和執(zhí)行階段
1)創(chuàng)建階段
(1)this綁定
在全局執(zhí)行上下文中轴咱,this指向全局對象(window對象)
在函數(shù)執(zhí)行上下文中,this指向取決于函數(shù)如何調(diào)用烈涮。如果它被一個(gè)引用對象調(diào)用朴肺,那么 this 會(huì)被設(shè)置成那個(gè)對象,否則 this 的值被設(shè)置為全局對象或者 undefined
(2)創(chuàng)建詞法環(huán)境組件
詞法環(huán)境是一種有標(biāo)識(shí)符——變量映射的數(shù)據(jù)結(jié)構(gòu)坚洽,標(biāo)識(shí)符是指變量/函數(shù)名戈稿,變量是對實(shí)際對象或原始數(shù)據(jù)的引用。
詞法環(huán)境的內(nèi)部有兩個(gè)組件:加粗樣式:環(huán)境記錄器:用來儲(chǔ)存變量個(gè)函數(shù)聲明的實(shí)際位置外部環(huán)境的引用:可以訪問父級作用域
(3)創(chuàng)建變量環(huán)境組件
變量環(huán)境也是一個(gè)詞法環(huán)境讶舰,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系鞍盗。
2)執(zhí)行階段
此階段會(huì)完成對變量的分配,最后執(zhí)行完代碼跳昼。
簡單來說執(zhí)行上下文就是指:
在執(zhí)行一點(diǎn)JS代碼之前般甲,需要先解析代碼。解析的時(shí)候會(huì)先創(chuàng)建一個(gè)全局執(zhí)行上下文環(huán)境鹅颊,先把代碼中即將執(zhí)行的變量敷存、函數(shù)聲明都拿出來,變量先賦值為undefined堪伍,函數(shù)先聲明好可使用锚烦。這一步執(zhí)行完了,才開始正式的執(zhí)行程序帝雇。
在一個(gè)函數(shù)執(zhí)行之前涮俄,也會(huì)創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文環(huán)境,跟全局執(zhí)行上下文類似尸闸,不過函數(shù)執(zhí)行上下文會(huì)多出this彻亲、arguments和函數(shù)的參數(shù)孕锄。
全局上下文:變量定義,函數(shù)聲明
函數(shù)上下文:變量定義苞尝,函數(shù)聲明硫惕,this,arguments