用9種辦法解決 JS 閉包經典面試題之 for 循環取 i

2021-02-19 JavaScript
轉自 https://segmentfault.com/a/1190000003818163

閉包

1.正確的說,應該是指一個閉包域,每當聲明了一個函數,它就產生了一個閉包域(可以解釋為每個函數都有自己的函數棧),每個閉包域(Function 對象)都有一個 function scope(不是屬性),function scope內默認有個名為Global的全局引用(有了這個引用,就可以直接調用 Global 的屬性或方法)

2.凡是在閉包域內聲明的變量或方法,外部無法直接訪問

3.閉包域可以訪問外部的變量或方法 
(上圖為 chrome 下 debug 環境)

當在一個閉包域內包含另一個閉包域時(簡單的說就是在一個函數內有另一個函數,當然這個內部函數的生命周期是依附於外部函數的), 此時,若子閉包域(內部的閉包域,內部函數)使用了父閉包域(外部閉包域,外部函數)的私有變量(在父閉包域中聲明的變量,父閉包域的外部空間無法直接訪問,但子閉包域可以訪問),子閉包域即當前的子函數的 function scope 會產生一個 closure 對象屬性,這個對象屬性內包含的是子閉包域對父閉包域的所有引用(只要子閉包域(內部函數)還存活,其父閉包域(外部函數)就依舊存活),倘若在父閉包域存活期間對其私有變量內容進行修改,則對這些父閉包域的私有變量進行引用的子閉包域中 function scope 的 closure 對象屬性的內容也會發生變化,因為這只是引用.

舉例:

<!DOCTYPE html>

<html lang="en">

<head>

   <meta charset="UTF-8">

   <title></title>

</head>

<body>

   <script type="text/javascript" charset="utf-8">

       //函數 a 有一個私有變量 p 和一個內部函數 innerA

       function a() {                      //外部閉包域 ,一個名為 a 的 Function 對象

           var p = 0;                      //私有變量 p

           var innerA = function () {      //內部閉包域 ,一個名為 innerA 的 Function 對象

               console.log(p);             //對外部閉包域的私有變量進行了引用,故 innerA 對象的 function scope 會產生一個名為 closure 的對象屬性,closure 對象內含有一個名為 p 的引用

           }

 

           innerA();//輸出0

           p++;

           innerA();//輸出1

       }

       a();

   </script>

</body>

</html>

結果如下:

第一次調用innerA


第二次調用 innerA


控制臺輸出


回到主題 面試經典問題

<!DOCTYPE html>

<html lang="en">

<head>

   <meta charset="UTF-8">

   <title></title>

   <script type="text/javascript">

       //面試經典問題:

 

       function onMyLoad(){

           /*

           拋出問題:

               此題的目的是想每次點擊對應目標時彈出對應的數字下標 0~4,但實際是無論點擊哪個目標都會彈出數字5

           問題所在:

               arr 中的每一項的 onclick 均為一個函數實例(Function 對象),這個函數實例也產生了一個閉包域,

               這個閉包域引用了外部閉包域的變量,其 function scope 的 closure 對象有個名為 i 的引用,

               外部閉包域的私有變量內容發生變化,內部閉包域得到的值自然會發生改變

           */

           var arr = document.getElementsByTagName("p");

           for(var i = 0; i < arr.length;i++){

               arr[i].onclick = function(){

                   alert(i);

               }

           }

       }

   </script>

</head>

<body onload="onMyLoad()">

   <p>產品一</p>

   <p>產品二</p>

   <p>產品三</p>

   <p>產品四</p>

   <p>產品五</p>

</body>

</html>

解決辦法:

解決辦法一

/*

解決思路:

   增加若干個對應的閉包域空間(這裡採用的是匿名函數),專門用來存儲原先需要引用的內容(下標),不過只限於基本類型(基本類型值傳遞,對象類型引用傳遞)

*/

for(var i = 0;i<arr.length;i++){

 

   //聲明一個匿名函數,若傳進來的是基本類型則為值傳遞,故不會對實參產生影響,

   //該函數對象有一個本地私有變量arg(形參) ,該函數的 function scope 的 closure 對象屬性有兩個引用,一個是 arr,一個是 i

   //儘管引用 i 的值隨外部改變 ,但本地私有變量(形參) arg 不會受影響,其值在一開始被調用的時候就決定了.

   (function (arg) {

       arr[i].onclick = function () {  //onclick函數實例的 function scope 的 closure 對象屬性有一個引用 arg,

           alert(arg);                 //只要 外部空間的 arg 不變,這裡的引用值當然不會改變

       }

   })(i);                              //立刻執行該匿名函數,傳遞下標 i(實參)

}

