摘要:作用域的類別可以影響到變量的取值,分為詞法作用域靜態作用域和動態作用域。而,采用的就是詞法作用域,或者叫靜態作用域。
關于javascript中的作用域和作用域鏈
? ? ? ? 前面的文章說到, 執行上下文的創建階段,主要有三個內容:
? ? ? ? 1、創建變量對象;2、初始化作用域鏈;3、確定this的指向。
? ? ? ? 在這里,要說一下作用域和作用域鏈了,先來一個例子:
//全局環境 var a = 10; function inner(){ console.log(a); } inner();
? ? ? ? 在inner函數的執行上下文的執行階段中,它的VO(變量對象)都沒有var a這樣的變量聲明,所以console的時候,怎樣獲得a的值呢,就是通過全局環境中的AO(活動對象),因為里面就有a的值(不知道什么是VO和AO,一定要先看一下我前面的文章:關于javascript中的變量對象和活動對象)。
? ? ? ? 其實,作用域這個東西,可以理解為自身執行上下文中的活動對象(AO)可以被訪問的區域,說的有點拗口,其實看一下我前面的文章(關于javascript中的變量對象和活動對象),就可以知道,其實我們執行函數的時候,用到的變量值,都是從AO上面取到的,如果自己的執行上下文中的AO沒有對應要用的值(例如上面例子中的a),那就要往上一層的執行上下文中的AO中找這個值,如果上一層還沒有,就要再往上一層的執行上下文中的AO去找,而這個一層一層的鏈接關系,就是所謂的作用域鏈。(這里說到的上一層,其實就是執行上下文棧中壓著的下一層執行上下文,不理解可以先看我前面的文章:關于javascript中的從堆棧內存到執行上下文)
? ? ? ? 說到作用域這個東西,我覺得不少人都被它坑過,舉個例子:
//先聲明變量jj并賦值為10 var jj = 10; //再聲明一個函數what function what(){ console.log(jj); } //執行what函數 what();
? ? ? ? 相信大家都非常清楚打印結果了,就和上面例子一樣,就是10。那如果這樣呢:
//先聲明變量jj并賦值為10 var jj = 10; //再聲明一個函數what function what(){ console.log(jj); var jj = 20; console.log(jj); } //執行what函數 what();
? ? ? ? 是不是會說打印結果是10和20呢?那就錯了,實際打印結果是undefined和20。為什么呢?不是一開始打印時候前面沒有變量jj,然后向上找到等于10,后面就改變它的值,然后輸入20嗎?
? ? ? ? 這樣就沒有真正理解javascript的詞法作用域的概念。作用域的類別可以影響到變量的取值,分為詞法作用域(靜態作用域)和動態作用域。
? ? ? ? 它們的區別是:對于詞法作用域,函數的作用域在函數定義的時候就已經確定了,而動態作用域不同,在函數調用的時候才確定的。
? ? ? ? 而javascript,采用的就是詞法作用域,或者叫靜態作用域。
? ? ? ? 所以在what函數中聲明了一個var jj = 20,就將里面有jj這個變量名的取值,框住了在這個函數里面了,或者可以說,調用what函數的時候,你用var這樣的字眼聲明了jj這個變量,就會在執行上下文創建時候的變量對象VO中掛上了屬性jj=undefined,所以一開始就將jj打印出來,由于還沒有賦值,所以打印出undefined了,然后后面賦值了,就打印出了20了。
? ? ? ? 如果你想按照你一開始想的那樣打印出10和20,可以將what函數里面的var jj = 20改為jj = 20,去掉var,這樣就相當于what函數里面沒有聲明變量jj,而是向上找到jj,并將它打印,然后更改jj的值,再打印,實際上,這種做法會污染全局變量,因為你在what函數里面將jj這個全局變量的值改為20了。
? ? ? ? 好了,如果你明白因為用var聲明了變量,導致在自身的執行上下文中尋找jj的值而不是向上尋找,但是你不明白為什么var jj 明明在console之后才聲明的,為什么會受到它影響呢?這里,就要再說一個概念,叫做變量提升。
? ? ? ? 變量提升,就是解釋器會將函數聲明和變量聲明提升到方法體的最頂部,函數聲明比變量聲明提得更高。
? ? ? ? 其實很容易理解變量提升,還是回去看一下我前面的文章(關于javascript中的變量對象和活動對象)就知道了,執行上下文在創建的時候就會創建變量對象,而變量對象的創建順序為:形參、函數聲明、變量聲明(用var 聲明的),所以在你的代碼執行階段(執行上下文的執行階段)之前,它已經創建了變量對象了,所以相對其他的執行代碼來說,這就是所謂的變量提升。
? ? ? ? 說回去最初的執行what函數的地方,其實我這樣寫也是可以的:
//先聲明變量jj并賦值為10 var jj = 10; //執行what函數 what(); //現在才聲明一個函數what function what(){ console.log(jj); }
? ? ? ? 為什么呢?因為變量提升,解釋器會將聲明的what這個函數提到頂部,所以你上面執行what這個函數,實際解釋器已經將what函數提升上去了。
? ? ? ? 除了函數聲明,變量聲明也一樣。
? ? ? ? 回到前面的例子,我在what函數內聲明并初始化var jj = 20 可以看成兩個步驟,第一個步驟,聲明變量var jj ,第二個步驟,初始化變量,jj = 20,所以上面的函數可以寫成這樣:
function what(){ console.log(jj); var jj ; jj = 20; console.log(jj); }
? ? ? ? 當然了,這里面聲明的jj變量,也會變量提升,所以會變成這樣:
function what(){ var jj ; console.log(jj); jj = 20; console.log(jj); }
? ? ? ? 再結合回到前面一起:
//先聲明變量jj并賦值為10 var jj = 10; //再聲明一個函數what function what(){ var jj ; console.log(jj); jj = 20; console.log(jj); } //執行what函數 what();
? ? ? ? 是不是很好地理解了打印結果就是undefined 和 20了,這里要注意的是,初始化變量是不會提升的,所以jj = 20還是留在了原位。
? ? ? ? 換個方式說一下變量提升,下面兩個函數寫法有什么不同的地方:
//寫法一 var claim = function(){ console.log("i am first"); }; //寫法二 function claim(){ console.log("i am first"); }
? ? ? ? 舉一個例子就很清楚了:
//寫法一 var claim = function(){ console.log("i am first"); }; claim();//打印結果為i am first var claim = function(){ console.log("i am second"); }; claim();//打印結果為i am second
//寫法二 function claim (){ console.log("i am first"); }; claim();//打印結果為i am second function claim(){ console.log("i am second"); }; claim();//打印結果為i am second
? ? ? ? 好了,理解了上面兩種打印結果就知道了變量提升了。
? ? ? ? 其實作用域前面已經說得很清楚了,就是執行上下文的AO(活動對象)可被訪問的范圍,而作用域鏈可以類比原型鏈,自己如果沒有,就一級一級往上找,這個一級一級,就是執行上下文棧中壓著的下一個執行上下文(再回顧前面文章:關于javascript中的從堆棧內存到執行上下文),那就很容易理解明白了。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84320.html
前言 JavaScript中有一個被稱為作用域(Scope)的特性。雖然對于許多新手開發者來說,作用域的概念并不是很容易理解,本文我會盡我所能用最簡單的方式來解釋作用域和作用域鏈,希望大家有所收獲! 想閱讀更多優質文章請猛戳GitHub博客 作用域(Scope) 1.什么是作用域 作用域是在運行時代碼中的某些特定部分中變量,函數和對象的可訪問性。換句話說,作用域決定了代碼區塊中變量和其他資源的可見...
前言 JavaScript中有一個被稱為作用域(Scope)的特性。雖然對于許多新手開發者來說,作用域的概念并不是很容易理解,本文我會盡我所能用最簡單的方式來解釋作用域和作用域鏈,希望大家有所收獲! 想閱讀更多優質文章請猛戳GitHub博客 作用域(Scope) 1.什么是作用域 作用域是在運行時代碼中的某些特定部分中變量,函數和對象的可訪問性。換句話說,作用域決定了代碼區塊中變量和其他資源的可見...
摘要:注意由于閉包會額外的附帶函數的作用域內部匿名函數攜帶外部函數的作用域,因此,閉包會比其它函數多占用些內存空間,過度的使用可能會導致內存占用的增加。 作用域和作用域鏈是javascript中非常重要的特性,對于他們的理解直接關系到對于整個javascript體系的理解,而閉包又是對作用域的延伸,也是在實際開發中經常使用的一個特性,實際上,不僅僅是javascript,在很多語言中都...
摘要:在代碼執行時,對應的作用域鏈常常是保持靜態的。當語句執行完畢后,會把作用域鏈恢復到原始狀態。在全局作用域中創建的函數,其作用域鏈會自動成為全局作用域中的一員。 列表項目 前言 學習了javascript已經很久了,關于這個語言中的這兩個特性也是早已耳熟能詳,但是在實際的使用的過程中或者是遇到相關的問題的時候,還是不能很好的解決。因此我覺得很有必要深入的學習并且記錄這個問題,以便在今后的...
摘要:保存在堆內存中。因此改變一方,另一方也會發生相應的改變。作用域鏈當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈,以保證對執行環境有權訪問的所有變量和函數的有序訪問。 基本類型和引用類型的值 基本類型值:簡單的數據段 ,五種基本類型(Number Boolean String Null Undefined)的值都是基本類型值,基本類型的值在內存中大小固定,因此保存在棧內存中。引用...
閱讀 482·2019-08-30 15:44
閱讀 897·2019-08-30 10:55
閱讀 2729·2019-08-29 15:16
閱讀 924·2019-08-29 13:17
閱讀 2801·2019-08-26 13:27
閱讀 568·2019-08-26 11:53
閱讀 2119·2019-08-23 18:31
閱讀 1882·2019-08-23 18:23