Какова цель упаковки целых файлов Javascript в анонимные функции, такие как «(function () {…}) ()»?

584

В последнее время я много читал Javascript и заметил, что весь файл упакован, как показано ниже, в импортируемые файлы .js.

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

В чем причина этого, а не простой набор функций конструктора?

Эндрю Коу
источник
6
Так как я думаю, что это будет использоваться многими людьми, пожалуйста, не забывайте закрытие;
2013 года
5
Эта техника называется "IIFE", я думаю. Это означает «немедленное выражение вызываемой функции» en.wikipedia.org/wiki/Immediately-invoked_function_expression
Адриен Бе,

Ответы:

786

Обычно это пространство имен (см. Далее) и управление видимостью функций-членов и / или переменных. Думайте об этом как об определении объекта. Техническое название для него - выражение немедленного вызова функции (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()откуда угодно, но не можете звонить localFunction1или localFunction2.

Когда вы пишете (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()! Очень похоже на определение класса. Чтобы лучше это понять, я рекомендую следующие ссылки для дальнейшего чтения:

РЕДАКТИРОВАТЬ

Я забыл упомянуть. В этом финале ()вы можете передать все, что хотите внутри. Например, когда вы создаете плагины jQuery, вы передаете jQueryили $вот так:

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

Итак, что вы делаете здесь - это определение функции, которая принимает один параметр (вызываемый jQ, локальная переменная и известный только этой функции). Затем вы вызываете функцию самостоятельно и передаете параметр (также вызываемый jQuery, но этот - из внешнего мира и является ссылкой на сам jQuery). В этом нет необходимости, но есть некоторые преимущества:

  • Вы можете переопределить глобальный параметр и дать ему имя, которое имеет смысл в локальной области видимости.
  • Есть небольшое преимущество в производительности, так как быстрее искать вещи в локальной области, вместо того, чтобы идти по цепочке областей в глобальную область.
  • Есть преимущества для сжатия (минификация).

Ранее я описывал, как эти функции запускаются автоматически при запуске, но если они запускаются автоматически, кто передает аргументы? Этот метод предполагает, что все необходимые параметры уже определены как глобальные переменные. Так что, если бы jQuery еще не был определен как глобальная переменная, этот пример не работал бы. Как вы можете догадаться, во время своей инициализации jquery.js определяет глобальную переменную 'jQuery', а также ее более известную глобальную переменную '$', которая позволяет этому коду работать после включения jQuery.

Вивин Палиат
источник
14
Очень круто, я хорошо понимаю пространство имен, но я видел много вашего последнего примера и не мог понять, чего пытались достичь люди. Это действительно проясняет ситуацию.
Эндрю Коу
34
Потрясающий пост. Большое спасибо.
Даррен
4
Я думаю добавить точку с запятой и конечную точку с запятой ';' завершил бы пример - ;(function(jQ) { ... code ... })(jQuery);таким образом, если кто-то пропустил точку с запятой в своем сценарии, это не сломало бы ваш, особенно если вы планируете минимизировать и объединить свой сценарий с другим.
Тарас Аленин
3
хороший пост, мне нравится акцент на частных переменных. Мне также нравится открытие в модуле pattern / closures (public_function1 & public_function2) и как вы передаете переменные, даже если немного выйти из области видимости это хорошее введение. Я также добавил ответ, который фокусируется на том, что, я полагаю, является корнем синтаксиса и различий между оператором функции и выражением функции и что я думаю, что это «просто соглашение» против «единственного способа достижения этого результата».
Adrien Be
4
Отличный пост, я думаю, может быть, больше о том, как полезно передавать переменные в самозапускающуюся функцию. Контекст в самоисполняющейся функции чистый - данных нет. Вы можете передать контекст, выполнив это, (function (context) { ..... })(this)что затем позволит вам присоединить все, что вам нравится, к родительскому контексту, тем самым раскрывая его.
Каллум Линнингтон,
79

Короче говоря

Резюме

В своей простейшей форме этот метод стремится заключить код в область действия функции .

Это помогает уменьшить шансы на:

  • столкновение с другими приложениями / библиотеками
  • загрязняющий превосходящий (наиболее вероятный глобальный) охват

Она не определяет , когда документ будет готов - это не какое - то 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Е. Объявленная с использованием ), будет «закрытой» и будет доступна ТОЛЬКО в области действия функции (как говорит Вивин Палиат). Другими словами, эти переменные не видны / недоступны вне функции. Смотрите живую демонстрацию .

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 +, a !, 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 scope& global scope( windowобласть видимости в среде браузера)

Узнайте больше о Javascript Scopes:


Ресурсы


Следующие шаги

Как только вы получите эту IIFEконцепцию, она приведет к тому module pattern, что обычно делается путем использования этого шаблона IIFE. Радоваться, веселиться :)