解決辦法二

/*

解決思路:

   將下標作為對象屬性(name:"i",value:i的值)添加到每個數組項(p對象)中

*/

for(var i = 0;i<arr.length;i++){

   //為當前數組項即當前 p 對象添加一個名為 i 的屬性,值為循環體的 i 變量的值,

   //此時當前 p 對象的 i 屬性並不是對循環體的 i 變量的引用,而是一個獨立p 對象的屬性,屬性值在聲明的時候就確定了

   //(基本類型的值都是存在棧中的,當有一個基本類型變量聲明其等於另一個基本變量時,此時並不是兩個基本類型變量都指向一個值,而是各自有各自的值,但值是相等的)

   arr[i].i = i;

   arr[i].onclick = function () {

       alert(this.i);

   }

}

解決辦法三

/*

解決思路:

   與解決辦法一有點相似但卻有點不太相似.

   相似點:同樣是增加若干個對應的閉包域空間用來存儲下標

   不同點:解決辦法一是在新增的匿名閉包空間內完成事件的綁定,而此例是將事件綁定在新增的匿名函數返回的函數上

 

   此時綁定的函數中的 function scope 中的 closure 對象的 引用 arg 是指向將其返回的匿名函數的私有變量 arg

*/

for(var i = 0; i<arr.length;i++){

   arr[i].onclick = (function(arg){

       return function () {

           alert(arg);

       }

   })(i);

}

解決辦法四

/*

解決思路與解決辦法一相同

*/

for(var i = 0; i<arr.length;i++){

   (function(){

      var temp = i;

       arr[i].onclick = function () {

           alert(temp);

       }

   })();

}

解決辦法五

/*

解決思路與解決辦法三及四相同

*/

for(var i = 0;i<arr.length;i++){

   arr[i].onclick = (function () {

       var temp = i;

       return function () {

           alert(temp);

       }

   })();

}

解決辦法六

/*

解決思路:

   將下標添加為綁定函數的屬性

*/

for(var i = 0;i<arr.length;i++){

   (arr[i].onclick = function () {

       alert(arguments.callee.i);      //arguments 參數對象  arguments.callee 參數對象所屬函數

   }).i = i;

}

解決辦法七

/*

解決思路:

   通過 new 使用 Function 的構造函數 創建 Function 實例實現,由於傳入的函數體的內容是字符串,故 Function 得到的是一個字符串拷貝,而沒有得到 i 的引用(這裡是先獲取 i.toString()然後與前後字符串拼接成一個新的字符串,Function 對其進行反向解析成 JS 代碼)

*/

for(var i = 0;i<arr.length;i++){

   arr[i].onclick = new Function("alert("+i+");");//每 new 一個 Function 得到一個 Function 對象(一個函數),有自己的閉包域

}

解決辦法八

/*

解決思路:

   直接通過 Function 返回一個函數

   與解決辦法七的不同之處在於:

       解決辦法七使用 new,使用了 new,此時 Function 函數就被當成構造器可以用來構造一個 Function 實例返回

       當前解決辦法沒有使用 new ,即將 Function 函數當成一個函數,傳入參數返回一個新函數;

       其實此處 new 與不 new 只是的區別在於:

           使用了 new 即 Function 函數充當構造器,由 JS 解析器生產一個新的對象,構造器內的 this 指向該新對象;

           不實用 new 即 Function 函數依舊是函數,由函數內部自己生產一個實例返回.

*/

for(var i = 0;i<arr.length;i++){

   arr[i].onclick = Function("alert("+i+");");

}

解決辦法九 
使用ES6新語法 let 關鍵字 由於幾新東西 各瀏覽器支持不同 
chrome 及 opera支持以下語法

<script type="application/javascript">

   "use strict";//使用嚴格模式,否則報錯 SyntaxError: Block-scoped declarations (let, const, function, class) not yet supported outside strict mode

   var arr = document.getElementsByTagName("p");

   for(var i = 0;i<arr.length;i++){

       let j = i;//創建一個塊級變量

       arr[i].onclick = function () {

           alert(j);

       }

   }

