javascript作用域鏈與執(zhí)行環(huán)境詳解
前言:這是筆者學(xué)習(xí)之后自己的理解與整理。如果有錯(cuò)誤或者疑問的地方,請(qǐng)大家指正,我會(huì)持續(xù)更新!
作用域、作用域鏈、執(zhí)行環(huán)境、執(zhí)行環(huán)境棧以及this的概念在javascript中非常重要,本人經(jīng)常弄混淆,這里梳理一下;
- 局部作用域函數(shù)內(nèi)部的區(qū)域,全局作用域就是window;
- 作用域鏈取決于函數(shù)被聲明時(shí)的位置,解析標(biāo)識(shí)符的時(shí)候就先找當(dāng)前作用域,再向外查找,直到全局,這樣一個(gè)順序;和函數(shù)在哪里調(diào)用無關(guān);
- 執(zhí)行環(huán)境就是函數(shù)可訪問的數(shù)據(jù)和變量的集合,也就是函數(shù)的作用域鏈上的所有數(shù)據(jù)和變量;
- 執(zhí)行環(huán)境棧就是根據(jù)代碼執(zhí)行順序,各執(zhí)行環(huán)境按照棧的形式逐層訪問,并且用完了退出來扔掉;如果當(dāng)前執(zhí)行環(huán)境(存放當(dāng)前作用域鏈里的數(shù)據(jù)和變量)找不到變量,那就是找不到了,不會(huì)往之前的那個(gè)執(zhí)行環(huán)境查找,它和作用域鏈?zhǔn)遣煌模?/li>
- this是一個(gè)對(duì)象,它取決于是誰執(zhí)行的,誰執(zhí)行那就是誰;(this的概念還是不太清楚,這里寫的有點(diǎn)萬金油,過兩天再來修正)
作用域
JavaScript沒有塊級(jí)作用域的概念,只有函數(shù)級(jí)作用域:變量在聲明它們的函數(shù)體及其子函數(shù)內(nèi)是可見的。
作用域就是變量和函數(shù)的可訪問范圍,控制著變量和函數(shù)的可見性與生命周期,在JavaScript中變量的作用域有全局作用域和局部作用域。
變量沒有在函數(shù)內(nèi)聲明或者聲明的時(shí)候沒有帶var就是全局變量,擁有全局作用域;
<script type="text/javascript"> function test1(){ a = 1;//全局變量,只有在當(dāng)前函數(shù)運(yùn)行時(shí),才有效 } test1(); console.log(a);//1 注意test1函數(shù)必須運(yùn)行,不然找不到a </script>
全局變量可以當(dāng)做window對(duì)象的屬性用,他們是一樣的;
<script type="text/javascript"> var b = 1;//全局變量 console.log(b === window.b);//true 全局變量可以當(dāng)做window對(duì)象的屬性用,他們是一樣的; </script>
window對(duì)象的所有屬性擁有全局作用域,在代碼任何地方都可以訪問;
函數(shù)內(nèi)部聲明的變量就是局部變量,只能在函數(shù)體內(nèi)使用,函數(shù)的參數(shù)雖然沒有使用var但仍然是局部變量。
<script type="text/javascript"> var c = 1;//全局變量 // console.log(d);//ReferenceError: d is not defined 引用錯(cuò)誤,當(dāng)前作用域就是最外層作用域,依然找不到d function test2(d){ console.log(c);//1 全局變量,哪都可以訪問;(先找當(dāng)前作用域,找不到,就向外層作用域找,直到window最外層,找到了) console.log(d);//3 形參是局部變量,只有當(dāng)前作用域下可以訪問 } test2(3); </script>
作用域鏈
作用域鏈取決于函數(shù)被聲明時(shí)的位置,解析標(biāo)識(shí)符的時(shí)候就先從當(dāng)前作用域開始找,在當(dāng)前作用域中無法找到時(shí),引擎就會(huì)在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或抵達(dá)最外層的作用域(也就是全局作用域)為止;它的路線已經(jīng)被定死了,和函數(shù)在哪里運(yùn)行無關(guān);
<script type="text/javascript"> var a = 1; var b = 2; var c = 3; var d = 4; function inner(d) {//它的作用域鏈?zhǔn)莍nner---全局 var c = 8; console.log(a);//1 當(dāng)前作用域找不到a,去全局作用域找到了a=1 console.log(b);//2 當(dāng)前作用域找不到b,去全局作用域找到了b=2 console.log(c);//8 當(dāng)前作用域找到了c=8 console.log(d);//7 當(dāng)前作用域找到了d=7,形參也是局部作用域 // console.log(e);//ReferenceError: e is not defined 引用錯(cuò)誤,找不到e, 它的作用域鏈?zhǔn)莍nner---全局 console.log(a+b+c+d);//18 } function outter(e) { var a = 5;//inner()的作用域鏈?zhǔn)莍nner---全局,所以這個(gè)a相當(dāng)于無效 var b = 6;//inner()的作用域鏈?zhǔn)莍nner---全局,所以這個(gè)a相當(dāng)于無效 inner(7); } outter(999);//這個(gè)999無效,里面的e根本找不到 </script>
在多層的嵌套作用域中可以定義同名的標(biāo)識(shí)符,這叫作“遮蔽效應(yīng)”,內(nèi)部的標(biāo)識(shí)符“遮蔽”了外部的標(biāo)識(shí)符
通過window.a這種技術(shù)可以訪問那些被同名變量所遮蔽的全局變量。但非全局的變量如果被遮蔽了,無論如何都無法被訪問到;
<script type="text/javascript"> var a = 'Lily'; var b = 'Lucy'; function outer() { var b = 'Jesica'; var c = 'Susan'; function inner(c) { console.log(a);//Lily console.log(window.b);//Lucy console.log(b);//Jesica console.log(c);//Jenifer } inner('Jenifer'); } outer(); </script>
執(zhí)行環(huán)境
執(zhí)行環(huán)境(execution context),也叫執(zhí)行上下文。每個(gè)執(zhí)行環(huán)境都有一個(gè)變量對(duì)象(variable object),保存函數(shù)可訪問的所有變量和數(shù)據(jù)(也就是函數(shù)的作用域鏈上的所有數(shù)據(jù)和變量)。我們的代碼訪問不到它,它是給引擎使用的;
執(zhí)行環(huán)境棧,當(dāng)執(zhí)行進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的執(zhí)行環(huán)境就會(huì)被推入一個(gè)棧中。而在函數(shù)執(zhí)行完之后,棧將其執(zhí)行環(huán)境移除,它里面的變量和數(shù)據(jù)會(huì)被標(biāo)記清除,等待垃圾回收,再把控制權(quán)返回給之前的執(zhí)行環(huán)境。javascript程序中的執(zhí)行正是由這個(gè)機(jī)制控制著;
需要注意的是如果當(dāng)前執(zhí)行環(huán)境(存放當(dāng)前作用域鏈里的數(shù)據(jù)和變量)找不到變量,那就是找不到了,不會(huì)往之前的那個(gè)執(zhí)行環(huán)境查找,和作用域鏈?zhǔn)遣灰粯拥模?/strong>
代碼的執(zhí)行順序也不全是一行一行的執(zhí)行,而是和函數(shù)的調(diào)用順序有關(guān):
- 代碼進(jìn)入全局執(zhí)行環(huán)境,全局執(zhí)行環(huán)境放入環(huán)境棧;
- 當(dāng)執(zhí)行到一個(gè)函數(shù)時(shí),就把這個(gè)函數(shù)的執(zhí)行環(huán)境推入到環(huán)境棧頂端,之前的執(zhí)行環(huán)境往后;
- 全局執(zhí)行環(huán)境最先進(jìn)入,所以一直在底端;就和棧的概念差不多;
- 函數(shù)執(zhí)行完之后,再把它的執(zhí)行環(huán)境從作用域鏈頂端移除,它保存的數(shù)據(jù)和函數(shù)都被標(biāo)記清除,等待垃圾回收;
- 控制權(quán)交給之前的執(zhí)行環(huán)境,繼續(xù)往下執(zhí)行;
- 當(dāng)頁面關(guān)閉時(shí),全局執(zhí)行環(huán)境才銷毀;
<script type="text/javascript"> var a = 1; var b = 2; var c = 3; var d = 4; function inner(d) {//它的作用域鏈?zhǔn)莍nner---全局 var c = 8; console.log(a);//1 當(dāng)前作用域找不到a,去全局作用域找到了a=1 console.log(b);//2 當(dāng)前作用域找不到b,去全局作用域找到了b=2 console.log(c);//8 當(dāng)前作用域找到了c=8 console.log(d);//7 當(dāng)前作用域找到了d=7,形參也是局部作用域 // console.log(e);//ReferenceError: e is not defined 引用錯(cuò)誤,找不到e, 它的作用域鏈?zhǔn)莍nner---全局 console.log(a+b+c+d);//18 } function outter(e) { var a = 5;//inner()的作用域鏈?zhǔn)莍nner---全局,所以這個(gè)a相當(dāng)于無效 var b = 6;//inner()的作用域鏈?zhǔn)莍nner---全局,所以這個(gè)a相當(dāng)于無效 inner(7); } outter(999);//這個(gè)999無效,里面的e根本找不到 </script>
以上代碼的執(zhí)行順序:
代碼執(zhí)行進(jìn)入全局執(zhí)行環(huán)境,并對(duì)全局執(zhí)行環(huán)境中的代碼進(jìn)入聲明提升;
執(zhí)行第2行,賦值a=1; 然后第3行賦值b=2; 然后第4行賦值c=3; 然后第5行賦值d=4;
執(zhí)行第20行,調(diào)用outer(999)函數(shù),然后進(jìn)入outer(999)函數(shù)執(zhí)行環(huán)境,聲明提升,并將實(shí)參999傳給形參e;現(xiàn)在環(huán)境棧中有兩個(gè)執(zhí)行環(huán)境,outer(999)是當(dāng)前執(zhí)行環(huán)境;
執(zhí)行第16行,賦值a=5; 然后第17行賦值b=6;
執(zhí)行第18行,調(diào)用inner(7)函數(shù),然后進(jìn)入inner(7)函數(shù)執(zhí)行環(huán)境,聲明提升,并將實(shí)參7傳給形參d;
執(zhí)行第7行,賦值c=8; 然后運(yùn)算并輸出;
代碼優(yōu)化
由于在作用域鏈上查找變量是需要消耗性能的,我們應(yīng)該盡快的找到變量,所以在函數(shù)多層嵌套的時(shí)候,我們應(yīng)盡可能的使用函數(shù)內(nèi)部的局部變量;
我們?cè)诤瘮?shù)內(nèi)部使用全局變量可以說是一種跨作用域操作,如果某個(gè)跨作用域的值在函數(shù)的內(nèi)部被多次使用,那么我們就把它存儲(chǔ)到局部變量里,這樣可以提高性能。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript自定義localStorage監(jiān)聽事件的解決方法
在項(xiàng)目開發(fā)過程中,發(fā)現(xiàn)有很多時(shí)候進(jìn)行l(wèi)ocalStorage.setItem()操作設(shè)置本地存儲(chǔ)后,頁面必須刷新才能夠獲取到存儲(chǔ)數(shù)據(jù),為了解決這個(gè)問題,就必須要用到自定義localStorage監(jiān)聽事件了,所以本文給大家介紹了自定義localStorage監(jiān)聽事件,需要的朋友可以參考下2024-10-10微信小程序?qū)崿F(xiàn)多宮格抽獎(jiǎng)活動(dòng)
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)多宮格抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11JS實(shí)現(xiàn)適合于后臺(tái)使用的動(dòng)畫折疊菜單效果
這篇文章主要介紹了JS實(shí)現(xiàn)適合于后臺(tái)使用的動(dòng)畫折疊菜單效果,實(shí)例展示了兩種折疊菜單顯示效果,涉及JavaScript響應(yīng)鼠標(biāo)事件動(dòng)態(tài)遍歷及改變頁面元素樣式的實(shí)現(xiàn)技巧,需要的朋友可以參考下2015-09-09js實(shí)現(xiàn)添加可信站點(diǎn)、修改activex安全設(shè)置,禁用彈出窗口阻止程序
下面小編就為大家?guī)硪黄猨s實(shí)現(xiàn)添加可信站點(diǎn)、修改activex安全設(shè)置,禁用彈出窗口阻止程序。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-08-08微信小程序-可移動(dòng)菜單的實(shí)現(xiàn)過程詳解
這篇文章主要介紹了微信小程序-可移動(dòng)菜單的實(shí)現(xiàn)過程詳解,我們可以經(jīng)??吹绞謾C(jī)app里有的菜單欄是懸浮在首頁的,用戶可以拖動(dòng)和點(diǎn)擊菜單欄進(jìn)行交互,今天就教大家利用小程序的控件,,需要的朋友可以參考下2019-06-06