今天為了搞清楚上下文(context)這個詞的概念,搜索了一番,根據(jù)網(wǎng)上的各種說法,個人理解context可以指中文中的語境,即為一段代碼、函數(shù)、變量所準(zhǔn)備的環(huán)境,借用輪子哥的解釋:
每一段程序都有很多外部變量。只有像Add這種簡單的函數(shù)才是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨(dú)立運(yùn)行。你為了使他們運(yùn)行,就要給所有的外部變量一個一個寫一些值進(jìn)去。這些值的集合就叫上下文。
譬如說在C++的lambda表達(dá)是里面,[寫在這里的就是上下文](int a, int b){ ... }。
--來自輪子哥的回答
對于前端來說,最常用的語言就是JavaScript,下面是對“Understanding Scope and Context in JavaScript”的文章的學(xué)習(xí)和總結(jié)。
首先,需要注意的是,JS中的作用域(scope)與上下文(context)不同,這兩個術(shù)語表達(dá)的并不是同一個概念。
- 對于JS的函數(shù)調(diào)用來說,每個函數(shù)調(diào)用都有與之相關(guān)的scope和context。
- scope基于函數(shù)(function-based)。與每次函數(shù)調(diào)用時訪問的變量相關(guān),每次調(diào)用時都不同。
- 上下文基于對象(object-based)。context總是關(guān)鍵字this的值,是調(diào)用當(dāng)前可執(zhí)行代碼的對象的引用。
變量作用域
對于JS來說,變量能夠被定義在全局或者局部作用域,導(dǎo)致運(yùn)行時變量的訪問來自不同的作用域。
全局變量需要聲明在函數(shù)體外,在整個執(zhí)行過程中都存在,能夠在任何作用域中訪問和修改(我的理解是,運(yùn)行環(huán)境[瀏覽器、node.js]的JS引擎/解釋器在執(zhí)行JS文件前,初始化了執(zhí)行代碼,為其準(zhǔn)備了全局環(huán)境,在運(yùn)行時(runtime),全局變量可以在任何作用域中訪問到)。
局部變量僅在函數(shù)體內(nèi)定義,runtime中,每次調(diào)用函數(shù)時的作用域范圍都不同,局部變量只能在其被調(diào)用期的作用域范圍內(nèi)被賦值、檢索、操縱。
ES6提供了let關(guān)鍵字,使得JS現(xiàn)在也支持了塊級作用域。ES6之前,if語句、switch語句、for循環(huán)、while循環(huán)中無法支持塊級作用域。使用關(guān)鍵字var在上面語句中,變量會在當(dāng)前局部作用域(函數(shù)體內(nèi))訪問到,因此會有我們常見的面試題:
for (var i = 0; i < 5; i++) {
setTimeout(()=> {
console.log(i)
}, 0)
}
console.log(i)
// 5 55555
// 使用let 使for 語句產(chǎn)生了塊級作用域
for (let i = 0; i < 5; i++){
setTimeout(()=> {
console.log(i)
}, 0)
}
console.log(i)
// 5 0 1 2 3 4
// 使用局部作用域
var output = function (i) {
setTimeout(()=>{
console.log(i);
}, 0);
};
for (var i = 0; i < 5; i++) {
output(i); // 將每個i作為參數(shù)傳入函數(shù)內(nèi),每個函數(shù)在調(diào)用時有自己獨(dú)立的作用域,因此每個i不同
}
// 0 1 2 3 4
this context
Context取決于函數(shù)是如何被調(diào)用的。
例如,當(dāng)函數(shù)作為對象方法被調(diào)用時,this指向這個對象。
var obj = {
foo:fucntion(){
console.log(this === obj)
}
}
obj.foo() // true
使用new關(guān)鍵字來創(chuàng)建對象實(shí)例,也是使用了這個規(guī)則:
function foo() {
console.log(this);
}
foo() // window
new foo() // foo
當(dāng)調(diào)用一個為綁定函數(shù)時,this默認(rèn)情況下是全局上下文,在瀏覽器中它指向window對象。需要注意的是,ES5引入了嚴(yán)格模式的概念, 如果啟用了嚴(yán)格模式,此時上下文默認(rèn)為undefined。
執(zhí)行上下文(Execution Context)
JS是一個單線程語言,因此JS解釋器在初始化執(zhí)行代碼時,會默認(rèn)進(jìn)入全局的執(zhí)行上下文(execution context),此時,每個函數(shù)調(diào)用都會創(chuàng)建一個新的執(zhí)行上下文。
需要注意的是,execution context的意義與context 不同(ES的規(guī)定),它更傾向于作用域的作用,定義了變量或函數(shù)仿問其他作用域的權(quán)限。
每個函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個函數(shù)時,函數(shù)的環(huán)境就會被推入一個環(huán)境棧中(execution stack)。在函數(shù)執(zhí)行完后,棧將其環(huán)境彈出, 把控制權(quán)返回給之前的執(zhí)行環(huán)境。ECMAScript程序中的執(zhí)行流正是由這個便利的機(jī)制控制著。
執(zhí)行環(huán)境可以分為創(chuàng)建和執(zhí)行兩個階段。在創(chuàng)建階段,解析器首先會創(chuàng)建一個變量對象(variable object,也稱為活動對象 activation object),它由定義在執(zhí)行環(huán)境中的變量、函數(shù)聲明、和參數(shù)組成。在這個階段,作用域鏈會被初始化,this的值也會被最終確定。 在執(zhí)行階段,代碼被解釋執(zhí)行。
每個執(zhí)行環(huán)境都有一個與之關(guān)聯(lián)的變量對象(variable object),環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。 需要知道,我們無法手動訪問這個對象,只有解析器才能訪問它。
這里,我的理解是:
- context關(guān)注的的是
this關(guān)鍵字的使用和指向。 - execution context指代的是作用域的范圍。
- JS初始化時,會往執(zhí)行棧創(chuàng)建一個變量對象(variable object/activation object),并由執(zhí)行環(huán)境的變量、函數(shù)聲明、和參數(shù)組成。
- 該執(zhí)行環(huán)境是獨(dú)立的。它更像是一個堆棧的過程,每一層會先根據(jù)代碼構(gòu)建,彈入堆棧。當(dāng)執(zhí)行完會將已執(zhí)行的代碼彈出。
- 由于是單線程語言,當(dāng)一個函數(shù)執(zhí)行完后會彈出這個函數(shù)(清理內(nèi)存),執(zhí)行下一個函數(shù),往全局變量上添加新的執(zhí)行函數(shù),因此每個函數(shù)都是獨(dú)立作用域。
- 由于類似堆棧的過程,因此每一層函數(shù)都可以向外訪問到上一層(外部環(huán)境),但是外部環(huán)境不能訪問內(nèi)部環(huán)境。
- 基于上面的說法,對變量/函數(shù)的訪問,會從當(dāng)前作用域開始,如果找不到就會逐步向上層訪問查找,于是構(gòu)成了作用域鏈。
參考資料: