38.循環(huán)索引同步:利用自執(zhí)行函數(shù),閉包

為什么要同步

  • 因為不同步啊,和預期不一樣,如何同步?同步的意思就是i=0,輸出0,i=1,輸出1
for(var i = 0; i < 3; i++){ // 0 1 2 3
        function test() {
            console.log(i); // 3
        }//注意,函數(shù)并沒有調用執(zhí)行!??!,只是創(chuàng)建函數(shù)而已
    }
    // console.log(i); // 3
    test();
  • 注意,for循環(huán)里面函數(shù)并沒有調用執(zhí)行!??!,只是創(chuàng)建函數(shù)而已
  • 默認情況下通過var定義的變量,只要不是定義在函數(shù)中都是全局變量,所以i是全局變量
  • for執(zhí)行三次,就是說會創(chuàng)建三個test函數(shù)(處于全局作用域中),而后創(chuàng)建的同名函數(shù)會覆蓋先前創(chuàng)建的同名函數(shù),那么當for執(zhí)行完時,i為3【i是全局變量】,==只有一個test函數(shù)(其他被覆蓋了),所以調用test函數(shù)時【順序執(zhí)行嘛,for已執(zhí)行完,創(chuàng)建test函數(shù)時,在for里面并沒有執(zhí)行test】,i=3
  • 每一個test函數(shù)都是屬于window的, 這也是test();能執(zhí)行的原因
  • 和預期很不同:想i等于0,輸出0,i=1,輸出1

循環(huán)索引同步方案一

for(var i = 0; i < 3; i++){ // 0 1 2 3
        function test() {
            console.log(i); // 0 1 2
        }
        test();
    }
    等價于下面【立即執(zhí)行函數(shù)】
       for(var i = 0; i < 3; i++){ // 0 1 2 3
        (function test() {
            console.log(i); // 0 1 2
        })();
    }
    
    又等價于下面
     for(var i = 0; i < 3; i++){ // 0 1 2 3
        // function test(index) { // var index = i;
        //     console.log(index); // 0 1 2
        // }
        // test(i);
        (function test(index) {
            console.log(index); // 0 1 2
        })(i);
    }
  • 注意最后一種寫法:
(function test(index) {
            console.log(index); // 0 1 2
        })
  • 實質上,這玩意不太容易看懂,學名叫自執(zhí)行函數(shù)((立即調用的函數(shù)表達式)),有兩種形式
    部分內容參考自淺談自執(zhí)行函數(shù)
// 方式一
    (function fun4(){
        console.log("fun4");
    }()); // "fun4"

  • 因為在JavaScript語言中,()里面不能包含語句(只能是表達式),所以解析器在解析到function關鍵字的時候,會把它們當作function表達式,而不是正常的函數(shù)聲明。

js中的表達式(expression):js中的一個短語,js解釋器會將其計算出一個結果。程序中的常量是最簡單的一類表達式。聯(lián)想,算數(shù)表達式,邏輯表達式都會返回一個結果。

  • 既然解析器在解析到function關鍵字的時候,把它們當作function表達式,既然是表達式,那么也會返回一個結果,假設為一個地址值fn,那么就相當于下面那樣了,
let fn=function fun4(){
        console.log("fun4");
    }
  • 似曾相識有沒有?加上后面的(),即fn(),這就調用了函數(shù)了,所以學名叫自執(zhí)行函數(shù)((立即調用的函數(shù)表達式))
    // 方式二
    (function fun5(){
        console.log("fun5");
    })();// "fun4"
    
  • 寫法上建議采用方式一(這是參考文的建議。但實際上,我個人覺得方式二比較常見)。(上面鏈接的原話)

循環(huán)索引同步練習(運用方案一)

let oBtns = document.querySelectorAll("button");
    for(var i = 0; i < oBtns.length; i++) {
        let btn = oBtns[i];
        (function test(index) { // var index = i;
            // console.log(index); // 0 1 2
            
 // 注意點: onclick對應的方法由于滿足了閉包的條件,
 // 所以onclick對應的方法也是一個閉包
            btn.onclick = function () {
                console.log(index);
            }
        })(i);
    }
  • 每定義一個函數(shù),就開啟一個作用域(作用域就一塊內存,不同內存的變量互不干擾),那么for執(zhí)行三次,就會定義了三個自執(zhí)行函數(shù)的作用域,每一個作用域中有兩塊東西,一是index值和閉包onclick函數(shù),如下圖
    [站外圖片上傳中...(image-ef2abf-1559914641385)]
  • 所以點擊時,輸出相應的值(==因為所處的內存不同==),達到了循環(huán)索引同步

