當咱們在開始學習任何一門語言的時候,都會接觸到變量的概念,變量的出現實際上是爲了解決一個問題,爲的是存儲某些值,進而,存儲某些值的目的是爲了在以後對這個值進行訪問或者修改,正是這種存儲和訪問變量的能力將狀態給了程序。咱們的程序中處處都充斥着對於狀態的判斷,根據不一樣的狀態執行不一樣的邏輯。java
咱們試想一下,若是沒有狀態這個概念,程序雖然也可以執行一些簡單的任務,可是它會受到不少的限制,所能完成的功能是有限制的,舉個例子,沒有狀態你是如何執行循環語句?沒有狀態如何更加優雅地使用邏輯結構?git
仔細想一想,好像是步履維艱,固然引入變量後幫咱們解決了這個問題。github
可是,引入變量和狀態的概念以後會引發幾個問題:這些變量住在哪裏?換句話說,它們存儲在哪裏?最重要的是,程序須要它們的時候如何找到它們?bash
今天咱們就一塊兒學習一下這套存儲和查找變量的規則,這套規則咱們稱之爲:做用域。函數
咱們來拆解一下這個詞語,所謂的「域」咱們能夠理解爲:範圍、區域,加上「做用」兩個字所要表述的問題就是做用的範圍、區域,好比國家的行政區域劃分是爲了便於管理,類比到程序源代碼中做用域的出現也是爲了便於對於變量作管理。學習
好,這裏咱們簡單作一下總結:code
- 定義:做用域是指程序源代碼中定義變量的區域。
- 做用:做用域規定了如何查找變量,也就是肯定當前執行代碼對變量的訪問權限。
- 在javaScript中的應用 :JavaScript採用詞法做用域(lexical scoping),也就是靜態做用域。
那什麼又是 詞法做用域或者靜態做用域呢?ip
請繼續往下看作用域
由於javaScript採用的是詞法做用域,函數的做用域在函數定義的時候就決定了。
而詞法做用域相對的是動態做用域,函數的做用域是在函數調用的時候才決定的。
讓咱們看一個例子來理解詞法做用域和動態做用域之間的區別:get
var value = 1; function foo() { console.log(value); } function bar() { var value = 2; foo(); } bar(); // 結果是 ???
上面的代碼中:
- 1.咱們首先定義了一個value,並賦值爲1;
- 2.聲明一個函數foo,函數的功能是打印 value 這個變量的值;
- 3.聲明一個函數bar,函數內部從新建立了一個變量 value 這個變量賦值爲2;
在函數內部執行了 foo() 這個函數;- 4.執行 bar() 這個函數
假設javaScript採用靜態做用域,讓咱們分析下執行過程:
執行foo函數,首先從 foo 函數內部查找是否有變量 value ,若是沒有
就根據書寫的位置,查找上面一層的代碼,咱們發現value等於1,因此結果會打印 1。
假設javaScript採用動態做用域,讓咱們分析下執行過程:
執行foo函數,依然是從 foo 函數內部查找是否有局部變量 value。若是沒有,
就從調用函數的做用域,也就是 bar 函數內部查找 value 變量,因此結果會打印 2。
上面在區分靜態做用於和動態做用域的時候,咱們已經說了若是是靜態做用域,那麼函數在書寫定義的時候已經肯定了,而動態做用域是函數執行過程當中才肯定的。
JavaScript採用的是靜態做用域,因此這個例子的結果是 1。
咱們在控制檯中輸入執行上面的函數,檢驗一下執行結果果真是 1。
那什麼語言是採用的動態的做用域呢? 其實bash 就是動態做用域,
咱們能夠新建一個 scope.bash 文件將下列代碼放進去,執行一下這個腳本文件:
#!/bin/bash value=1 function foo () { echo $value; } function bar () { local value=2; foo; } bar
上面代碼運行的結果輸出2很好解釋,雖然在代碼最上層定義了 value並賦值爲1,可是在調用foo函數的時候,在查找 foo 內部沒有 value 變量後,會在foo 函數執行的環境中繼續查找,也就是在bar 函數中查找,很幸運咱們找到了。
最後,讓咱們看一個《JavaScript權威指南》中的例子:
// 例1: var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f(); } checkscope(); // 例2: var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } checkscope()();
讓咱們來分析一下上面例1的代碼:
- 一、定義一個變量 scope 並賦值 global scope;
- 二、聲明一個函數 checkscope ,在這個函數中 定義一個變量 scope 並賦值 local scope;
- 三、在checkscope 函數中 又定義一個函數 f ,這個函數 只作了一件事:返回scope 這個變量;
- 四、最後返回並執行 f 這個函數;
- 五、調用checkscope
按照咱們上面解釋的javaScript中靜態做用域理解,在執行 checkscope 這個函數的時候在函數內部執行的是f 這個函數,首先在 f 這個函數內部查找 scope 這個變量發現沒有,繼續在定義函數f的上面一層查找,發如今checkscope 這個函數做用域內 找到了scope的值 直接返回,至於 checkscope外面定義的scope沒有理睬。
讓咱們來分析一下上面例2的代碼:
- 一、定義一個變量 scope 並賦值 global scope;
- 二、聲明一個函數 checkscope 在這個函數中 定義一個變量 scope 並賦值 local scope;
- 三、在checkscope 函數中 又定義一個函數 f 這個函數 只作了一件事:返回scope 這個變量;
- 四、最後單純的返回 f 這個函數;
- 五、調用checkscope
按照咱們上面解釋的javaScript中靜態做用域理解,在執行 checkscope 這個函數的時候在函數內返回了函數f實際是在最外面調用的f可是因爲javaScript是採用的詞法做用域,所以函數的做用域基於函數建立的位置。
而引用《JavaScript權威指南》的回答就是:
JavaScript 函數的執行用到了做用域鏈,這個做用域鏈是在函數定義的時候建立的。嵌套的函數 f() 定義在這個做用域鏈裏,其中的變量 scope 必定是局部變量,無論什麼時候何地執行函數 f(),這種綁定在執行 f() 時依然有效。
可是在這裏真正想讓你們思考的是:
雖然兩段代碼執行的結果同樣,可是兩段代碼究竟有哪些不一樣呢?
敬請期待下面一篇關於javaScript 中的執行上下文棧的相關內容。
參考:
- 一、《你不知道的Javascript上卷》
- 二、JavaScript深刻之詞法做用域和動態做用域