夯實基礎--你所不知道的當即執行函數

基礎回顧

當即調用的函數表達式是咱們常用的函數編碼模式之一,也被稱之爲 IIFE 。javascript

在咱們瞭解 IIFE 是什麼以及爲何須要它以前,咱們須要快速回顧一下關於 Javascript 函數中的基本概念。java

函數聲明

function say() {
    console.log('Hello World');
}

say(); // print: Hello World
複製代碼
  • 咱們在 1-3 行定義了一個名爲 say 的函數,
  • 第 5 行使用函數名以及一對圓括號對函數進行調用。

這種建立函數的方式稱之爲:函數聲明。git

一般,剛接觸 Javascript 的開發人員使用這種語法沒有問題, 由於它很是相似於其餘流行的編程語言中的函數或方法。github

咱們須要注意的是,函數聲明老是以 function 關鍵字開頭,後面緊跟着函數的名稱, 而且名稱不可省略。express

函數表達式

let msg = 'Hello World';
let say = function() {
  console.log(msg);
}

say(); // print:Hello World
複製代碼
  • 第1行聲明瞭 msg 變量併爲其分配了一個字符串值
  • 第 2-4 行聲明 say 變量併爲其賦予函數類型的值
  • 第 6 行使用變量名以及一對圓括號對函數進行調用

在上面的例子中,咱們爲變量 say 賦予了函數類型的值, 這種賦值運算符右側的函數一般稱爲: 函數表達式。編程

函數表達式在 Javascript 中無處不在,咱們可能編寫的大多數回調函數一般都是函數表達式。瀏覽器

所以,咱們要知道一個重要的概念,在 Javascript 中函數幾乎與其餘任何值同樣, 既能夠位於賦值運算符的右側,也能夠做爲參數傳遞給其餘函數。安全

匿名函數

顧名思義,匿名函數就是沒有名字的函數。閉包

let say = function() {
  console.log('Hello World');
}
複製代碼

咱們建立了一個函數表達式,而它其實也是匿名函數, 由於 function 關鍵字後面沒有名字。編程語言

具備名稱的函數表達式

函數表達式一樣能夠有函數名稱,此時函數名稱只是函數體內的一個本地變量,沒法在外部調用, 它的最主要的做用是在遞歸中使用。

let fn = function say() {
    console.log('Hello World');
}
say(); // print error :say is not defined
複製代碼

開始 IIFE

咱們已經簡單回顧了函數定義和函數表達式,如今讓咱們直接進入 IIFE 的祕密世界。

咱們但願獲得的是一個能夠當即執行的函數,那麼咱們聲明一個函數, 而後經過括號()來執行不就好了麼,咱們來試一下

它們有幾種風格。讓咱們首先看到一個很是容易理解的變體。

!function() {
    console.log("Hello from IIFE!");
}();
複製代碼

當幫這段代碼複製到瀏覽器的控制檯中嘗試時,會在控制檯中打印咱們想要輸出的內容。

接下來,咱們來理解這段不是那麼直觀的代碼:

在 Javascript 語言中,當 function 關鍵字視爲有效語句中的第一個單詞時, Javascript 引擎會將其當作函數聲明來處理。 所以,爲了防止這種狀況的發生,咱們在第 1 行代碼中的 function 關鍵字前加上 「!」前綴。 此時 Javascript 會將這段代碼視爲函數表達式。

在代碼的第 3 行,經過一對圓括號對函數表達式進行調用。

因此,咱們得出結論,什麼是 IIFE:函數表達式,在建立後當即執行,就成爲 IIFE。

經過將「!」替換爲「+」,「-」,「~」甚至「void」,「typeof」甚至「delete」。

咱們能夠在瀏覽器的控制檯上,盡情的嘗試~

!function(){console.log('!')}();
+function(){console.log('+')}();
-function(){console.log('-')}();
~function(){console.log('~')}();
void function(){console.log('void')}();
typeof function(){console.log('typeof')}();
delete function(){console.log('delete')}();
複製代碼

