簡體   English   中英

將整個 Javascript 文件包裝在像“(function(){ ... })()”這樣的匿名函數中的目的是什么?

[英]What is the purpose of wrapping whole Javascript files in anonymous functions like “(function(){ … })()”?

我最近讀了很多 Javascript,我注意到整個文件在要導入的 .js 文件中如下所示。

(function() {
    ... 
    code
    ...
})();

這樣做的原因是什么而不是一組簡單的構造函數?

它通常用於命名空間(見下文)並控制成員函數和/或變量的可見性。 把它想象成一個對象定義。 它的技術名稱是立即調用函數表達式(IIFE)。 jQuery 插件通常是這樣寫的。

在 Javascript 中,您可以嵌套函數。 因此,以下內容是合法的:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

現在您可以調用outerFunction() ,但innerFunction()的可見性僅限於outerFunction()的范圍,這意味着它是outerFunction()私有的。 它基本上遵循與 Javascript 中的變量相同的原則:

var globalVariable;

function someFunction() {
   var localVariable;
}

相應地:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}

在上述場景中,您可以從任何地方調用globalFunction() ,但不能調用localFunction1localFunction2

當您編寫(function() { ... })()時,您正在做的事情是將第一組括號內的代碼制作為函數文字(意味着整個“對象”實際上是一個函數)。 之后,您將自行調用剛剛定義的函數(最后一個() )。 因此,正如我之前提到的,它的主要優點是您可以擁有私有方法/函數和屬性:

(function() {
   var private_var;

   function private_function() {
     //code
   }
})();

在第一個示例中,您將通過名稱顯式調用globalFunction來運行它。 也就是說,您只需執行globalFunction()即可運行它。 但是在上面的例子中,你不只是定義一個函數; 您正在一次定義調用它。 這意味着當你的 JavaScript 文件被加載時,它會立即被執行。 當然,你可以這樣做:

function globalFunction() {
    // code
}
globalFunction();

除了一個顯着的區別外,行為基本上是相同的:當您使用 IIFE 時避免污染全局范圍(因此這也意味着您不能多次調用該函數,因為它沒有名稱,但是因為此功能僅在真正不成問題時才執行)。

IIFE 的巧妙之處在於,您還可以在內部定義事物,並且只向外界公開您想要的部分(命名空間的示例,因此您基本上可以創建自己的庫/插件):

var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

現在您可以調用myPlugin.public_function1() ,但不能訪問private_function() 非常類似於類定義。 為了更好地理解這一點,我推薦以下鏈接以供進一步閱讀:

編輯

我忘了提。 在那個 final ()中,你可以在里面傳遞任何你想要的東西。 例如,當您創建 jQuery 插件時,您可以像這樣傳入jQuery$

(function(jQ) { ... code ... })(jQuery) 

因此,您在這里所做的是定義一個接受一個參數的函數(稱為jQ ,一個局部變量,並且只有該函數知道)。 然后,您將自行調用該函數並傳入一個參數(也稱為jQuery ,但這個參數來自外部世界,是對實際 jQuery 本身的引用)。 沒有迫切需要這樣做,但有一些優點:

  • 您可以重新定義全局參數並為其命名在本地范圍內有意義。
  • 有一點性能優勢,因為在本地范圍內查找內容更快,而不必沿着范圍鏈向上進入全局范圍。
  • 壓縮(縮小)有好處。

前面我描述了這些函數如何在啟動時自動運行,但如果它們自動運行,誰在傳遞參數? 該技術假定您需要的所有參數都已定義為全局變量。 因此,如果 jQuery 尚未定義為全局變量,則此示例將不起作用。 正如您可能猜到的,jquery.js 在其初始化期間所做的一件事是定義一個“jQuery”全局變量,以及它更著名的“$”全局變量,它允許此代碼在包含 jQuery 后工作。

簡而言之

概括

以最簡單的形式,這種技術旨在將代碼包裝在函數范圍內

它有助於減少以下機會:

  • 與其他應用程序/庫沖突
  • 污染優越(全球最有可能)范圍

不會檢測文檔何時准備好 - 它不是某種document.onload也不是window.onload

它通常被稱為Immediately Invoked Function Expression (IIFE)Self Executing Anonymous Function

代碼解釋

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

在上面的示例中,函數中定義的任何變量(即使用var聲明)都將是“私有的”並且只能在函數范圍內訪問(正如 Vivin Paliath 所說)。 換句話說,這些變量在函數之外是不可見/不可訪問的。 見現場演示

Javascript具有函數作用域。 “在函數中定義的參數和變量在函數之外是不可見的,而在函數內任何地方定義的變量在函數內的任何地方都是可見的。” (來自“Javascript:好的部分”)。


