深入學習js之——詞法作用域和動態作用域

開篇

當我們在開始學習任何一門語言的時候,都會接觸到變量的概念,變量的出現其實是爲了解決一個問題,爲的是存儲某些值,進而,存儲某些值的目的是爲了在之後對這個值進行訪問或者修改,正是這種存儲和訪問變量的能力將狀態給了程序。我們的程序中到處都充斥着對於狀態的判斷,根據不同的狀態執行不同的邏輯。

我們試想一下,如果沒有狀態這個概念,程序雖然也能夠執行一些簡單的任務,但是它會受到很多的限制,所能完成的功能是有限制的,舉個例子,沒有狀態你是如何執行循環語句?沒有狀態如何更加優雅地使用邏輯結構

仔細想想,好像是寸步難行,當然引入變量後幫我們解決了這個問題。

但是,引入變量和狀態的概念之後會引起幾個問題:這些變量住在哪裏?換句話說,它們存儲在哪裏?最重要的是,程序需要它們的時候如何找到它們?

今天我們就一起學習一下這套存儲和查找變量的規則,這套規則我們稱之爲:作用域。

作用域

我們來拆解一下這個詞語,所謂的“”我們可以理解爲:範圍、區域,加上“作用”兩個字所要表述的問題就是作用的範圍、區域,比如國家的行政區域劃分是爲了便於管理,類比到程序源代碼中作用域的出現也是爲了便於對於變量做管理。

好,這裏我們簡單做一下總結:

  • 定義:作用域是指程序源代碼中定義變量的區域。
  • 作用:作用域規定了如何查找變量,也就是確定當前執行代碼對變量的訪問權限。
  • 在javaScript中的應用 :JavaScript採用詞法作用域(lexical scoping),也就是靜態作用域

那什麼又是 詞法作用域或者靜態作用域呢?

請繼續往下看

靜態作用域與動態作用域

因爲javaScript採用的是詞法作用域,函數的作用域在函數定義的時候就決定了。
而詞法作用域相對的是動態作用域,函數的作用域是在函數調用的時候才決定的。
讓我們看一個例子來理解詞法作用域和動態作用域之間的區別:

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的代碼:

  • 1、定義一個變量 scope 並賦值 global scope;
  • 2、聲明一個函數 checkscope ,在這個函數中 定義一個變量 scope 並賦值 local scope;
  • 3、在checkscope 函數中 又定義一個函數 f ,這個函數 只做了一件事:返回scope 這個變量;
  • 4、最後返回並執行 f 這個函數;
  • 5、調用checkscope

按照我們上面解釋的javaScript中靜態作用域理解,在執行 checkscope 這個函數的時候在函數內部執行的是f 這個函數,首先在 f 這個函數內部查找 scope 這個變量發現沒有,繼續在定義函數f的上面一層查找,發現在checkscope 這個函數作用域內 找到了scope的值 直接返回,至於 checkscope外面定義的scope沒有理睬。

讓我們來分析一下上面例2的代碼:

  • 1、定義一個變量 scope 並賦值 global scope;
  • 2、聲明一個函數 checkscope 在這個函數中 定義一個變量 scope 並賦值 local scope;
  • 3、在checkscope 函數中 又定義一個函數 f 這個函數 只做了一件事:返回scope 這個變量;
  • 4、最後單純的返回 f 這個函數;
  • 5、調用checkscope

按照我們上面解釋的javaScript中靜態作用域理解,在執行 checkscope 這個函數的時候在函數內返回了函數f實際是在最外面調用的f但是由於javaScript是採用的詞法作用域,因此函數的作用域基於函數創建的位置。

而引用《JavaScript權威指南》的回答就是:

JavaScript 函數的執行用到了作用域鏈,這個作用域鏈是在函數定義的時候創建的。嵌套的函數 f() 定義在這個作用域鏈裏,其中的變量 scope 一定是局部變量,不管何時何地執行函數 f(),這種綁定在執行 f() 時依然有效。

但是在這裏真正想讓大家思考的是:

雖然兩段代碼執行的結果一樣,但是兩段代碼究竟有哪些不同呢?

敬請期待下面一篇關於javaScript 中的執行上下文棧的相關內容。

參考:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章