上面例子中,關鍵字 function 前的「!、+、-、~、void、typeof、delete」所作的事, 就是將函數轉換爲函數表達式而不是函數聲明。

這種方式構造的 IIFE 很容易理解,接下來咱們介紹其餘更傳統和更普遍使用的 IIFE 風格。

經典 IIFE 風格

(function(){
    console.log("I'm not IIFE")
})
複製代碼

在上面的代碼中,函數表達式包含在第 1-3 行的括號中。 它還不是 IIFE,由於函數表達式永遠不會被執行。 如今要將該代碼轉換爲 IIFE,咱們有如下兩種風格變化:

//方法1
(function(){
    console.log("I'm IIFE")
})()
//方法2
(function(){
    console.log("I'm IIFE")
}())
複製代碼

如今咱們生成了 2 個 IIFE,可能很難注意到這兩者的區別,解釋一下:

  1. 在方法1中和方法2中,咱們都是用一對括號()將函數體包裹起來,使之變成函數表達式。
  2. 在方法1中,用於調用函數表達式的括號(),包含在外括號內。
  3. 在方法2中,用於調用函數表達式的括號(),在外括號外。

這兩種方法都被普遍的使用,咱們能夠根據本身的喜愛使用其中任何一個。

如今讓咱們再看一個有效的例子,以及兩個無效的例子。 咱們將從如今開始命名咱們的 IIFE,由於使用匿名函數一般不是一個好主意。

//有效的IIFE
(function initAppIIFE() {
    //全部你的神奇代碼
}());

//如下兩個是無效的IIFE示例
function nonWorkingIIFE() {
    //如今你知道爲何你須要我周圍的那些括號!
    //若是沒有這些括號,我是一個函數聲明,而不是一個表達式。
    //你會收到語法錯誤!
}();

function () {
     //這裏也會出現語法錯誤!
}();
複製代碼

咱們得出一個結論:

只有函數表達式可以建立 IIFE ,函數聲明永遠不會用於建立 IIFE。

IIFE 和私有變量

IIFE 很是擅長的一件事就是可以爲 IIFE 建立私有做用域。

在 IIFE 內聲明的任何變量對外界都不可見。

來看一個例子:

(function IIFE_initApp() {
    // IIFE 的私有變量,外部沒法訪問
    let lives;
    let weapons;
    init();

    // IIFE 的私有函數,外部沒法訪問
    function init() {
        lives = 5;
        weapons = 10;
    }
}());
複製代碼

當咱們須要建立一堆變量和函數時,咱們定義一個 IIFE 並在 IIFE 內建立咱們所需的變量和函數, 這樣既不會污染全局空間,也能夠保護本身的代碼不被他人之外的影響。

具備返回值的 IIFE

若是咱們不須要來自 IIFE 的返回值,那麼咱們可使用咱們最早介紹的經過一元運算符(!、+、-、~等)來實現的IIFE。

~function IIFE_initApp() {
    //...
}
複製代碼

但 IIFE 的另外一個很是重要且強大的功能是它們能夠返回能夠分配給變量的值。

let result = (function() {
    return "From IIFE";
}());

console.log(result); // print:"From IIFE"
複製代碼

在上面的代碼中,咱們建立了一個 IIFE 當即執行,並將返回值傳遞給變量 result

這是一個很是強大的功能,咱們將在後文介紹模塊的時候來使用它。

具備參數的 IIFE

IIFE 不只能夠有返回值,也能夠在調用時進行參數傳遞,咱們來看一下:

(function IIFE(msg, times) {
    for (let i = 1; i <= times; i++) {
        console.log(msg);
    }
}("Hello!", 5));
複製代碼

在這個例子中,咱們建立了一個 IIFE 而且給予了兩個參數 msg 和 times。