Адриен Бе
источник
Очень полезно. Большое спасибо!
Кристоффер Хельгелин Холд
Хорошо, я предпочитаю демо- версию :)
Фабрицио Бертольо
Такое прекрасное объяснение. Спасибо!
Викрам Хемлани
26

Javascript в браузере действительно имеет только несколько эффективных областей действия: область действия функции и глобальная область действия.

Если переменная не находится в области видимости функции, она находится в глобальной области видимости. А глобальные переменные, как правило, плохие, так что это конструкция для хранения переменных библиотеки при себе.

Gareth
источник
1
Но разве функция конструктора сама по себе не предоставляет область действия для своих переменных?
Эндрю Коу
1
Да, каждая функция, определенная в этой библиотеке, может определять свои собственные локальные переменные, но это позволяет переменным распределяться между функциями без их утечки за пределы библиотеки
Gareth
@ Гарет, так что это позволяет использовать «глобальные» переменные в области видимости (;
Francisco Presencia
2
@FranciscoPresencia "глобальный в рамках" не является полезной фразой, потому что это в основном то, что означает "область". Весь смысл "глобальной" области видимости состоит в том, что именно к этой области имеют доступ все остальные области.
Гарет
19

Это называется закрытием. Он в основном запечатывает код внутри функции, чтобы другие библиотеки не мешали ему. Это похоже на создание пространства имен в скомпилированных языках.

Пример. Предположим, я пишу:

(function() {

    var x = 2;

    // do stuff with x

})();

Теперь другие библиотеки не могут получить доступ к переменной, которую xя создал для использования в моей библиотеке.

Joel
источник
7
Осторожнее с вашей терминологией. Пространство имен подразумевает, что к переменным можно получить доступ извне, обратившись к пространству имен (обычно с помощью префикса). Хотя это возможно в Javascript, это не то, что продемонстрировано здесь
Гарет
Я согласен , что это не так же , как пространство имен, однако, вы можете предоставить подобную функциональность, возвращая объект со свойствами , которые вы хотите опубликовать: (function(){ ... return { publicProp1: 'blah' }; })();. Очевидно, что не совсем параллельно пространству имен, но это может помочь думать об этом таким образом.
Джоэл
в вашем примере x по-прежнему является частной переменной ... Несмотря на то, что вы заключили ее в IIFE. иди и попробуй получить доступ к x вне функции, ты не можешь ..
RayLoveless
Ваша точка зрения недействительна. Даже в следующей функции другие библиотеки не могут получить доступ к x. function () {var x = 2}
RayLoveless
@RayLoveless Я согласен. Я не противоречу этому утверждению. На самом деле я сделал то же утверждение, что и последнее предложение этого ответа.
Джоэл
8

Вы также можете использовать замыкания функций в качестве данных в больших выражениях, как в этом методе определения поддержки браузером некоторых объектов 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;
     })()
    }
Kennebec
источник
Что это !! делать?
1,21 гигаватт
!! преобразует значение в его логическое (true / false) представление.
Лиам
7

Помимо сохранения локальных переменных, очень удобно использовать при написании библиотеки с использованием глобальной переменной, вы можете дать ей более короткое имя переменной для использования в библиотеке. Он часто используется при написании плагинов jQuery, поскольку jQuery позволяет отключить переменную $, указывающую на jQuery, с помощью jQuery.noConflict (). Если он отключен, ваш код все еще может использовать $ и не прерываться, если вы просто сделаете:

(function($) { ...code...})(jQuery);
Coronus
источник
3
  1. Чтобы избежать столкновения с другими методами / библиотеками в том же окне,
  2. Избегайте Глобальной области, сделайте ее локальной,
  3. Чтобы ускорить отладку (локальная область),
  4. JavaScript имеет только область действия функции, поэтому он также поможет в компиляции кодов.
Вивек Мехта
источник
1

Мы также должны использовать «использовать строгий» в функции объема, чтобы убедиться, что код должен выполняться в «строгом режиме». Пример кода показан ниже

(function() {
    'use strict';

    //Your code from here
})();
Неха Джайн
источник
Почему мы должны использовать строгий?
nbro
Проверьте эту статью: stackoverflow.com/questions/1335851/…
Neha Jain
Не очень отвечает на вопрос!
Притам Банерджи
Притам, это хорошая практика использования. Пожалуйста, сделайте правильное исследование, прежде чем голосовать за любой ответ
Неха Джейн
1
«строгое использование» избавляет плохих программистов от самих себя. А поскольку большинство программистов являются плохими программистами, это помогает им не делать то, что им определенно не следует делать, и заканчивать быстро тонущим беспорядком кода.
MattE