更多細節

替代代碼

最后,之前貼的代碼也可以這樣寫:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

見現場演示


迭代 1

有一天,有人可能認為“必須有一種方法可以避免命名 'myMainFunction',因為我們只想立即執行它。”

如果你回到基礎,你會發現:

  • expression :評估為值的東西。 3+11/x
  • statement :代碼行做某事但它沒有評估為一個值。 if(){}

類似地,函數表達式計算為一個值。 一個后果(我假設?)是它們可以立即被調用:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

所以我們更復雜的例子變成了:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

見現場演示

迭代 2

下一步是思考“為什么要有var myMainFunction =如果我們甚至不使用它!?”。

答案很簡單:嘗試刪除它,如下所示:

 function(){ console.log('mamamia!'); }();

見現場演示

它不起作用,因為“函數聲明不可調用”

訣竅是通過刪除var myMainFunction =我們將函數表達式轉換為函數聲明 有關這方面的更多詳細信息,請參閱“資源”中的鏈接。

下一個問題是“為什么我不能將它作為函數表達式保留為var myMainFunction =以外的東西?

答案是“你可以”,實際上有很多方法可以做到這一點:添加一個+ ,一個! , a - , 或者可能用一對括號括起來(就像現在按照慣例所做的那樣),我相信還有更多。 例如:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

或者

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

或者

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

因此,一旦將相關修改添加到我們曾經的“替代代碼”中,我們就會返回與“代碼解釋”示例中使用的完全相同的代碼

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

閱讀有關Expressions vs Statements的更多信息:


揭秘范圍

人們可能想知道的一件事是“當你沒有在函數內'正確'定義變量時會發生什么——即改為進行簡單的賦值?”

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

見現場演示

基本上,如果未在其當前范圍內聲明的變量被分配一個值,則“查找范圍鏈直到它找到變量或到達全局范圍(此時它將創建它)”。

在瀏覽器環境中(與 nodejs 等服務器環境相比),全局范圍由window對象定義。 因此我們可以做window.myOtherFunction()

我關於這個主題的“良好實踐”提示是在定義任何東西時始終使用var :無論是數字、對象還是函數,甚至在全局范圍內也是如此。 這使得代碼更簡單。

筆記:

  • javascript沒有block scope (更新:在ES6中添加了塊作用域局部變量。)
  • javascript 只有function scopeglobal scope (瀏覽器環境中的window作用域)

閱讀有關Javascript Scopes的更多信息:


資源


下一步

一旦你得到這個IIFE概念,它就會導致module pattern ,這通常是通過利用這個 IIFE 模式來完成的。 玩得開心 :)

瀏覽器中的 Javascript 實際上只有幾個有效范圍:函數范圍和全局范圍。

如果變量不在函數范圍內,則它在全局范圍內。 全局變量通常很糟糕,所以這是一個將庫變量保留給自身的構造。

這叫做閉包。 它基本上將代碼密封在函數內部,以便其他庫不會干擾它。 這類似於在編譯語言中創建命名空間。

例子。 假設我寫:

(function() {

    var x = 2;

    // do stuff with x

})();

現在其他庫無法訪問我創建的要在我的庫中使用的變量x

您也可以在更大的表達式中使用函數閉包作為數據,就像在這種確定瀏覽器對某些 html5 對象的支持的方法中一樣。

   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }

除了將變量保持在本地之外,一個非常方便的用途是在使用全局變量編寫庫時,您可以給它一個較短的變量名以在庫中使用。 它經常用於編寫 jQuery 插件,因為 jQuery 允許您使用 jQuery.noConflict() 禁用指向 jQuery 的 $ 變量。 如果它被禁用,您的代碼仍然可以使用 $ 而不會中斷,如果您這樣做:

(function($) { ...code...})(jQuery);
  1. 為避免與同一窗口中的其他方法/庫發生沖突,
  2. 避免全局范圍,使其成為本地范圍,
  3. 為了使調試更快(本地范圍),
  4. JavaScript 僅具有函數作用域,因此它也有助於代碼的編譯。

我們還應該在作用域函數中使用“使用嚴格”來確保代碼應該在“嚴格模式”下執行。 示例代碼如下所示

(function() {
    'use strict';

    //Your code from here
})();

為接受的答案提供一個示例,來自https://requirejs.org/docs/whyamd.html

(function () {
    var $ = this.jQuery;

    this.myExample = function () {};
}());

代碼表明我們可以:

  1. 在范圍內使用全局變量
  2. 導出函數、變量等。通過綁定this ,這是瀏覽器的window對象。

它在 OOP 中稱為封裝

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM