Как работает этот синтаксис JavaScript / jQuery: (функция (окно, неопределенное) {}) (окно)?

153

Вы когда-нибудь заглядывали изнутри в исходный код jQuery 1.4 и замечали, как он инкапсулируется следующим образом:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

Я прочитал статью о пространстве имен JavaScript и еще одну статью под названием « Важная пара паренов », поэтому я кое-что знаю о том, что здесь происходит.

Но я никогда не видел этот синтаксис раньше. Что это undefinedделает там? И почему windowнужно пройти, а потом снова появиться в конце?

dkinzer
источник
3
Я хотел бы добавить, что Пол Ирландский рассказывает об этом в этом видео: paulirish.com/2010/10-things-i-learned-from-the-jquery-source
Mottie,
@ Берджи, ты пометил мой вопрос как дубликат другого, но я задал вопрос почти за год до дубликата. Актерский состав должен быть наоборот.
dkinzer
@dkinzer: Речь идет не о том, чтобы спросить раньше, а о самом качественном ответе. Правда, в данном случае это шея и шея, но я нашел ответ CMS лучшим. Заходите на chat.stackoverflow.com/rooms/17, чтобы обсудить это
Bergi

Ответы:

161

Undefined является нормальной переменной и может быть изменен просто с помощью undefined = "new value";. Таким образом, jQuery создает локальную «неопределенную» переменную, которая ДЕЙСТВИТЕЛЬНО не определена.

Переменная окна сделана локальной из соображений производительности. Потому что, когда JavaScript ищет переменную, он сначала просматривает локальные переменные, пока не найдет имя переменной. Когда он не найден, JavaScript проходит через следующую область и т. Д., Пока не отфильтрует глобальные переменные. Так что, если переменная окна сделана локальной, JavaScript сможет быстрее ее найти. Дополнительная информация: ускорение JavaScript - Николас С. Закас

Винсент
источник
1
Спасибо! это было очень информативное видео. Я думаю, что вам нужно отредактировать ваш ответ, чтобы сказать "переменная окна сделана локальной". Я думаю, что это лучший ответ. Я посчитал и обнаружил, что объект окна вызывается как минимум 17 раз в исходном коде JQuery. Таким образом, должен быть значительный эффект при перемещении окна в локальную область.
dkinzer
@DKinzer Ах да, ты прав, конечно, это сделано местным. Рад, что смог вам помочь =)
Винсент
1
Согласно этому обновленному тесту jsperf.com/short-scope/5 прохождение «окна» на самом деле медленнее, когда он получает оконную константу через jQuery, и особенно плохо для Safari. Таким образом, хотя «undefined» имеет вариант использования, я не совсем уверен, что «окно» особенно полезно во всех случаях.
hexalys
1
Это также имеет положительный эффект для минимизации кода. Таким образом, каждое использование «окна» и «неопределенного» может быть заменено минифайером на более короткое имя.
TLindig
2
@ lukas.pukenis Когда вы создаете библиотеку, которая используется на миллионах веб-сайтов, не стоит делать предположения о том, что будут делать сумасшедшие.
JLRishe
54

Неопределенный

Объявление undefinedв качестве аргумента, но никогда не передача ему значения гарантирует, что оно всегда неопределено, поскольку это просто переменная в глобальной области видимости, которую можно перезаписать. Это делает a === undefinedбезопасную альтернативу typeof a == 'undefined', которая сохраняет несколько символов. Это также делает код более удобным для минификаторов, что undefinedможно сократить, uнапример, сохранив еще несколько символов.

Окно

Передача windowв качестве аргумента сохраняет копию в локальной области, что влияет на производительность: http://jsperf.com/short-scope . Все доступы windowтеперь должны проходить на один уровень меньше по цепочке областей действия. Как и в случае undefined, локальная копия снова позволяет более агрессивно минимизировать.


Примечание:

Хотя это, возможно, и не было целью разработчиков jQuery, передача windowпозволяет легче интегрировать библиотеку в серверные среды Javascript, например, node.js, где нет глобального windowобъекта. В такой ситуации необходимо изменить только одну строку, чтобы заменить windowобъект другой. В случае jQuery, фиктивный windowобъект может быть создан и передан с целью очистки HTML (библиотека, такая как jsdom, может сделать это).

