以前學習閉包的時候碰到一道題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) //10
es6
函數在循環以後調用。因此從做用域鏈中查找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
上面代碼中,變量
i
是let
聲明的,當前的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 a = 10;
var b = 1;
}
a // a is not defined
b // 1
複製代碼
在全局做用域下let聲明的變量並不會綁定到window上
let a = 1;
var b = 2;
window.a //undefined
window.b //2
複製代碼
for循環除了上文說起的特性,還有一個特色就是父子做用域
for (let i = 0; i < 3; i++){
let i = 'abc';
console.log(i)
}
複製代碼
不存在變量提高。因此必定要先聲明再使用
暫時性死區 ES6 明確規定,若是區塊中存在let
和const
命令,這個區塊對這些命令聲明的變量,從一開始就造成了封閉做用域。凡是在聲明以前就使用這些變量,就會報錯。
var tmp = 123;
if (true){
tmp = 'abc'; //ReferenceError
let tmp;
}
複製代碼
不可重複聲明
const聲明一個只讀的常量。改變值會報錯,只聲明不賦值也會報錯。其餘特性與let相同
本質:const
實際上保證的,並非變量的值不得改動,而是變量指向的那個內存地址所保存的數據不得改動。對於引用類型來講,保存的只是指針。因此能夠改變對象,但不能改變指針
const foo = {}
foo.prop = 123 //ok
foo = {} //報錯
複製代碼
參考資料