你不可不知的JS面試題(第三期)

1、什么是閉包?


如圖所示,閉包就是一個定義在函數(shù)內(nèi)部的函數(shù),其作用是將函數(shù)內(nèi)部和函數(shù)外部連接起來。
大家知道,作用域的問題,就是在函數(shù)內(nèi)部定義的變量稱為局部變量,外部取不到值。
下面我們通過代碼來更加詳細地看一下:

   function A() {
       let x = 1;
       return function B() {
           console.log(x);
           x++;
       }
   }
   console.log(A()); //直接打印返回的B
   let b = A();
   b(); //1
   b(); //2
   b(); //3

 

上述代碼我們可以看到的是再次執(zhí)行A函數(shù)的時候(其實這時候執(zhí)行的是B),會打印出1,再次執(zhí)行,會打印出2,再執(zhí)行會打印出3。我們知道了閉包不僅可以拿到函數(shù)內(nèi)部的變量,還可以保存內(nèi)部的變量。

接下來,我們來看下閉包的使用場景,

for (var index = 0; index < 5; index++) {
    setTimeout(function () {
        console.log(index)
    });
}

 

上面的代碼段,我們會打印5個5,為啥呢?因為當我們執(zhí)行for循環(huán)時,setTimeout是異步的,所以每次等for循環(huán)加完,再執(zhí)行setTimeout函數(shù),一共執(zhí)行5次。另一個原因因為使用了var聲明的。循環(huán)體里的index跟外部的index是存在于同一個作用域,相當于在全局定義了一次。

下面我們來使用let來聲明一下。結(jié)果就不一樣。分別打印0、1、2、3、4,

    for (let index = 0; index < 5; index++) {
        setTimeout(function () {
            console.log(index)
        });
    }

 

最后,我們使用閉包也來實現(xiàn)一下,同樣分別打印0、1、2、3、4,在外部函數(shù)每次傳入實參時,也就是每次循環(huán)的值index,作用到形參i,因為setTimeout為內(nèi)部函數(shù),每次都會記錄值,然后打印出來。

    for (var index = 0; index < 5; index++) {
        (function (i) {
            setTimeout(function () {
                console.log(i)
            });
        })(index);
    }

 

另一個使用場景是柯里化。下面一個問題我們將詳解。
2、什么是柯里化?

是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)而且返回結(jié)果的新函數(shù)的技術(shù)。


3、柯里化的好處?

    提高適用性
    延遲執(zhí)行
    提前確認

(1)、提高適用性

我們之前使用函數(shù)都是這樣寫,很冗余。

function check(reg,text) {
     return reg.test(text);
 }
 console.log(check(/\d+/g,'123456')) //true
 console.log(check(/\d+/g,'abcd')) // false
 console.log(check(/\d+/g,'r8r8r7')) // true

 

那么我們通過柯里化的形式簡化一下。

function check(reg) {
     return function (text) {
        return reg.test(text);
     }
 }
 const checkNum=check(/\d+/g);
 
 console.log(checkNum('123456')) //true
 console.log(checkNum('abcd')) // false
 console.log(checkNum('r8r8r7')) // true

 

(2)、延遲執(zhí)行
在需要的時候再執(zhí)行。

const getResult = add(1)(2)(3);
// TODO
getResult();

 

或者

const getResult =add.bind(null,1).bind(null,2).bind(null,3);
// TODO
getResult();

 

(3)、提前確認

之前執(zhí)行一次,就不會每次都去判斷了,下面舉例:

 function getBindEvent(isIE) {
     if(isIE){
         return  function () {
             // TODO
             console.log(1)
         }
     }
     return function () {
            // TODO
             console.log(0)
     }
 }
 const isIE = !!window.ActiveXObject||"ActiveXObject" in window;
 const bindEvent = getBindEvent(isIE);
 bindEvent();

 

最后這里來實現(xiàn)下圖所示:
 

function add() {
    // arguments是一個類數(shù)組
    const args = Array.prototype.slice.call(arguments); // 將參數(shù)都放在一個數(shù)組里
    return function () {
        // 下面的arguments與上面的不一樣,下面的arguments為當前函數(shù)環(huán)境下的參數(shù)
        if(arguments.length){ // 如果有傳進來的參數(shù)
          args.push(...arguments); // 添加到數(shù)組里
          return arguments.callee; // 返回閉包函數(shù)自身
        }
        return args.reduce((n,m)=>{return n+m}); // 否則參數(shù)沒傳進來,就直接執(zhí)行他
    }
}
console.log(add(1)(2)()); // 3
console.log(add(1,2)(3)(4)()); // 10

 

    本期結(jié)束,謝謝
 




作者:Vam的金豆之路

主要領(lǐng)域:前端開發(fā)

我的微信:maomin9761

微信公眾號:前端歷劫之路