循環(huán)索引同步方案二

  • 為了引入這個方案,先看以下的代碼
  var list = [];
    // 這里的i是全局變量
    for(var i = 0; i < 3; i++){ // 0 1 2 3
        var fn = function test() {
            console.log(i); // 3
        }
        list.push(fn);
    }
    // console.log(i);
    // console.log(list);
    list[0]();
    list[1]();
    list[2]();
  • 三個函數(shù)輸出都為3,因為i是全局變量。

方案二:用let定義索引

let list = [];
    // 這里的i是局部變量
    // 注意點: 由于i是局部變量, 所以每次執(zhí)行完循環(huán)體都會重新定義一個i變量
    for(let i = 0; i < 3; i++){ // 0 1 2 3
        let fn = function test() {
            console.log(i); // 3
        }
        list.push(fn);
    }
    // console.log(i); // i is not defined
    // console.log(list);
    list[0]();
    list[1]();
    list[2]();
  • 分別輸出0,1,2,因為每次執(zhí)行完循環(huán)體都會重新定義一個i變量(因為局部變量的生命周期在for的右邊花括號就結束了),看下圖。

[站外圖片上傳中...(image-9052fc-1559914641385)]


for(let i = 0; i < 3; i++){ // 0 1 2 3
        // 注意點: 在ES6中由于{}是塊級作用域, 所以只要在塊級作用域中定義了一個函數(shù)
        //         并且這個函數(shù)中用到了塊級作用域中的數(shù)據(jù), 那么這個函數(shù)就是閉包
        function test() {
            console.log(i); // 3
        }
    }
    test(); // 2
    
  • test()是閉包,看起來是不是怪?以往函數(shù)嵌套函數(shù),且用到了外部作用域的值才會是閉包,現(xiàn)在沒有函數(shù)嵌套函數(shù)啊?其實函數(shù)嵌套函數(shù),本質上定義了一個函數(shù)就是開啟了一個作用域,函數(shù)嵌套了函數(shù),也可以說是非全局的作用域嵌套了一個作用域,那么定義閉包的說,可以說成在某一個非全局作用域里面定義了一個函數(shù)而且這個函數(shù)用到了那個作用域的值,就是閉包,因為for的{}是塊級作用域,而且在里面定義了一個函數(shù)test,且test用到了i,所以test也是一個閉包!~
  • 為什么 test(); // 2輸出2,因為其他test都被覆蓋了,只剩最后一個,
  • 為什么其他test函數(shù)會被覆蓋?for的局部作用域和全局作用域,在for執(zhí)行完三次循環(huán),就銷毀for的作用域,回收內存了,所以還剩下全局作用域,又因為閉包執(zhí)行完后其作用域就不會被收回,所以三個test函數(shù)都暴露在全局作用域了,所以會被最后一個test函數(shù)覆蓋其他函數(shù)
  • 為什么說三個test函數(shù)都暴露在全局作用域了??看下面的圖片

[站外圖片上傳中...(image-cc159b-1559914641385)]


循環(huán)索引同步練習:方案二

    // 在ES6中
    // 1.for循環(huán)中通過let定義的變量是一個局部變量
    // 2.for循環(huán)中通過let定義的變量每次執(zhí)行循環(huán)體都會重新定義一個新的
    //   也就是每個循環(huán)體都有一個屬于自己的變量
    
    // 3.for循環(huán)中如果定義了函數(shù), 這個函數(shù)用到了通過let定義的變量,
    // 那么這個函數(shù)是一個閉包
    for(let i = 0; i < 3; i++){
        function test() {
            console.log(i);
        }
    }

    let oBtns = document.querySelectorAll("button");
    for(let i = 0; i < oBtns.length; i++){
        let btn = oBtns[i];
        btn.onclick = function () {
            console.log(i);
        }
    }
    
  • 頁面三個按鈕,點擊按鈕0,輸出0,點擊按鈕1,輸出1
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容