標籤:
一 : 範圍的相關概念
首先看下 變數範圍 的概念:一個變數的範圍是程式原始碼中定義這個變數的地區。————————《javascript權威指南》第六版
全域變數擁有全域範圍,函數體內定義的局部變數擁有函數範圍。
就個人理解,範圍(scope),顧名思義,是一塊地區 或 領域 ,並且有某些對象(包括變數,屬性,方法等)能夠在這裡起作用;範圍是在定義的時候決定的,和什麼時候執行無關;這時候問題來了:
問題1:地區在那兒?
他是一個概念,是看不到摸不著的;在這個地區內原本是什麼都沒有的,但是與範圍密切相關的一個概念 執行內容(EC),他卻是與範圍相呼應,哪些變數、參數、this值等統統的會儲存在上下文中(上下文是一個對象,變數參數等作為他的屬性儲存);全域範圍 對應 全域執行內容,函數範圍 對應相應的 函數執行內容; 而上下文是在代碼執行的時候產生的,他儲存在記憶體中,這個上下文中的變數等 則屬於 其相對應的範圍(全域範圍或函數範圍);詳見下一節【執行內容】。
問題1:某些對象是誰?
這塊地區中定義了誰,對象就有誰。全域範圍中定義了變數a和聲明了函數 fun,那麼a和fun就在全域範圍中起作用;fun域中定義了變數m ,則m 就在fun域中起作用(這時候變數a 也是起作用的,原理可查看後面範圍鏈 概念);另外還有this值,當前函數參數;主要就包含這三類對象。
問題2:這些對象啥時候起作用?
執行時,指全域函數執行時,產生執行內容,變數等起作用;局部函數執行時,產生函數執行內容,函數體內變數等起作用;詳細查看 執行內容。
js變數具有 的 向下透明 和 向上封閉 特性,向下透明,指fun,fn可以在當前範圍訪問全域變數a,fn可以訪問a,m,但在函數體內,局部變數的優先順序高於同名上級變數。 向上封閉,指全域範圍 不可以訪問 fun域中的變數m,更不可以訪問 fn中的變數n,fun範圍也不可以訪問他內部嵌套的fn域中變數n;還涉及到的一個概念就是 範圍鏈(scope chain):是一個對象列表或者鏈表,這組對象定義了這段代碼“範圍中”的變數。
當全域範圍中沒有函數定義時,範圍鏈上就只有一個對象,全域對象,這個對象定義了相關的變數資訊;一般所謂鏈,都是指多個穿起來才叫鏈,這兒比較特殊,是只有一個元素的鏈。
當全域範圍中包含多個嵌套函數時,隨著函數一級一級執行展開,範圍鏈將會變成由多個對象組成的鏈表;這個鏈表是動態,隨著代碼的執行而不停的增加和釋放記憶體。
可見,向下透明,向上封閉的原理,例如b變數儲存的值是一個指標,那麼在a域內,大家能看到的就是這個指標值,而不能看到b域內的變數,因此 b域 相對於a域是封閉的,也就是向上封閉;說到這兒,是不是感覺有點和閉包相似呢,閉包就是封閉的,只留一個介面(等同於a)給外界,包裡都有啥不讓別人直接看見。js之所以會出現閉包,可能就是因為這種儲存機製造成的——變數儲存為參考型別!閉包就是這樣,但凡函數傳回值是參考型別,都會產生閉包。
js是基於 詞法範圍 的語言:通過閱讀包含變數定義在內的數行源碼,就能知道變數的範圍。
二 :範圍的分類
範圍 可分為兩種,全域範圍 和 函數範圍;函數範圍是由函數建立的,至於全域範圍,我們也可以將全域範圍當作是 window函數 建立的,並且這個函數只有一個,而我們所寫的所有代碼都在這個函數之內。所以總結一句話:只有 函數 才能建立範圍
引申:這個全域函數 和 pareInt()等全域函數是什麼關係呢?
默全域範圍從代碼運行開始就一直存在著;
函數範圍:
變數在聲明他們的函數體以及這個函數體內嵌套的任意函數體內都是有定義的。
在函數內聲明的所有的變數在函數體內始終是可見的。
-------------《javascript權威指南》第六版
三個要點:所有變數 、 始終可見、都有定義
1、所有變數 ———— 函數參數 + 函數體變數+ for迴圈/if 語句/while語句 變數
如中所標出的所有變數,都 屬於當前 函數範圍,這些變數向下透明,而向上封閉,既fn2函數體內可以訪問fun函數以及全域變數,但是全域和fun卻不能直接存取fn2中的變數。
註:一段帶有大括弧的代碼,經常會被稱為 代碼塊;而這種稱呼,在Js中可能會帶來誤解;JS中 沒有 塊級範圍 ,這個概念與其他程式設計語言(如C等), 是不一樣的;如果一定要用代碼塊來理解的話,js 只識別 全域代碼塊 和 函數代碼塊 當作其範圍。
例如下C++代碼:
void fun(int x){ int a; //變數a的範圍在函數fun中 for(int i=0;i<=10;i++){i++} //變數i在塊級範圍 for 代碼塊中}
2、始終可見—————— 指變數在書寫 聲明原始碼 之前已經可用
js中聲明包含兩種聲明 變數聲明(var) 和 函式宣告 (function);變數聲明時,只定義了變數名,類型不確定;函式宣告時,將函數名當作變數名,類型是函數。
這個特性被稱為 聲明提前 ,即當前函數體內的聲明所有變數(不涉及賦值)代碼,都被提前到函數體頂部;通俗的說,就是指只要是在函數體內聲明了變數,不論代碼寫在了什麼位置,在函數體內的任意位置都可以訪問到。事實上這和Js引擎規定如何執行代碼順序有關,他會先尋找有關聲明的關鍵詞var 和 function 來儲存在執行內容中,這個工作是在 形成執行內容 的初始化階段 完成的,而賦值則是在
舉例,執行如下代碼:
function fn(){console.log(a); //underfinedvar a=3;}
fn();
等同於下面代碼:
function fn(){var a; //變數聲明提前console.log(a); //變數已聲明未賦值時預設值是underfineda=3; }
fn();
引申疑問:聲明提前 這步工作 是在什麼時候做的呢?
這就涉及到了另外一個概念,執行內容環境 ,變數/函數的聲明 工作都是在 代碼執行的時候才做的;
例如 檔案運行開始,就產生了 全域執行內容環境,這個環境的產生分為兩個階段完成,分別是 初始化階段 + 賦值階段 ,那麼聲明提前 就包含在 初始化階段中。詳情見下一節。
3、都有定義—————— 變數在當前函數體內有定義
根據定義,變數在聲明他們的函數體以及這個函數體內嵌套的任意函數體內都是有定義的,可見,函數體內不論包括啥函數,包括多少層函數,當前函數體內定義的變數一直在眾多嵌套函數中是有效可用的;也就是上面提到的向下透明特性。
JS函數——範圍