深入理解JavaScript立即執行函數

我們在很多場景中使用了立即執行函數,或者看到別人寫了立即執行函數,但是對它的作用和用法 還有一些疑惑,寫這篇文章就是來解決這個問題的。

立即執行函數

IIFE(Immediately-Invoked Function Expression)
立即執行函數模式是一種語法,可以讓你的函數在定義後立即被執行。

函數相關的概念

函數聲明函數表達式匿名函數

函數聲明

function func() { console.log(‘this is a function’) };
首先使用 function 關鍵字聲明一個函數,再執行一個函數名,叫函數聲明。

函數表達式
let func = function() { console.log(‘this is a function’) };
使用 function 關鍵字聲明一個函數,但未給函數命名,將匿名函數賦予一個變量,叫函數表達式,這是最常見的函數表達式語法形式。

匿名函數:
function() { console.log(‘this is a function’) } ;
使用 function 關鍵字聲明一個函數,但未給函數命名,所以叫匿名函數,匿名函數屬於函數表達式,匿名函數有很多作用,賦予一個變量則創建函數,賦予一個事件則成爲事件處理程序或創建閉包等等。

函數聲明和函數表達式區別

1、JavaScript 引擎在解析 JavaScript 代碼時會“函數聲明提升”當前執行環境(作用域)上的函數聲明,而函數表達式必須等到 JavaScript 引擎執行到它所在行時,纔會從上而下一行一行地解析函數表達式。

2、函數表達式後面可以加括號立即調用該函數,函數聲明不可以,只能以 func() 形式調用。

我們看一下下面的例子就會明白

函數聲明

func(); //正常輸出this is a function
function func() {
    console.log('this is a function') 
}; 

函數表達式

func();// Uncaught SyntaxError: Invalid or unexpected token
////函數表達式報錯,func未保存對函數的引用,函數調用需放在函數表達式後面
var func = function() { 
    console.log('this is a function') 
};

立即執行函數的兩種寫法

//第一種寫法
(function(){ 
   console.log('this is a function')
})();

//第二種寫法
(function(){ 
   console.log('this is a function')
}());

爲什麼立即執行函數要加(),不加可不可以呢,我們來試一下

function(){ 
   console.log('this is a function')
}()
//Uncaught SyntaxError: Function statements require a function name
//報錯,意思是函數聲明需要一個函數名

上面這個例子其實是一個匿名函數,
雖然匿名函數屬於函數表達式,但未進行賦值,所以javascript解析時將開頭的function當做函數聲明,故報錯提示需要函數名;
立即執行函數裏面的函數必須是函數表達式

立即執行函數是否可以有返回值

let func = function() {
    return 1;
}();
console.log(func) //1 

我們在瀏覽器的控制檯可以看到,立即執行函數是可以有返回值的
爲什麼給一個匿名函數賦值就可以正常了呢?
我們可以理解爲在匿名函數前加了 “=” 有了運算符後,將函數聲明轉化爲函數表達式。
我們拿!,+,-,()…等運算符來進行測試:

!function(){
    console.log('this is a function1')
}()
// this is a function1
    
+function(){
    console.log('this is a function2')
}()
// this is a function2
    
-function(){
    console.log('this is a function3')
}()
// this is a function3
    
;(function(){
    console.log('this is a function4')
})()
// this is a function4

由此可見,加運算符確實可將函數聲明轉化爲函數表達式

需要注意的地方

注意在代碼console.log(‘this is a function4’)這個函數我在最前面加上了一個“;”(分號),
爲什麼要加這分號,不加行不行呢,大家可以驗證一下。
不加會報錯,會報下面的錯
(Uncaught TypeError: (intermediate value)(…) is not a function)
上面的代碼有一些多,我們截取一部分來講

-function(){
    console.log('this is a function3')
}()

(function(){
    console.log('this is a function4')
})()

這段代碼一樣會報錯,因爲ECMAScript規範具有分號自動插入規則,但是在上面代碼中,在第一個立即執行函數末尾卻不會插入,因爲第二個立即執行函數,會被解釋爲如下形式:

-function(){
    console.log('this is a function3')
}()(function(){console.log('this is a function4')})()

因此我們在最後一個立即執行函數前面加一個分號;(是爲了防止前一個立即函數尾部沒有的分號;)
其實還有其它的方式也可以實現,使用void 運算符,個人感覺這種方式更優雅

-function(){
    console.log('this is a function3')
}()

void (function(){
    console.log('this is a function4')
})()

立即執行函數的作用

**創造一個作用域空間,防止變量衝突或覆蓋 **

我們下面來看一個精典的面試題

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

輸出一個數據,從0-4,按常理會輸出//0 1 2 3 4,結果輸出了5個5。
這樣的需求我們可以用立即執行函數來做,
我們可以把代碼稍微改一下可以了,

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

首先 JS中調用函數傳遞參數都是值傳遞 ,所以當立即執行函數執行時,首先會把參數 i 的值複製一份,然後再創建函數作用域來執行函數,循環5次就會創建5個作用域,所以1秒後幾乎會同時輸出 0 1 2 3 4 。
其實還有其它的方式可以來實現,把原代碼中的var改成let(ES6的塊級作用域)也是可以正常輸出 0 1 2 3 4

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

立即執行函數的應用場景

1、代碼在頁面加載完成之後,不得不執行一些設置工作,比如時間處理器,創建對象等等。

2、所有的這些動作只需要執行一次,比如只需要顯示一個事件。

3、將代碼包裹在它的局部作用域中,不會讓任何變量泄漏成全局變量。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章