官术网_书友最值得收藏!

3.2 變量和變量作用域

3.2.1 變量

變量用來存儲值或表達式,是存儲數據的容器,示例如下。

      a = 1;
      a = a + 1; // -> 2

變量名稱的命名規則與標識符類似。

3.2.2 聲明變量

在JavaScript中,有多種聲明變量的方式。

  • var
  • let
  • const
1. var

var聲明一個變量,可以在聲明變量的時候為其賦值,示例如下。

      var a = 1;

上面的代碼很好理解,定義了一個名稱為a,值為1的變量,我們也可以先聲明變量,在必要的時候再對其進行賦值操作,示例如下。

      var a; //聲明變量a
      a = 1; //把數字1賦值給變量a

聲明多個變量的方式如下。

      var a;
      var b = 1;

聲明多個變量時,也可以只使用一次var關鍵字,簡寫成:

      var a , b = 1;
2. 變量的作用域

ECMAScript使用的是詞法作用域(Lexical scoping,又稱“靜態作用域”),其變量又稱為“詞法變量”,詞法變量在變量聲明時確定其有效范圍,這一有效范圍就是變量的作用域(scope),在作用域外,該變量不可見。

通常,作用域是一個函數,示例如下。

ES6增加了塊級作用域:

在函數作用域和塊級作用域內聲明的變量叫作“局部變量”,除此之外的變量叫作“全局變量”。

局部變量只能在該函數或塊級作用域內被訪問;全局變量可以在當前文檔內的任何位置訪問。

