Яка мета загортати цілі файли Javascript в анонімні функції, такі як «(функція () {…}) ()»?


584

Останнім часом я читав багато Javascript і помічав, що весь файл загортається так, як у наступні файли .js для імпорту.

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

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


6
Оскільки я думаю, що цим користуватимуться багато людей, будь ласка, не забувайте про закриття;
dgh

5
Ця техніка називається "IIFE", я думаю. Це означає «Виявлення негайно викликаних функцій» en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Відповіді:


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()! Так досить схоже на визначення класу. Щоб краще зрозуміти це, я рекомендую наступні посилання для подальшого читання:

EDIT

Я забув згадати. У цьому фіналі ()ви можете передати все, що завгодно, всередині. Наприклад, коли ви створюєте плагіни jQuery, ви переходите в таке jQueryчи $так:

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

Отже, що ви робите тут, це визначення функції, яка бере один параметр (називається jQ, локальна змінна і відома лише цій функції). Тоді ви самостійно викликаєте функцію та передаєте параметр (також називається jQuery, але цей - із зовнішнього світу та посилання на власне jQuery). Для цього немає гострої необхідності, але є деякі переваги:

  • Ви можете переглянути глобальний параметр і надати йому ім'я, яке має сенс у локальній області.
  • Є невелика перевага в продуктивності, оскільки швидше шукати речі в локальному масштабі, а не потрібно переходити по ланцюжку сфери в глобальну сферу.
  • Є переваги для стиснення (мінімізації).

Раніше я описав, як ці функції запускаються автоматично при запуску, але якщо вони запускаються автоматично, хто передає аргументи? Ця методика передбачає, що всі необхідні вам параметри вже визначені як глобальні змінні. Тож якби jQuery не був визначений як глобальна змінна, цей приклад не працював. Як ви можете здогадатися, однією з речей, яку робить jquery.js під час її ініціалізації, є визначення глобальної змінної 'jQuery', а також її більш відома глобальна змінна '$', яка дозволяє цьому коду працювати після включення jQuery.


14
Дуже круто, я добре розумію простір імен, але я бачив багато свого останнього прикладу і не міг зрозуміти, чого люди намагаються досягти. Це дійсно очищає речі.
Ендрю Коу

34
Дивовижний пост. Дуже дякую.
Даррен

4
Я думаю, додаючи провідну та крапку з комою ";" зробить приклад завершеним. ;(function(jQ) { ... code ... })(jQuery);Таким чином, якщо хтось залишив крапку з комою у своєму сценарії, він не зламає ваш, особливо якщо ви плануєте мінімізувати та поєднувати свій сценарій з іншим.
Тарас Аленін

3
приємний пост, мені подобається наголос на приватних змінних. Мені також подобається відкриття на схемі модуля / закриття (public_function1 & public_function2) і те, як ви передаєте змінні, хоча трохи виходячи з сфери застосування, це приємне вступ. Я також додав відповідь. Цей акцент зосереджується на тому, що я гадаю, є коренями синтаксису та відмінностей між функцією твердження та експресією функції та тим, що, на мою думку, є "просто умовою" проти "єдиним способом досягти цього результату".
Адрієн Бе

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 =?

Відповідь "ти можеш", і насправді існує багато способів, як ти це зробиш: додавання а +, а !, а -чи, можливо, загортання в пару круглих дужок (як це зараз робиться за умовами), і багато іншого я вважаю. Наприклад:

 (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. Приємно :)


Дуже корисний. Дуже дякую!
Крістофер Гелгелін Халд

Приємно, я віддаю перевагу демо- версії :)
Fabrizio Bertoglio

Таке чудове пояснення. Дякую!
Вікрам Хемлані

26

Javascript у веб-переглядачі дійсно має пару ефективних областей: область функціонування та глобальний обсяг.

Якщо змінна не в області функцій, то в глобальній області. І глобальні змінні, як правило, погані, тому це конструкція для збереження змінних бібліотеки до себе.


1
Але чи не сама функція конструктора не надає можливості для власних змінних?
Ендрю Коу

1
Так, кожна функція, визначена в цій бібліотеці, могла б визначити власні локальні змінні, але це дозволяє поділити змінні між функціями, не витікаючи з них за межі бібліотеки
Гарет

@Gareth, тож це дозволяє "глобальні" змінні в межах сфери (;
Francisco Presencia

2
@FranciscoPresencia "глобальна рамка" не є корисною фразою, оскільки це в основному лише те, що означає "область". Вся суть "глобальної" сфери полягає в тому, що саме це сфера доступу до всіх інших областей.
Гарет

19

Це називається закриттям. В основному він запечатує код всередині функції, щоб інші бібліотеки не заважали йому. Це схоже на створення простору імен у зібраних мовах.

Приклад. Припустимо, я пишу:

(function() {

    var x = 2;

    // do stuff with x

})();

Тепер інші бібліотеки не можуть отримати доступ до змінної, яку xя створив для використання у своїй бібліотеці.


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;
     })()
    }

Що робить !! робити?
1,21 гігаватт

!! перетворює значення в його бульне (справжнє / хибне) подання.
Ліам

7

Крім збереження змінних місцевим, дуже зручним є використання під час написання бібліотеки за допомогою глобальної змінної, ви можете дати їй коротше ім'я змінної для використання в бібліотеці. Він часто використовується при написанні плагінів jQuery, оскільки jQuery дозволяє відключити змінну $, що вказує на jQuery, використовуючи jQuery.noConflict (). Якщо його вимкнено, ваш код все одно може використовувати $, а не ламатися, якщо ви просто зробите:

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

3
  1. Щоб уникнути зіткнення з іншими методами / бібліотеками в одному вікні,
  2. Уникайте глобальної сфери, зробіть її локальною сферою,
  3. Щоб налагодити роботу швидше (локальна область),
  4. JavaScript має лише функціональну область, тому він також допоможе у складанні кодів.

1

Ми також повинні використовувати функцію "використовувати строгий" у функції області, щоб переконатися, що код повинен виконуватися в "суворому режимі". Зразок коду, показаний нижче

(function() {
    'use strict';

    //Your code from here
})();

Чому ми повинні використовувати суворі?
nbro

Перегляньте цю статтю: stackoverflow.com/questions/1335851/…
Neha Jain

Насправді не відповідає на питання!
Прітам Банерджі

Pritam, його хороша практика використання. Будь ласка, зробіть належне дослідження, перш ніж проголосувати будь-яку відповідь
Neha Jain,

1
"використовувати строгий", рятує від себе поганих програмістів. А оскільки більшість програмістів є поганими програмістами, це допомагає їм заважати робити те, чого вони точно не повинні робити, і закінчується швидким промахом коду.
МЕТЕ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.