在開始本文之前我們先來看一段代碼 顯然這段代碼輸出10,並沒有向我們期望的返回3,原因也很簡單(js的變數提升)函數在調用時候訪問的是一個全局作用域的i,此時for迴圈已經執行完畢,全局變數i=10; 在ES5標準中,我們要想返回期望的3,通常的做法也很簡單,就是讓數組中的每個函數有單獨的作用域,那 ...
在開始本文之前我們先來看一段代碼
for(var i=0;i<10;i++){ arr[i]=function(){ return i; } } console.log(arr[3]());//10
顯然這段代碼輸出10,並沒有向我們期望的返回3,原因也很簡單(js的變數提升)函數在調用時候訪問的是一個全局作用域的i,此時for迴圈已經執行完畢,全局變數i=10;
在ES5標準中,我們要想返回期望的3,通常的做法也很簡單,就是讓數組中的每個函數有單獨的作用域,那麼我們只要構造一個立即執行函數即可(js中沒有塊級作用域,只區分函數作用域和全局作用域)就像下麵這樣:
var array=[]; for(var i=0;i<10;i++){ array[i]=(function(i){ return function(){ return i; } })(i); } console.log(array[3]());//3
這樣一來數組的每個函數就處於一個立即執行函數的函數作用域中,該立即執行函數傳入i,其實for迴圈執行瞭如下代碼:
array[0]=(function(i){ return function(){ return i; } })(0); array[1]=(function(i){ return function(){ return i; } })(1); array[2]=(function(i){ return function(){ return i; } })(2); ……
這樣一來,數字組中每個函數對應一個單獨的函數作用域(立即執行函數的)這裡共創建了10個函數作用域,這些函數作用域里的i值就是執行時候傳入的0……9,當執行
array[3]();時候函數訪問的i值是其對應的立即執行函數作用域里的 i,而不是全局的i值,這樣我們就得到了預期的效果。
說得到這裡我們簡單來說一下閉包,閉包可以理解為一個閉包就是一個沒有釋放資源的棧區,棧區內的變數處於激活狀態。上面的例子中for迴圈在執行時系統分配記憶體,js執行線程創建執行棧區,執行時候檢測到立即執行函數里的變數i被內部函數引用,所以該棧區在記憶體中沒有被釋放,函數(數組元素)被調用時候根據作用鏈首先訪問到的是上一級作用域(立即執行函數)的變數。
這裡不再詳細介紹閉包,如果想詳細瞭解閉包請閱讀《javascript高級程式設計》第7章
前面提到js中並沒有塊級作用域,只區分全局作用域和函數作用域,在ES6中let實際是為js新增了塊級作用域,例如下麵代碼不用創造函數作用域就可以讓每個數組裡的函數訪問各自作用域里的值:
let arr=[]; for(let i=0;i<10;i++){ arr[i]=function(){ return i; } } console.log(arr[3]());//3
可以看到我們並沒有像之前那樣構造一個函數作用域就能實現我們期望的效果,引入塊級作用域之後更方便我們書寫和理解代碼,上述代碼中for迴圈之後的{}是塊級作用域,每次迴圈時候每個返回的函數引用的是其對應塊作用域的變數,稍微改一下代碼看著形象些:
let arr=[]; for(let i=0;i<10;i++){ let k=i; arr[k]=function(){ return k; } } console.log(arr[3]());//3
可見ES6引入塊作用域之後我們構造閉包函數更方便了。
這裡不多敘述let和const的相關內容,如果之前沒接觸ES6的小伙伴建議閱讀阮一峰老師的《ES6標準入門》。
在這裡再提一點,很多人看完概念之後,第一印象都是:“const 是表示不可變的值,而 let 則是用來替換原來的 var 的。”很多時候把let當做是var的替代品,凡是聲明變數就用let,你很可能寫出下麵代碼:
// 定義常量 const REG_GET_INPUT = /^\d{1,3}$/; // 定義配置項 let config = { isDev : false, pubDir: './admin/' } let path = require('path'); let HtmlWebpackPlugin = require('html-webpack-plugin'); let CleanWebpackPlugin = require('clean-webpack-plugin');
const 的定義是不可重新賦值的值,與不可變的值(immutable value)不同;const 定義的 Object,在定義之後仍可以修改其屬性。
所以其實他的使用場景很廣,包括常量、配置項以及引用的組件、定義的 “大部分” 中間變數等,都應該以cosnt做定義。反之就 let 而言,他的使用場景應該是相對較少的,我們只會在 loop(for,while 迴圈)及少量必須重定義的變數上用到他。
猜想:就執行效率而言,const 由於不可以重新賦值的特性,所以可以做更多語法靜態分析方面的優化,從而有更高的執行效率。
所以上面代碼中,所有使用 let 的部分,其實都應該是用 const 的。