從閉包到es6變量聲明

以前學習閉包的時候碰到一道題javascript

var arr = [];
for (var i = 0; i < 10; i++){
    arr[i] = function(){
        return i
    }
}
console.log(arr[3]())  //10
複製代碼

var聲明的變量沒有塊級做用域。因此實際上這裏的i其實是定義在全局做用域下的java

console.log(window.i) //10es6

函數在循環以後調用。因此從做用域鏈中查找i,結果爲10segmentfault

若是要輸出3,可使用當即執行函數修改babel

var arr = []
for (var i = 0; i < 10; i++){
    arr[i] = (function(j){
    	return function(){
    		return j
    	}
    })(i)
}
console.log(arr[3]()) //3
複製代碼

當即執行函數傳入i,形參j在當前做用域保存了i的值。造成一個閉包。閉包

mdn對閉包的解釋函數

閉包是由函數以及建立該函數的詞法環境組合而成。這個環境包含了這個閉包建立時所能訪問的全部局部變量oop

這道題有個更優雅的解法,就是咱們今天的主角let學習

var arr = []
for (let i = 0; i < 10; i++){
    arr[i] = function(){
        return i
    }
}
console.log(arr[3]())  //3
複製代碼

阮一峯老師的是這麼解釋的:ui

上面代碼中,變量ilet聲明的,當前的i只在本輪循環有效,因此每一次循環的i其實都是一個新的變量,因此最後輸出的是6。你可能會問,若是每一輪循環的變量i都是從新聲明的,那它怎麼知道上一輪循環的值,從而計算出本輪循環的值?這是由於 JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量i時,就在上一輪循環的基礎上進行計算

babel轉換爲ES5是這樣的:

"use strict";

var arr = [];

function _loop(i) {
    arr[i] = function () {
        return i;
    };
};

for (var i = 0; i < 10; i++) {
    _loop(i);
}

console.log(arr[3]());
複製代碼

個人理解是建立一個函數,生成一個閉包存儲i。因此每次遍歷就能獲得對應的i

關於閉包,我以爲還有一個很重要的題目

function makeCounter() {
  var count = 0

  return function() {
    return count++
  };
}

var counter = makeCounter() 
var counter2 = makeCounter();
// 執行函數並賦值給counter,counter2
// counter, counter2對應的是不一樣的函數
// 他們都初始化了一個 count = 0
// 以前按引用類型指針理解,因此錯了

console.log( counter() ) // 0
console.log( counter() ) // 1

console.log( counter2() ) // 0
console.log( counter2() ) // 1
複製代碼

let 命令總結

  1. 塊級做用域

    {
        let a = 10;
        var b = 1;
    }
    a // a is not defined
    b // 1
    複製代碼
  2. 在全局做用域下let聲明的變量並不會綁定到window上

    let a = 1;
    var b = 2;
    window.a //undefined
    window.b //2
    複製代碼
  3. for循環除了上文說起的特性,還有一個特色就是父子做用域

    for (let i = 0; i < 3; i++){
        let i = 'abc';
        console.log(i)
    }
    
    複製代碼
  4. 不存在變量提高。因此必定要先聲明再使用

  5. 暫時性死區 ES6 明確規定,若是區塊中存在letconst命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。

    var tmp = 123;
    if (true){
        tmp = 'abc'; //ReferenceError
        let tmp;
    }
    複製代碼
  6. 不可重複聲明

const 命令總結

  1. const聲明一個只讀的常量。改變值會報錯,只聲明不賦值也會報錯。其餘特性與let相同

  2. 本質const實際上保證的,並非變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。對於引用類型來講,保存的只是指針。因此能夠改變對象,但不能改變指針

    const foo = {}
    foo.prop = 123  //ok
    foo = {} //報錯
    複製代碼

參考資料

阮一峯-es6入門

怎麼理解for循環中用let聲明的迭代變量每次是新的變量?

相關文章
相關標籤/搜索