全局變量其實是global對象(瀏覽器環境下,global對象指的是window對象)的屬性,可以通過window['變量名]訪問。

· 作用域鏈

當代碼在一個執行環境中執行時,會創建變量對象(又稱“活動對象”,activation object,包含形參、函數聲明、變量聲明)的一個作用域鏈,作用域鏈的最前端,始終都是當前執行的代碼所在執行環境的變量對象(如果執行環境是函數,變量對象以arguments初始化),作用域鏈的下一個對象來自包含(外部)的執行環境,再下一個則來自下一個包含執行環境,這樣一直追溯到全局執行環境global。

· 執行環境

執行環境又稱“執行上下文”,JavaScript在執行代碼時,會創建一個執行環境,該執行環境會成為當前的執行環境,每個執行環境包含3部分:

  • 詞法環境,即作用域鏈。
  • 變量環境,即聲明的變量。
  • this綁定。

也就是說,在執行代碼時,其執行環境就已經對詞法環境、變量環境和this進行了初始化操作。

· 聲明提前(var hoisting)

我們先看以下一段代碼。

      console.log(a); // > undefined
      console.log(b); // > Uncaught ReferenceError: b is not defined
      var a = 1;
      console.log(a); // > 1

查看變量的生命周期,變量的生命周期可以理解為3部分。

① 聲明階段,為變量創建存儲空間。

② 初始化階段,變量值被初始化為undefined。

③ 賦值階段,執行賦值操作。

聲明提前是指在進入變量的作用域時,立即完成變量的聲明階段和初始化階段。

因此,上述代碼可以看成:

其中,聲明階段就位于執行環境創建時,因此,在尚未執行代碼前,聲明的變量其值均為undefined。

3. let與const

ES6新增了兩個定義變量的關鍵字——let與const,用來取代var。

· let

let的用法與var類似,但let聲明的變量具有塊級作用域,即let聲明的變量只在當前代碼塊內有效,示例如下。

上述代碼中,分別使用var和let在一個代碼塊內聲明一個變量,之后在代碼塊外訪問這兩個變量,使用var聲明的變量可以正常訪問,使用let聲明的變量則報錯,這說明let聲明的變量只在當前代碼塊內有效。

· 暫時性死區(temporal dead zone)

let和const聲明的變量擁有暫時性死區(TDZ),即在進入它的作用域后,變量無法被訪問,直到聲明結束,示例如下。

上述代碼中,分別使用var和let以及非關鍵字的方式聲明一個變量,并在變量聲明前,嘗試訪問這個變量,使用var聲明的變量返回undefined,使用let和非關鍵字的方式聲明的變量拋出ReferenceError,這表明使用let聲明的變量不存在聲明提前。

之后,對a、b、c三個變量進行賦值操作,變量a和c順利完成賦值,并返回相應值,但對使用let聲明的變量b進行賦值時,拋出了異常,對照變量c,這表明變量b此時存在但不能進行賦值操作,也就是說變量b此時完成了聲明階段,但不能被正常訪問。

下面使用let聲明變量b,此后,再次訪問變量b時,盡管沒有對變量b進行賦值操作,但依然可以獲取變量b的值,這表明在聲明結束之前,變量b已經完成了聲明階段、初始化階段和賦值階段。

綜上,使用let聲明的變量在進入其作用域后,立即完成變量的聲明階段和初始化階段(如果有賦值操作,也會完成賦值階段),但在變量聲明結束前,無法對變量進行操作。

使用let聲明的變量在進入其作用域后,直到聲明結束前的這塊語法區間稱為“暫時性死區”(TDZ)。

聲明結束前不能訪問,意味著死區并不是基于空間的,而是基于時間的,由以下示例可以看出。

· const

const的用法和特性與let基本相同,不同之處在于,const聲明的是一個只讀常量,被const聲明的變量不能被重新聲明或賦值。換句話說,它將不能再被改變(對于引用類型的數據,其地址指向不可修改,屬性可修改),示例如下。

對于引用類型的數據,即便是將其地址指向修改為自身的地址也會拋出異常,示例如下。

      const arr  = [];
      const arr2 = arr;
      arr = arr2; //拋出異常> Uncaught TypeError: Assignment to constant variable

這個示例之后,聲明了一個變量arr,并將arr中存儲的堆中的地址賦值給另一個變量arr2,此時arr2和arr中存放的地址指向堆中的同一個對象,之后,將arr的地址修改為arr2的地址,即將arr的地址修改為自身的地址,此時,控制臺拋出異常,這表示被const聲明的數組或對象的地址指向不可修改。

4. 重復聲明

在相同作用域內,let和const聲明的變量不允許有重復聲明,示例如下。

3.2.3 非聲明變量

非聲明變量是指不使用關鍵字聲明的變量,示例如下。

      b = 1; //嚴格模式下會拋出ReferenceError: b is not defined
      console.log(window.b); // > 1

非聲明變量會被掛載到global對象(瀏覽器環境下,global對象指的是window對象)的屬性上,因此可以通過window訪問。

· var聲明變量和非聲明變量的區別

示例如下。

刪除情況,示例如下。

      console.log(typeof a); // > string
      console.log(typeof b); // > undefined

delete操作符可以刪除一個對象的屬性,但如果屬性是一個不可配置(non-configurable)屬性,刪除時則會返回false(嚴格模式下刪除一個不可配置的變量會拋出異常)。

這就表示使用var聲明的變量是不可配置的,我們可以使用getOwnPropertyDescriptor方法來獲取描述屬性特性的對象,以此驗證這一點,示例如下。

      Object.getOwnPropertyDescriptor(window, "a");
      // -> {value: "a", writable: true, enumerable: true, configurable: false}

Object.getOwnPropertyDescriptor(window, "b"); // -> {value: "b", writable: true, enumerable: true, configurable: true}

兩者的根本區別在于關鍵字var聲明的變量是不可配置的,不能通過delete操作符刪除。

需要注意的是,configurable值一旦為false,描述屬性特性的對象就不能被修改,因此不能通過修改屬性描述符使var聲明的變量能被delete刪除,但反過來,可以使非聲明變量也不能被delete刪除,示例如下。

現在,你已經學會了多種聲明變量的方式,但不同的聲明方式應該在什么情況下使用呢?

建議是,盡量使用const,我們已經知道,let和const聲明的變量是不能被重復聲明的,但let所聲明的變量可以被重新賦值,const可以避免意外情況(例如,沒有使用關鍵字聲明變量)下對變量進行賦值操作,導致程序出現錯誤,而如果想要改變變量,就使用let聲明變量。

在ES6中,避免使用var。

練習

  • 使用不同的方式聲明一個變量。
主站蜘蛛池模板: 大港区| 开平市| 汉沽区| 衡山县| 桂平市| 观塘区| 霍州市| 宁城县| 巴塘县| 五指山市| 大安市| 长丰县| 涪陵区| 南阳市| 武陟县| 阳信县| 濮阳县| 临城县| 鹿泉市| 井冈山市| 浦江县| 开远市| 通海县| 静宁县| 简阳市| 花垣县| 从化市| 沙田区| 饶阳县| 崇义县| 射阳县| 元谋县| 靖宇县| 县级市| 铜山县| 秦安县| 安义县| 明水县| 曲松县| 海淀区| 哈尔滨市|