Como essa sintaxe JavaScript / jQuery funciona: (função (janela, indefinida) {}) (janela)?

153

Você já deu uma olhada no código fonte do jQuery 1.4 e percebeu como ele é encapsulado da seguinte maneira:

(function( window, undefined ) {

  //All the JQuery code here 
  ...

})(window);

Eu li um artigo sobre JavaScript Namespacing e outro chamado " An Important Pair of Parens ", para saber um pouco do que está acontecendo aqui.

Mas nunca vi essa sintaxe específica antes. O que isso está undefinedfazendo aí? E por que windowprecisa ser aprovado e depois aparecer no final novamente?

dkinzer
fonte
3
Eu queria acrescentar que Paul Irish fala sobre isso neste vídeo: paulirish.com/2010/10-things-i-learned-from-the-jquery-source
Mottie
@ Bergi, você marcou minha pergunta como uma duplicata de outra, mas eu a fiz quase um ano antes da duplicata. O elenco deve ser o contrário.
dkinzer
@ dkinzer: Não se trata de perguntas anteriores, é de uma resposta da mais alta qualidade. É certo que, neste caso, é pescoço e pescoço, mas achei a resposta do CMS a melhor. Venha para chat.stackoverflow.com/rooms/17 para discutir isso, porém
Bergi

Respostas:

161

O indefinido é uma variável normal e pode ser alterado simplesmente com undefined = "new value";. Portanto, o jQuery cria uma variável local "indefinida" que é REALMENTE indefinida.

A variável window é feita local por razões de desempenho. Porque quando o JavaScript procura uma variável, ele primeiro percorre as variáveis ​​locais até encontrar o nome da variável. Quando não é encontrado, o JavaScript passa pelo próximo escopo etc. até filtrar pelas variáveis ​​globais. Portanto, se a variável da janela for tornada local, o JavaScript poderá procurar mais rapidamente. Mais informações: Acelere seu JavaScript - Nicholas C. Zakas

Vincent
fonte
1
Obrigado! esse foi um vídeo muito informativo. Eu acho que você precisa editar sua resposta para dizer "a variável da janela é tornada local". Eu acho que essa é a melhor resposta. Contei e descobri que o objeto window é chamado pelo menos 17 vezes no código-fonte JQuery. Portanto, deve haver um efeito significativo ao mover a janela para o escopo local.
dkinzer
@DKinzer Ah, sim, você está certo, é claro que é feito local. Ainda bem que pude ajudá-lo =)
Vincent
1
De acordo com este teste atualizado, jsperf.com/short-scope/5 a passagem de 'window' é realmente mais lenta, quando se trata de obter uma janela constante através do jQuery, e particularmente ruim para o Safari. Portanto, embora 'indefinido' tenha um caso de uso, não estou totalmente convencido de que 'janela' seja particularmente útil em todos os casos.
Hexalys 28/04
1
Também tem um efeito positivo para minificar o código. Portanto, todo uso de "window" e "undefined" pode ser substituído por um nome mais curto pelo minifyer.
TLindig
2
@ lukas.pukenis Quando você cria uma biblioteca usada em milhões de sites, é aconselhável não fazer suposições sobre o que os loucos farão.
JLRishe
54

Indefinido

Declarar undefinedcomo argumento, mas nunca passar um valor para ele garante que ele seja sempre indefinido, pois é simplesmente uma variável no escopo global que pode ser substituída. Isso faz a === undefineduma alternativa segura para typeof a == 'undefined', o que salva alguns caracteres. Também torna o código mais amigável ao minifier, como undefinedpode ser reduzido para, upor exemplo, salvando mais alguns caracteres.

Janela

Passar windowcomo argumento mantém uma cópia no escopo local, o que afeta o desempenho: http://jsperf.com/short-scope . Todos os acessos windowagora precisam percorrer um nível a menos na cadeia de escopo. Assim como acontece undefined, uma cópia local novamente permite minificação mais agressiva.


Nota:

Embora isso possa não ter sido a intenção dos desenvolvedores do jQuery, a transferência windowpermite que a biblioteca seja mais facilmente integrada nos ambientes Javascript do servidor, por exemplo, node.js - onde não há windowobjeto global . Em tal situação, apenas uma linha precisa ser alterada para substituir o windowobjeto por outra. No caso do jQuery, um windowobjeto simulado pode ser criado e transmitido para fins de raspagem de HTML (uma biblioteca como o jsdom pode fazer isso).