</script>

在 chrome 查看


可以在控制臺看到 j 變量是一個 block 級的變量

待函數綁定完成後看數組項:


此時的該數組項的<function scope>的 Block 域有個 j 存儲的就是對應的數組下標 
firefox支持一下語法

<script type="application/javascript;version=1.7">

   var arr = document.getElementsByTagName("p");

   for(var i = 0;i<arr.length;i++){

       let j = i;

       arr[i].onclick = function () {

           alert(j);

       }

   }

</script>

由於新語法各大廠商的支持尚未規範故暫不不推薦使用

解決辦法大同小異,只要理解其中的實質,可以寫出多多的解決辦法

點擊閱讀全文,查看更多

相關焦點

  • JavaScript同步、異步、回調執行順序之經典閉包setTimeout面試題分析
    有一道經典的面試題:for (var i = 0; i < 5; i++) {    setTimeout(function() {        console.log('i: ',i);    }, 1000);} console.log(i); //
  • 大部分人都會做錯的經典 JS 閉包面試題
    (點擊上方公眾號,可快速關注)作者:小小滄海,www.cnblogs.com/xxcanghai/p/4991870.html如有好文章投稿,請點擊 → 這裡了解詳情由工作中演變而來的面試題這是一個我工作當中的遇到的一個問題,似乎很有趣,就當做了一道題去面試,發現幾乎沒人能全部答對並說出原因,遂拿出來聊一聊吧。
  • 詳解 js 閉包
    函數裡放匿名函數,則產生了閉包七、在循環中直接找到對應元素的索引   <!0;i<aLi.length;i++){                    aLi[i].onclick = function(){        //當點擊時for循環已經結束                    alert(i);                    };            }    }
  • 7 個沙雕又帶有陷阱的 JS 面試題
    > numbers.push(i + 1);}numbers; // => [5]用不規範的代碼去檢驗別人是否細心,我覺得很沙雕。被考爛的一個經典閉包問題面試官問下面的代碼執行結果是什麼?在每次迭代時,都會創建一個新函數 log(),該函數將捕獲變量 i。然後, setTimout() 調度 log() 的執行。當 for() 循環完成時,變量 i 的值為 3。log() 是一個捕獲變量 i 的閉包,該變量在 for() 循環的外部作用域中定義。重要的是要了解閉包在詞法上捕獲了變量 i。
  • 詳解js閉包
    函數裡放匿名函數,則產生了閉包七、在循環中直接找到對應元素的索引 <!0;i<aLi.length;i++){                    aLi[i].onclick = function(){  //當點擊時for循環已經結束                    alert(i);                    };            }    }        </script>                &
  • 五、閉包
    初學JavaScript時,我在閉包上,走了很多彎路。而這次重新回過頭來對基礎知識進行梳理,要講清楚閉包,也是一個非常大的挑戰。閉包有多重要?如果你是初入前端的朋友,我沒有辦法直觀的告訴你閉包在實際開發中的無處不在,但是我可以告訴你,前端面試,必問閉包。
  • 乾貨 | Web前端經典面試題及答案
    上周,黑分享了關於Java的面試題,有學員反映需要前端方向的面試題,
  • 一道神奇的Python面試題,你會嗎?
    無意間,看到這麼一道Python面試題:以下代碼將輸出什麼?最後發現原因竟是:Python 的閉包的後期綁定導致的 late binding。這意味著在閉包中的變量是在內部函數被調用的時候被查找,所以當任何testFun() 返回的函數被調用,i 的值是在它被調用時的周圍作用域中查找。也就是說無論哪個返回的函數被調用,for 循環都已經完成了,i 最後的值是 3,因此,每個返回的函數 testFun 的值都是 3。
  • 你應該收藏的web前端面試題
    最新前端開發工程師面試題——HTML部分1、Doctype作用?
  • 由一道面試題引發的JS計時器花式玩法
    // 每日前端夜話 第400篇// 正文共:3100 字// 預計閱讀時間:10 分鐘先從一個面試題開始:❝JavaScript 面試題:setTimeout 和 setInterval 的原始碼是在哪裡實現的
  • 詳解 JavaScript 閉包
    使用閉包的好處那麼使用閉包有什麼好處呢?函數裡放匿名函數,則產生了閉包七、在循環中直接找到對應元素的索引  <!0;i<aLi.length;i++){                    aLi[i].onclick = function(){        //當點擊時for循環已經結束                    alert(i);                    };            }    }
  • 前端基礎進階(五):JavaScript 閉包詳細圖解
    而這次重新回過頭來對基礎知識進行梳理,要講清楚閉包,也是一個非常大的挑戰。閉包有多重要?如果你是初入前端的朋友,我沒有辦法直觀的告訴你閉包在實際開發中的無處不在,但是我可以告訴你,前端面試,必問閉包。面試官們常常用對閉包的了解程度來判定面試者的基礎水平,保守估計,10個前端面試者,至少5個都死在閉包上。
  • Python遞歸函數、閉包和裝飾器
    什麼是遞歸函數  函數直接或間接的調用自己本身可有解決循環上的問題import timedef 循環來代替。>i = 1while i<=n:result = result*ifor i in range(1,n+1)]print(a)擴充面試題目:如何獲取計算機當中的最大的遞歸層數?
  • 走近 Python (類比 JS)
    有了切片操作符,大大簡化了一些原來得用循環的操作。>for 循環可以嵌套,因此,在列表生成式中,也可以用多層 for 循環來生成列表。下面來看下 Py 中閉包之 for 循環經典問題:# 希望一次返回3個函數,分別計算1x1,2x2,3x3:def count():    fs = []    for i in range(1, 4):        def f():            return i * i        fs.append
  • 前端面試題系列(一)
    結構層 Html 表示層 CSS 行為層 js8.css的基本語句構成是?選擇器{屬性1:值1;屬性2:值2;……}9.你做的頁面在哪些流覽器測試過?這些瀏覽器的內核分別是什麼?Important 解決』7.select 在ie6下遮蓋 使用iframe嵌套8.為什麼沒有辦法定義1px左右的寬度容器(IE6默認的行高造成的,使用over:hidden,zoom:0.08 line-height:1px)11.<img>標籤上title與alt屬性的區別是什麼?Alt 當圖片不顯示是 用文字代表。
  • Map經典面試題,你遇到了嗎?
    依稀記得我當初面試遇到過兩道涉及到Map的面試題,當時是有點眼急,會,直接手寫代碼確實又不會了。  工作了一段時間,再看完Map,自己又重新溫習了一下這兩道面試題,難,不難,只在一瞬息。   for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if(!
  • 什麼是閉包?一分鐘帶你了解!
    什麼是閉包?這就是閉包!所有的教科書教程上都喜歡用最後一段來說明閉包,但我覺得這將問題複雜化了。這裡面返回的是函數名,沒看過譚浩強C/C++程序設計的同學可能一下子沒反應出帶不帶()的區別,也就是說這種寫法自帶一個陷阱。雖然這種寫法更顯高大上,但我還是喜歡將問題單一化,看看代碼 1 和代碼 2,你還會糾結函數的調用,你會糾結 n 的值嗎?
  • 360子公司,華閱文化前端面試 面試題,技術問題,邏輯題詳解
    2019.1.9收到的面試。和hr老哥,談到21號上午去。(樓主在職)不多說,直接面試題。1.舉例HTML5和css3的新特性(常考)2js代碼用來保存用戶訪問某一個頁面的次數。輸出結果:for(var i=0;i<5;i++){setTimeout(function(){console.log(i);},i*1000);}10手機正則表達式
  • 說說那些經典的web前端面試題-JavaScript部分
    請寫出函數實現typeof(obj) === "string"typeof obj === "string"obj.constructor === String請用js去除字符串空格?5、使用閉包的注意點1)濫用閉包,會造成內存洩漏:由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存洩露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。2)會改變父函數內部變量的值。
  • 【面試篇】JavaScript面試經,offer拿到手軟
    ==會自動進行類型轉換,===不會例舉3種強制類型轉換和2種隱式類型轉換?Document.onload 是在結構和樣式加載完才執行jswindow.onload:不僅僅要在結構和樣式加載完,還要執行完所有的樣式、圖片這些資源文件,全部加載完才會觸發window.onload事件Document.ready原生種沒有這個方法