Como é chamado esse padrão JavaScript e por que é usado?

100

Estou estudando THREE.js e percebi um padrão em que as funções são definidas assim:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Veja o exemplo do método de raycast aqui ).

A variação normal de tal método seria assim:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

Comparando a primeira versão com a variação normal , a primeira parece diferir em que:

  1. Ele atribui o resultado de uma função autoexecutável.
  2. Ele define uma variável local dentro desta função.
  3. Ele retorna a função real contendo a lógica que faz uso da variável local.

Portanto, a principal diferença é que na primeira variação a barra é atribuída apenas uma vez, na inicialização, enquanto a segunda variação cria essa variável temporária toda vez que é chamada.

Meu melhor palpite sobre por que isso é usado é que ele limita o número de instâncias de bar (haverá apenas uma) e, portanto, economiza sobrecarga de gerenciamento de memória.

Minhas perguntas:

  1. Esta suposição está correta?
  2. Existe um nome para este padrão?
  3. Por que isso é usado?
Patrick Klug
fonte
1
@ChrisHayes bastante justo. Eu o etiquetei como THREE.js porque pensei que os colaboradores do THREE.js são os mais qualificados para responder a isso, mas sim, é uma pergunta JS genérica.
Patrick Klug
2
Eu acredito que se chama encerramentos. Você pode ler sobre eles.
StackFlowed em
1
Se este for o único lugar em que um Bar é instanciado, então é um padrão singleton .
Paul
8
Não necessariamente para salvar a memória, mas pode manter o estado entre as invocações
Juan Mendes
2
@wrongAnswer: não exatamente. aqui, a função anônima (que seria o encerramento) é executada imediatamente.
njzk2

Respostas:

100

Suas suposições estão quase corretas. Vamos revisá-los primeiro.

  1. Ele atribui o retorno de uma função autoexecutável

Isso é chamado de expressão de função invocada imediatamente ou IIFE

  1. Ele define uma variável local dentro desta função

Essa é a maneira de ter campos de objetos privados em JavaScript, pois de outra forma não fornece a privatepalavra - chave ou funcionalidade.

  1. Ele retorna a função real contendo a lógica que faz uso da variável local.

Novamente, o ponto principal é que essa variável local é privada .

Existe um nome para este padrão?

AFAIK você pode chamar esse padrão de Módulo de Padrão . Citando:

O padrão de Módulo encapsula "privacidade", estado e organização usando fechamentos. Ele fornece uma maneira de agrupar uma mistura de métodos e variáveis ​​públicos e privados, protegendo as peças contra vazamento no escopo global e colisão acidental com a interface de outro desenvolvedor. Com esse padrão, apenas uma API pública é retornada, mantendo todo o resto dentro do encerramento privado.

Comparando esses dois exemplos, meus melhores palpites sobre por que o primeiro é usado são:

  1. Ele está implementando o padrão de design Singleton.
  2. Pode-se controlar a maneira como um objeto de um tipo específico pode ser criado usando o primeiro exemplo. Uma correspondência próxima a esse ponto pode ser os métodos de fábrica estáticos, conforme descrito em Java Efetivo.
  3. É eficiente se você precisar do mesmo estado de objeto todas as vezes.

Mas se você só precisa do objeto vanilla todas as vezes, então esse padrão provavelmente não adicionará nenhum valor.

MD. Sahib Bin Mahboob
fonte
1
+1 para identificar corretamente o padrão do livro de Addy Osmani. Você está correto em sua nomenclatura - este é de fato o padrão de módulo - o padrão de módulo revelador nisso, a propósito.
Benjamin Gruenbaum
4
Eu concordo com sua resposta, exceto a parte 'sem variáveis ​​privadas prontas para usar'. Todas as variáveis ​​JS têm escopo léxico 'pronto para uso', que é um mecanismo mais poderoso / geral do que variáveis ​​"privadas" (por exemplo, como encontradas em Java); portanto, JS oferece suporte a variáveis ​​"privadas" como um caso especial da maneira como trata todas as variáveis.
Warbo
Acho que por "variáveis ​​privadas", você quis dizer "campo de objeto" privado
Ian Ringrose,
1
Apenas para os detalhes: klauskomenda.com/code/javascript-programming-patterns/#module
Nagaraj Tantri
4
@LukaHorvat: na verdade, javascript não é mais "poderoso" do que outras linguagens (prefiro o termo expressivo). Na verdade, é menos expressivo, pois a única maneira de proteger suas variáveis ​​é incluí-las em funções para evitar qualquer bobagem de reutilização de variável. O padrão de módulo é um requisito definitivo para fazer um bom código javascript, mas não é um recurso da linguagem, é mais uma solução alternativa para evitar ser mordido pelos pontos fracos da linguagem.
Falanwe
11

Limita os custos de inicialização do objeto e, adicionalmente, garante que todas as invocações de função usem o mesmo objeto. Isso permite, por exemplo, que o estado seja armazenado no objeto para uso em futuras invocações.

Embora seja possível que isso limite o uso de memória, geralmente o GC coletará objetos não utilizados de qualquer maneira, portanto, esse padrão provavelmente não ajudará muito.

Esse padrão é uma forma específica de fechamento .

Fengyang Wang
fonte
1
Em JS é geralmente referido como 'módulo'
Lesha Ogonkov
2
Eu não chamaria isso de "uma forma específica de fechamento", por si só. É um padrão que usa um fechamento. O nome do padrão ainda está em jogo.
Chris Hayes
4
Ele realmente precisa de um nome? Tudo tem que ser um padrão? Precisamos realmente de uma taxonomia infinita de variantes de "padrão de módulo"? Não pode ser apenas "um IIFE com algumas variáveis ​​locais que retorna uma função?"
Dagg Nabbit
3
@DaggNabbit Quando a pergunta é "como é chamado esse padrão"? Sim, precisa de um nome ou então um argumento convincente de que não tem. Além disso, os padrões existem por uma razão. Não entendo por que você está reclamando deles aqui.
Chris Hayes
4
@ChrisHayes se precisa de um nome, por que você precisaria argumentar que não tem um? Isso não faz sentido. Se precisa de um, não deve ter. Não tenho problemas com padrões, mas não acho que seja necessário classificar cada idioma simples como um padrão. Fazer isso leva a pensar de maneiras limitadas ("Este é um padrão de módulo? Estou usando o padrão de módulo corretamente?" Vs. "Eu tenho um IIFE com algumas variáveis ​​locais que retornam uma função, este design funciona para mim?")
Dagg Nabbit
8

Não tenho certeza se esse padrão tem um nome mais correto, mas isso parece um módulo para mim, e o motivo pelo qual é usado é para encapsular e manter o estado.

O encerramento (identificado por uma função dentro de uma função) garante que a função interna tenha acesso às variáveis ​​dentro da função externa.

No exemplo que você deu, a função interna é retornada (e atribuída a foo) executando a função externa, o que significa que tmpObjectcontinua a viver dentro do encerramento e várias chamadas para a função interna foo()operarão na mesma instância de tmpObject.

itsmejodie
fonte
5

A principal diferença entre o seu código e o código Three.js é que no código Three.js a variável tmpObjecté inicializada apenas uma vez e, em seguida, compartilhada por cada invocação da função retornada.

Isso seria útil para manter algum estado entre as chamadas, semelhante a como as staticvariáveis ​​são usadas em linguagens semelhantes a C.

tmpObject é uma variável privada visível apenas para a função interna.

Ele muda o uso da memória, mas não é projetado para economizar memória.

mcfedr
fonte
5

Eu gostaria de contribuir com esse segmento interessante estendendo para o conceito do padrão de módulo revelador, que garante que todos os métodos e variáveis ​​sejam mantidos privados até que sejam explicitamente expostos.

insira a descrição da imagem aqui

No último caso, o método de adição seria chamado de Calculator.add ();

carlodurso
fonte
0

No exemplo fornecido, o primeiro snippet usará a mesma instância de tmpObject para cada chamada à função foo (), onde, como no segundo snippet, tmpObject será uma nova instância todas as vezes.

Uma razão pela qual o primeiro fragmento pode ter sido usado é que a variável tmpObject pode ser compartilhada entre chamadas para foo (), sem que seu valor vaze para o escopo em que foo () é declarado.

A versão da função não executada imediatamente do primeiro snippet seria realmente semelhante a esta:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Observe, entretanto, que esta versão tem tmpObject no mesmo escopo que foo (), portanto, ele pode ser manipulado posteriormente.

A melhor maneira de obter a mesma funcionalidade seria usar um módulo separado:

Módulo 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Módulo 2:

var foo = require('./foo');

Uma comparação entre o desempenho de um IEF e uma função de criador de foo nomeada: http://jsperf.com/ief-vs-named-function

Kory Nunn
fonte
3
Seu exemplo 'melhor' só funciona em NodeJS e você não explicou como é melhor.
Benjamin Gruenbaum
Fazer um módulo separado não é "melhor", é apenas diferente. Em particular, é uma forma de reduzir funções de ordem superior a objetos de primeira ordem. O código de primeira ordem tende a ser mais fácil de percorrer, mas geralmente é mais detalhado e nos força a reificar os resultados intermediários.
Warbo
Os módulos @BenjaminGruenbaum não estão apenas no Node, existem muitas soluções de módulo do lado do cliente, por exemplo, browserify. Considero a solução do módulo "melhor" porque é mais legível, mais fácil de depurar e é mais explícita sobre o que está no escopo e onde.
Kory Nunn