Дэвид Тан
источник
2
+1 за упоминание Minification, хотелось бы +2 для jsperf - минимизированная версия (function(a,b){})(window);- aи bнамного короче windowи undefined:)
gnarf
+1 Я раньше слышал о цепочке прицелов, но забыл термин. Спасибо!
Алекс
Это также альтернатива использованию void (0), который имел в виду ту же причину: void (0) - это безопасный способ получить неопределенное значение.
glmxndr
«Что вы говорите» относится к этому другому вопросу: stackoverflow.com/questions/4945265/…
gnarf
@gnarf, спасибо за уведомление о слиянии вопросов. Я отредактирую свой ответ, чтобы сделать его более понятным;)
Дэвид Тан
16

Другие объяснили undefined. undefinedэто как глобальная переменная, которая может быть переопределена на любое значение. Этот метод заключается в предотвращении взлома всех неопределенных проверок, если кто-то что-то написал undefined = 10. Аргумент, который никогда не передается, гарантированно будет действительным undefinedнезависимо от значения переменной undefined .

Причина прохождения окна может быть проиллюстрирована на следующем примере.

(function() {
   console.log(window);
   ...
   ...
   ...
   var window = 10;
})();

Что записывает консоль? Значение windowобъекта верно? Неправильно! 10? Неправильно! Это логи undefined. Интерпретатор Javascript (или JIT-компилятор) переписывает его таким образом -

(function() {
   var window; //and every other var in this function

   console.log(window);
   ...
   ...
   ...
   window = 10;

})();

Однако, если вы получите windowпеременную в качестве аргумента, нет переменной var и, следовательно, никаких сюрпризов.

Я не знаю, делает ли это jQuery, но если windowпо какой-либо причине вы переопределяете локальную переменную где-либо в вашей функции, было бы неплохо позаимствовать ее из глобальной области видимости.

Четан С
источник
6

windowпередается таким образом на тот случай, если кто-то решит переопределить объект окна в IE, я предполагаю то же самое для undefinedслучая, если он будет переназначен каким-то образом позже.

Верхняя часть windowэтого скрипта просто называет аргумент «окно», более локальный аргумент, чем глобальная windowссылка, и то, что будет использовать код внутри этого замыкания. В windowконце фактически указывается, что передать первому аргументу, в данном случае текущее значение window... надеюсь, вы не облажались до windowтого, как это произойдет.

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

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);
Ник Крейвер
источник
Спасибо. Это имеет большой смысл. Хотя, я думаю, что ответ является комбинацией этого и того, что говорит @ C. Zakas.
dkinzer
@DKinzer Я боюсь, что я не C. Zakas;)
Винсент
@Vincent, извините за это :-)
dkinzer
5

Протестировано с 1000000 итераций. Этот вид локализации не повлиял на производительность. Ни одной миллисекунды за 1000000 итераций. Это просто бесполезно.

Semra
источник
2
Не говоря уже о том, что closure переносит все переменные вместе с экземпляром функции, поэтому, если у вас есть 100 байтов в замыкании и 1000 экземпляров, это на 100 КБ больше памяти.
Акаш Кава
@AkashKava и @Semra, я не думаю, что этот тест действительно объясняет, почему вы проходите через окно в реальной ситуации. Увеличение производительности будет видно только тогда, когда у вас есть глубоко вложенные замыкания, которые обращаются к этой переменной дальше по всей цепочке областей действия. Очевидно, что если ваша функция находится в шаге от цепочки областей видимости от переменной, вы не увидите большого выигрыша. но если бы это было вааааа там внизу и нужно было получить, windowвы могли бы увидеть некоторую выгоду от локальной копии.
gabereal
@gabereal Новые JavaScript-движки разрешают замыкания во время самой компиляции, при этом во время выполнения выигрыш в быстродействии отсутствует. Я сомневаюсь, что есть какой-то ощутимый прирост производительности, если вы так уверены, вы можете создать пример jsperf, чтобы продемонстрировать производительность. Единственная производительность, которую мы получаем, - это изоляция замыканий, что приводит к лучшей минимизации, которая уменьшает размер, а производительность достигается за счет более быстрой загрузки и анализа сценария.
Акаш Кава
@AkashKava, что заставляет тебя думать, что я так уверен? Я просто сказал «может увидеть какой-то выигрыш» и «я не думаю». Я мог бы создать тест, но я бы предпочел, потому что я все равно никогда не буду использовать этот шаблон. Можете ли вы объяснить, что вы подразумеваете под "разрешает замыкания во время самой компиляции"? в отличие от какой альтернативы? как движки Javascript делали вещи раньше?
gabereal
У вас есть код, в котором окно в конце каррируется, но не вводится в параметрах функции, что это значит? это гарантирует, что параметры следуют за исходным глобальным объектом области видимости, даже если я не предполагаю никакого параметра окна?
Webwoman