這是一個很是強大的功能,咱們常常在 Jquery 或其餘庫中看到這種使用方式。

(function($, global, document) {
    // use $ for jQuery, global for window
}(jQuery, window, document));
複製代碼

在這個例子中,咱們將 jQuery、window 和 document 做爲參數傳遞給 IIFE, 在 IIFE中的代碼使用 $、global 和 document 做爲形參變量來接收這三個實參。

這種傳遞參數的有點以下:

  1. JS引擎在查找變量時,從當前做用域開始查找,若是找不到就會向上一級繼續查找,直至抵達最外層的全局做用域。 從性能上考慮,所需的變量存在於當前做用域時,變量查找時間要小於變量存在於上層做用域。
  2. 在將代碼發佈線上時,咱們會對JS代碼進行壓縮,安全的縮小函數中聲明的參數名稱。若是咱們沒有將 jQuery、window、document 做爲參數傳遞至 IIFE 內,則在對JS代碼進行壓縮時,全部使用到這些參數的地方,不會對這些參數引用進行壓縮。

經典的 JavaScript 模塊模式

下面咱們經過 IIFE 來實現一個模塊模式的例子。

咱們來實現一個經典的 Sequence 對象,咱們分爲兩步編寫此代碼,以便逐步瞭解正在發生的事情。

let Sequence = (function sequenceIIFE() {
    // IIFE 中的私有變量
    let current = 0;
   
    // 經過 IIFE 返回一個空對象
    return {};
}());

console.log(typeof Sequence); // print:"object"
複製代碼

在上面的例子中,咱們建立了一個返回空對象的 IIFE,而且在 IIFE 內建立了一個私有變量 current。

接下來,咱們作一些改進,在 IIFE 返回的空對象中添加一些函數。

let Sequence = (function sequenceIIFE() {
    // IIFE 中的私有變量
    let current = 0;
   
    // 經過 IIFE 返回一個空對象
    return {
        getCurrentValue: function() {
            return current;
        },
        getNextValue: function() {
            current = current + 1;
            return current;
        }
    };
    
}());

console.log(Sequence.getNextValue()); // 1
console.log(Sequence.getNextValue()); // 2
console.log(Sequence.getCurrentValue()); // 2
複製代碼

在這個例子中,咱們在 IIFE 返回的對象中,添加了兩個函數 getNextValue 和 getCurrentValue。

getCurrentValue 用於將當前的 current 的值返回。

getNextValue 用於將 current 的值遞增 1 ,而且返回當前 current 的值。

因爲 IIFE 中的變量 current 是私有的,外部沒法訪問的,由於只有經過 getCurrentValue 或 getNextValue 函數才能訪問它的值。

這是一個很是強大的 Javascript 模塊模式,它結合了 IIFE 和閉包的強大功能。

何時能夠省略括號

用於包裹函數體的括號是用於強制將咱們正在操做的函數體變爲函數表達式。

可是當 Javascript 引擎能夠確認這是一個函數表達式時,咱們就不須要包裹在函數外的括號了。

let result = function() {
    return 'From IIFE'
}();
console.log(result);
複製代碼

在上面的示例中,function 關鍵字不是語句中的第一個單詞。所以 Javascript 引擎不會將其視爲函數聲明。

即便如此,依然仍是建議在函數體外包裹一對括號。

使用括號能夠經過在第一行上對讀者進行風格上的暗示,暗示該函數將成爲 IIFE , 而沒必要要滾動到函數的最後一行才能肯定是不是 IIFE ,所以使用括號能夠提升代碼的可讀性。

參考


預計每週1-2篇文章,持續更新,歡迎各位同窗點贊+關注

後續內容參見寫做計劃

寫做不易,若是以爲稍有收穫,歡迎~點贊~關注~

本文同步首發與github,歡迎在issues與我互動,歡迎Watch & Star ★

相關文章
相關標籤/搜索