David Tang
fonte
2
1 para mencionar Minification, gostaria de poder +2 para o JSPerf - A versão minified é (function(a,b){})(window);- ae bsão muito mais curtos do que windowe undefined:)
gnarf
Eu já tinha ouvido falar da cadeia de escopo antes, mas esqueci o termo. Obrigado!
Alex
Também é uma alternativa ao uso de void (0), que foi feito pelo mesmo motivo: void (0) é uma maneira segura de obter o valor indefinido.
glmxndr
"O que você diz" está em referência a essa outra pergunta: stackoverflow.com/questions/4945265/…
gnarf
@gnarf, obrigado pela notificação de que as perguntas foram mescladas. Vou editar a minha resposta para fazer um pouco mais de sentido;)
David Tang
16

Outros já explicaram undefined. undefinedé como uma variável global que pode ser redefinida para qualquer valor. Essa técnica é para impedir que todas as verificações indefinidas sejam quebradas se alguém escreveu digamos, em undefined = 10algum lugar. Um argumento que nunca é passado é garantido como real, undefinedindependentemente do valor da variável undefined .

A razão para passar pela janela pode ser ilustrada com o exemplo a seguir.

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

O que o console registra? O valor do windowobjeto certo? Errado! 10? Errado! Ele registra undefined. O interpretador Javascript (ou compilador JIT) o reescreve desta maneira -

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

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

})();

No entanto, se você receber a windowvariável como argumento, não haverá var e, portanto, nenhuma surpresa.

Eu não sei se o jQuery está fazendo isso, mas se você estiver redefinindo windowa variável local em qualquer lugar da sua função por qualquer motivo, é uma boa ideia emprestá-la do escopo global.

Chetan S
fonte
6

windowé passado dessa maneira, apenas no caso de alguém decidir redefinir o objeto de janela no IE, presumo o mesmo undefined, caso seja reatribuído de alguma forma posteriormente.

A parte superior windowdesse script está apenas nomeando o argumento "janela", um argumento mais local que a windowreferência global e qual o código dentro deste fechamento usará. O windowfinal está realmente especificando o que passar para o primeiro argumento, neste caso, o significado atual de window... a esperança é que você não tenha estragado tudo windowantes que isso aconteça.

Isso pode ser mais fácil de pensar, mostrando o caso mais comum usado no jQuery, .noConflict()manipulação de plug-ins , portanto, para a maioria do código você ainda pode usar $, mesmo que isso signifique algo diferente de jQueryfora deste escopo:

(function($) {
  //inside here, $ == jQuery, it was passed as the first argument
})(jQuery);
Nick Craver
fonte
Obrigado. Isso faz muitosentido. No entanto, acho que a resposta é uma combinação disso e do que @ C.Zakas diz.
dkinzer
@DKinzer Acho que não sou C. Zakas;)
Vincent
@Vincent, desculpe sobre isso :-)
dkinzer
5

Testado com 1000000 iterações. Esse tipo de localização não teve efeito no desempenho. Nem um único milissegundo em 1000000 iterações. Isso é simplesmente inútil.

Semra
fonte
2
Sem mencionar, o fechamento carrega todas as variáveis, juntamente com a instância da função; portanto, se você tiver 100 bytes no fechamento e 1000 instâncias, isso representa 100kb de mais memória.
Akash Kava
@AkashKava e @Semra, não acho que esse teste realmente explique por que você passaria pela janela em uma situação do mundo real. O ganho de desempenho só será visto quando você tiver fechamentos profundamente aninhados que acessam essa variável ainda mais ao longo da cadeia de escopo. obviamente, se sua função estiver a apenas um passo da cadeia de escopos da variável, você não verá muito ganho. mas se estivesse lá em baixo e precisasse obtê- windowlo, você poderá obter algum ganho por uma cópia local.
gabereal
@gabereal Os novos mecanismos JavaScript resolvem os fechamentos no momento da compilação, não há ganho de desempenho no fechamento em tempo de execução. Duvido que haja algum ganho mensurável de desempenho; se você estiver confiante, poderá criar um exemplo jsperf para demonstrar o desempenho. O único desempenho que obtemos é isolando os fechamentos, o que resulta em uma melhor minificação, que reduz o tamanho e o desempenho, através do download e análise mais rápidos do script.
Akash Kava
@AkashKava, o que faz você pensar que estou tão confiante? Eu apenas disse "pode ​​ver algum ganho" e "eu não acho". Eu poderia criar um teste, mas prefiro não, pois nunca uso esse padrão. você pode explicar o que você quer dizer com "resolve fechamentos no momento da compilação em si"? em oposição a que alternativa? como os mecanismos Javascript faziam as coisas de maneira diferente antes?
precisa
Encontre um código em que a janela é exibida ao final, mas não inserida nos parâmetros da função, o que significa? assegura que os parâmetros sigam o objeto de escopo global original, mesmo que não exista nenhum parâmetro de janela?
Webwoman 22/01/19