003.[轉載]前端的必須會一道經典面試題

前面的答案都是能做對的,但是后面的ES6和ES7真的一點都不會,該學習了!胖先森!
被打擊了,真心被打擊了!
最后面的語法還是不明所以,需要學習,學習,學習!

由一道簡單的JS面試題發起的一系列追問,看看你能答對幾個?
我們直接從簡單的代碼開始,看看它的運行結果是什么?
1. JS代碼

        for (var i=0;i<5;i++){
            window.setTimeout(function(){
                console.log(new Date(),i)
            },1000)
        }

上面的題目很簡單,只要對作用域,閉包,異步執行[定時器]等知識有了解,應該會很快說出答案,在本地測試后,輸出的答案如下。

跟預測結果不一樣

追問①

如果我們需要用箭頭->表示前后輸出有一秒的間隔,那么最終輸出的答案會是下面的結果1還是結果2呢?

        for (var i=0;i<5;i++){
            window.setTimeout(function(){
                console.log(new Date(),i)
            },1000)
        }
        console.log(new Date(),i,"->");
結果1. 5 -> 5 -> 5 -> 5 -> 5 -> 5
結果2. 5 -> 5, 5, 5, 5, 5

如果你對Javascript的定時器有所了解的話,會很快知道結果2是正確的。
在循環時設置定時器,幾乎是同時生效的,一般情況下,這5個定時器會在同一秒內執行,結果會立即輸出,因此結果2是正確的。

追問②

如果我期望輸出以下的結果,該怎么做呢?

想要的結果: 5 -> 0, 1, 2, 3, 4
  • 解決方案一:定義輸出函數

有的同學可能會很容易想到通過定義一個專門的輸出函數來執行。

        var output = function(i){
            window.setTimeout(function(){
                console.log(new Date(),i)
            },1000)
        }

        for (var i=0;i<5;i++){
           output(i);//這里傳過去的i的值被復制了
        }

        console.log(new Date(),i)
  • 解決方案二:立即執行函數+閉包

熟悉閉包的同學可能會覺得這種方案比較low,他們會想到利用立即執行函數來解決閉包產生的問題,于是會給出以下答案。

       for (var i=0;i<5;i++){
           (function(j){
               window.setTimeout(function(){
                   console.log(new Date(),j)
               },1000)
           })(i);
           
       }
       console.log(new Date(),i,"->");

上面的答案利用了立即執行函數來解決閉包造成的問題,是一種比答案1略好的方法。

  • 解決方案二:let關鍵字

如果熟悉ES6語法的同學又會想到以上兩種方法對于代碼的改動都比較大,如果使用ES6的let命令會可能會更加容易,于是他們有了以下代碼。

針對web前端應該去掌握最新的ES6的語法

       for (let i=0;i<5;i++){
           window.setTimeout(function(){
               console.log(new Date(),i)
           },1000)
       }
      // console.log(new Date(),i,"->")

首先,我們要確定的是,上述代碼在運行中會報錯,因為let是塊級作用域,i只存在于循環體內部,最后一句i并未定義,在直接輸出時會報錯。
但是既然能夠想到用let命令去做,說明對ES6有一定的了解,很不錯。

追問③

如果對于上面的問題,覺得太簡單了,那進行下面一個問題。
需要輸出的形式如下,每隔一秒輸出一個數字,該如何實現?

預想得到的結果: 0 -> 1 -> 2 -> 3 -> 4 -> 5 

可能會有很多同學想到使用如下簡單粗暴的方案去做。

       for (var i=0;i<5;i++){
           (function(j){
               window.setTimeout(function(){
                   console.log(new Date(),j)
               },1000*j);//修改0~4的時間
           })(i);

       }
       // 增加定時器,超時設置為5秒
       window.setTimeout(function(){
           console.log(new Date(),i)
       },1000*i)

對于以上的方案,雖然能實現這個簡單的需求,但是并不能算面試時的一個加分項。
如果考慮到異步執行后的操作,上面的代碼就會顯得很吃力,那該怎么辦呢?

  • ES6的Promise

如果你對ES6的Promise有所了解,會很容易想到使用Promise去實現。

       const tasks=[];
       for (var i=0;i<5;i++){
           ((j)=>{
               tasks.push(new Promise((resolve)=>{
                   window.setTimeout(()=>{
                    console.log(new Date(),j);
                    resolve();//這里需要執行resolve()
                   },1000*j);
               }));
           })(i);
       }

       Promise.all(tasks).then(()=>{
           window.setTimeout(function(){
               console.log(new Date(),i)
           },1000);
       });

沒有學習ES6,突然感覺打擊真大

追問④ 極限

既然掌握了ES6的Promise,我們可以更加追求代碼的極致,使用ES7的async和await。
這里給出使用async和await的解決方案。

//模擬其他語言中的sleep,實際上可以是任何的異步操作
        const sleep = (time) => new Promise((resolve)=>{
           window.setTimeout(resolve,time);
       });

       (async()=>{//聲明即執行的async函數表達式
           for (var i=0;i<5;i++){
               await sleep(1000);
               console.log(new Date(),i);
           }
           await sleep(1000);
           console.log(new Date(),i);
       })();

最后說一下

本篇文章從一個簡單的Javascript面試題開始,進行了一系列的追問,想一想你能答對幾個追問呢?如果又不會的,記得回去查漏補缺,學習響應的知識。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容