好記性不如爛筆頭,在寫作的程序中自己消化吸收,所以寫下這個JavaScript學習系列文章,文章中的文字都是自己理解的內容,各位酌情觀看,
接上節作用域,插播一個閉包
在《你不知道的JavaScript——上》中看到一個非常常見的題目,但是之前一直都是迷迷糊糊,這里講的更透徹、更方便理解,上代碼
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
講閉包概念的時候,這段代碼是不少見了,答案我都記下了,可是在深入一些,要怎么改,改的思路是什么就有點模模糊糊了,好,這里的答案就是全部輸入都是6;
我覺得要理解這個結果還需要一些js的事件回圈的初步認識,js是個單執行緒語言,代碼一般都是從上往下依次執行的(上一章節提到編譯階段存在變數提升,不是嚴格的依次),當遇到setTimeout的時候會把這個任務放到宏任務佇列中(不需要太多理解,就是排隊稍后執行),主程式走的同步任務,因為這里同步任務就是構建了幾個 setTimeout...,所以代碼就簡化成這樣了
for (var i=1; i<=5; i++) {
setTimeout(...);
}
//執行完之后 排隊的setTimeout就可以執行了
setTimeout( function timer() {
console.log( i );
}, i*1000 );
所以到setTimeout執行的時候,for回圈已經走完了,i變數已經自增到了6,setTimeout里面的代碼列印i的時候使用的全域變數i,所以列印出來的結果就是5個6,怎么改成先要的1 2 3 4 5這種結果呢?書中給了比較詳細的思路程序:
首先確定了導致輸出6的原因是因為參考了同一個i,要想得到想要的效果,就要構建閉包作用域,而閉包,當函式可以記住并訪問所在的詞法作用域時,就產生了閉包,那我們創建一個立即執行函式來創建作用域,
for (var i=1; i<=5; i++) {
(function() {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
})();
}
這樣就可以了嗎?
我們現在顯然擁有更多的詞法作用域了,每個延遲函式都會將立即執行函式在每次迭代中創建的作用域封閉起來, 如果作用域是空的,那么僅僅將它們進行封閉是不夠的,參考的i還是那個全域作用域的i,我們需要有自己的變數將i存盤下來,改進如下:
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}
這樣就可以了,更進一步:
for (var i=1; i<=5; i++) {
(function(j) {
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})( i );
}
更高級:
我們使用 IIFE(立即執行函式) 在每次迭代時都創建一個新的作用 域,換句話說,每次迭代我們都需要一個塊作用域,第 3 章介紹了 let 宣告,可以用來劫 持塊作用域,并且在這個塊作用域中宣告一個變數,
本質上這是將一個塊轉換成一個可以被關閉的作用域,因此,下面這些看起來很酷的代碼 就可以正常運行了:
for (var i=1; i<=5; i++) { let j = i; // 是的,閉包的塊作用域! setTimeout( function timer() { console.log( j ); }, j*1000 ); }但是,這還不是全部!(我用 Bob Barker 6 的聲音說道)for 回圈頭部的 let 宣告還會有一 個特殊的行為,這個行為指出變數在回圈程序中不止被宣告一次,每次迭代都會宣告,隨 后的每個迭代都會使用上一個迭代結束時的值來初始化這個變數,
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }很酷是吧?塊作用域和閉包聯手便可天下無敵,不知道你是什么情況,反正這個功能讓我 成為了一名快樂的 JavaScript 程式員,
這段這么長的參考是我對這里理解的不夠透徹,所以先把原文摘錄下來,然后我寫下我的理解:
塊作用域就是{},而let用于塊作用域,所以上面參考的第一段代碼就可以逐步決議為
var i=1;
{
let j = i; //塊級宣告
setTimeout(...,j);//塊級變數參考
}
i++;
{
let j = i; //塊級宣告
setTimeout(...,j);//塊級變數參考
}
i++;
.......
//括號和括號之間都是相對獨立的塊級作用域,因此互不想通,即便用的變數的名稱看起來是一樣的
//對比————如果是之前的var 全域作用域,那么就變成了
var i=1;
{
setTimeout(...,i);
}
i++;
{
setTimeout(...,i);
}
i++;
.......
//自然就參考的同一個全域變數i
這樣理解就再也不會迷糊了,第二段代碼說的無非就是在for的結構體中,let變數也是每個塊{}中都會重新定義一下,跟上面的代碼結構類似,(感謝自己寫這篇博文,自己的理解更深刻了~~)
還介紹一篇很深刻的文章《破解前端面試(80% 應聘者不及格系列):從閉包說起》,對ES6、和ES7比較熟悉的也可以看看,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/345770.html
標籤:其他
上一篇:java版Spring Cloud+SpringBoot+mybatis+uniapp b2b2c 多商戶入駐商城 直播 電子商務之全渠道資料庫高可用
