網(wǎng)站建設(shè)的簡歷制作b2b免費網(wǎng)站推廣平臺
本篇為第二篇,本系列文章會在后續(xù)學習后持續(xù)更新。
第一篇:#深入學習JavaScript系列(一)—— ES6中的JS執(zhí)行上下文
第二篇:# 深入學習JavaScript系列(二)——作用域和作用域鏈
第三篇:# 深入學習JavaScript系列(三)——this
第四篇:# 深入學習JavaScript系列(四)——JS閉包
第五篇:# 深入學習JavaScript系列(五)——原型/原型鏈
第六篇: # 深入學習JavaScript系列(六)——對象/繼承
第七篇:# 深入學習JavaScript系列(七)——Promise async/await generator
上一篇提到 在js的執(zhí)行上下文中詞法環(huán)境中會包含作用域鏈,同時詞法環(huán)境解釋階段生成,在執(zhí)行完畢后會被銷毀,這也說明了作用域鏈的生命周期是隨著函數(shù)的創(chuàng)建與銷毀的。
先通過兩個問題來引出一下
1、js執(zhí)行上下文和作用域鏈的關(guān)系?
作用域鏈和執(zhí)行上下文是緊密相連的概念。
執(zhí)行上下文是JavaScript代碼執(zhí)行時的環(huán)境,包括變量、函數(shù)、參數(shù)等信息。每個函數(shù)都有自己的執(zhí)行上下文,而全局代碼也有自己的執(zhí)行上下文。
從執(zhí)行上下文的角度來說:
作用域鏈是由當前執(zhí)行上下文和所有外層執(zhí)行上下文的變量對象組成的鏈式結(jié)構(gòu)
。當JavaScript代碼在一個執(zhí)行上下文中查找變量時,會先在當前執(zhí)行上下文的變量對象中查找,如果沒有找到,就會繼續(xù)在外層執(zhí)行上下文的變量對象中查找,直到找到該變量或者到達全局執(zhí)行上下文。
因此,作用域鏈的形成是由執(zhí)行上下文的嵌套關(guān)系決定的。在函數(shù)定義時,就已經(jīng)確定了該函數(shù)的作用域鏈。當函數(shù)被調(diào)用時,會創(chuàng)建一個新的執(zhí)行上下文,并將該執(zhí)行上下文的變量對象添加到作用域鏈的頂端,從而形成新的作用域鏈。
函數(shù)銷毀時,與之對應(yīng)的作用域鏈節(jié)點也會銷毀。
總之,作用域鏈和執(zhí)行上下文是密不可分的,它們共同構(gòu)成了JavaScript代碼的作用域和變量查找機制。
2、什么是js作用域鏈和作用域?
在 JavaScript 中,每個函數(shù)都有一個作用域鏈,它是一個由當前函數(shù)和所有外層函數(shù)的變量對象組成的列表
。當 JavaScript 引擎查找一個變量時,它會先在當前函數(shù)的變量對象中查找,如果找不到,就會在外層函數(shù)的變量對象中查找,直到找到該變量或者到達全局對象為止。
每個函數(shù)內(nèi)部聲明的變量和函數(shù)都只能在該函數(shù)內(nèi)部訪問,外部無法訪問。但是,如果一個函數(shù)內(nèi)部嵌套了另一個函數(shù),那么內(nèi)部函數(shù)可以訪問外部函數(shù)的變量和函數(shù),這就是作用域鏈的作用。
作用域鏈的創(chuàng)建是在函數(shù)定義時確定的,而不是在函數(shù)調(diào)用時確定的
。當一個函數(shù)被調(diào)用時,它會創(chuàng)建一個新的執(zhí)行上下文,并將其添加到調(diào)用棧中。在執(zhí)行上下文中,會創(chuàng)建一個變量對象,該變量對象包含了當前函數(shù)的所有變量和函數(shù)聲明。同時,該執(zhí)行上下文的作用域鏈會指向當前函數(shù)的作用域鏈。
當 JavaScript 引擎在執(zhí)行一個函數(shù)時,如果需要訪問一個變量,它會先在當前函數(shù)的變量對象中查找,如果找不到,就會在外層函數(shù)的變量對象中查找,直到找到該變量或者到達全局對象為止。這個查找的過程就是作用域鏈的遍歷過程。
通過上面兩個問題的展開,其實已經(jīng)能大致了解了作用域與作用域鏈,下面就展開講一講 在學習作用域鏈時需要注意哪些內(nèi)容
3、作用域和作用域鏈的創(chuàng)建
js采用的是靜態(tài)作用域
,也就是說js函數(shù)的作用域是在函數(shù)定義的情況下就已經(jīng)確定了,那么作用域鏈也是在函數(shù)定義的時候就創(chuàng)建,
舉個靜態(tài)作用域的小例子:
var value = 1;function foo() {console.log(value);
}function bar() {var value = 2;foo();
}bar();// 結(jié)果是 1 在函數(shù)定義的時候確定的作用value等于1 所以調(diào)用的時候也等于1
如果上述的代碼是動態(tài)作用域,那么value就是在執(zhí)行時才生成 所以打印為2
動態(tài)作用域的語言:
- Bash:Bash 是一種常用的 Unix shell,它支持動態(tài)作用域。在 Bash 中,變量的作用域是在函數(shù)被調(diào)用時
- Perl:Perl 是一種通用的高級編程語言,它支持動態(tài)作用域。在 Perl 中,變量的作用域是在程序運行時確定
- Lisp:Lisp 是一種函數(shù)式編程語言,它也支持動態(tài)作用域。在 Lisp 中,變量的作用域是在函數(shù)被調(diào)用時確定
- Emacs Lisp:Emacs Lisp 是一種基于 Lisp 的編程語言,它是 Emacs 編輯器的擴展語言,也支持動態(tài)作用域。
4 作用域和作用域鏈的執(zhí)行銷毀
js有兩種函數(shù)作用域:全局作用域和函數(shù)作用域
,創(chuàng)建上面已經(jīng)講到了,那么執(zhí)行呢,其實是在函數(shù)中的變量被使用時會執(zhí)行,先在當前作用域中查找變量,然后再逐步往上查找父級作用域,直到找到為止,
如果最終都沒找到,那么就會拋出一個 ReferenceError
異常。
作用域鏈及作用域鏈的銷毀
當一個函數(shù)執(zhí)行完畢之后,那么它的執(zhí)行上下文也隨之銷毀,于此同時。對應(yīng)的函數(shù)作用域和作用域鏈也會銷毀(當前函數(shù)作用域節(jié)點),函數(shù)中聲明的變量也會被銷毀,這就是js中大垃圾回收機制(gc)
最常見的有標記清除法和計數(shù)算法
注:如果函數(shù)中使用了閉包,那可能包含外部變量,此時只有外部變量被銷毀時才能銷毀對應(yīng)的閉包。這就是所謂的內(nèi)存泄漏。
5 幾個作用域練習題目
題目一:
var scope = "global scope";
function checkscope(){var scope = "local scope";function scope(){return scope;}return scope();
}
checkscope();
// local scope
var scope = "global scope";
function checkscope(){var scope = "local scope";function scope(){return scope;}return scope;
}
checkscope()();
// local scope
解釋:函數(shù)的作用域基于函數(shù)創(chuàng)建的位置,
注:checkscope()()和checkscope()的區(qū)別:
題目一中,checkscope()
是一個函數(shù),它返回另一個函數(shù)scope()
。這個內(nèi)部函數(shù)scope()
可以訪問checkscope()
函數(shù)的局部變量。它是一個閉包函數(shù),因為它可以訪問其外部函數(shù)checkscope()
的詞法環(huán)境。
checkscope()()
是在調(diào)用checkscope()
返回的函數(shù)scope()
。它不是直接調(diào)用checkscope()
函數(shù)本身。由于checkscope()
函數(shù)返回了一個函數(shù),因此需要在checkscope()
后添加另一個括號來調(diào)用內(nèi)部函數(shù)scope()
。相當于》checkscope(). f()
題目二:
var a = 1;function foo() {console.log(a);var a = 2;
}foo();
// undefined
解釋:因為在函數(shù) foo()
中,先執(zhí)行了 var a = 2
語句,此時變量 a
被重新定義并賦值為 2。所以,在函數(shù) foo()
中,變量 a
的值是 2,而不是全局變量 a
的值。因此,執(zhí)行 console.log(a)
語句時,輸出的是 undefined,因為變量 a
被重新定義,但沒有被初始化。 變量能提升 但是賦值不能提升
題目三:
var x = 1;function outer() {var y = 2;function inner() {var z = 3;console.log(x + y + z);}inner();
}outer();
// 6
解釋:在調(diào)用inner函數(shù)時,可以訪問外層作用域中的變量 x
和 y
。變量 x
的值是 1,變量 y
的值是 2,變量 z
的值是 3。所以,執(zhí)行 console.log(x + y + z)
語句時,輸出的是 6。
題目四:
function foo() {var a = 1;function bar() {console.log(a);}return bar;
}var baz = foo();
baz();
答案:代碼輸出的是1。因為bar
函數(shù)在定義時可以訪問到其外層函數(shù)foo
的變量a
,并將其保存在函數(shù)作用域內(nèi)部。當執(zhí)行baz()
時,實際上是執(zhí)行了內(nèi)部函數(shù)bar()
,此時訪問到的變量a
是定義時保存在bar
函數(shù)作用域內(nèi)的變量,所以輸出的是1
題目五:
var a = 1;
function foo() {console.log(a);
}function bar() {var a = 2;foo();
}bar();
答案:代碼輸出的是1。因為bar
函數(shù)定義了一個名為a
的局部變量,并將其賦值為2,但是在調(diào)用foo
函數(shù)時,它會在全局作用域中查找變量a
,因為在foo
函數(shù)內(nèi)部沒有定義變量a
。因此,foo
函數(shù)輸出的是全局變量a
的值,即1。
關(guān)鍵點 foo的父級作用域不是bar 而是全局 只是foo()在bar()中執(zhí)行。
題目六
var a = 1;
function foo() {var a = 2;function bar() {console.log(a);}return bar;
}var baz = foo();
baz();
答案:代碼輸出的是2。在foo
函數(shù)內(nèi)部定義了一個局部變量a
,并將其賦值為2,然后返回了內(nèi)部定義的函數(shù)bar
。bar
函數(shù)可以訪問到foo
函數(shù)的作用域鏈,因此可以訪問到變量a
。當調(diào)用baz
函數(shù)時,實際上是執(zhí)行了內(nèi)部函數(shù)bar
,此時訪問到的變量a
是定義時保存在foo
函數(shù)作用域內(nèi)的變量,所以輸出的是2。
和題目五對比,因為bar函數(shù)是定義在foo函數(shù)內(nèi)的 ,所以bar的父級作用域是foo
看到這里的同學都很厲害,那就順便給我點個贊吧!
參考一:# JavaScript深入之作域鏈
參考二:# JavaScript深入之詞法作用域和動態(tài)作用域