Função anônima auto-executável vs protótipo

26

Em Javascript, existem algumas técnicas claramente importantes para criar e gerenciar classes / namespaces em javascript.

Estou curioso para saber quais situações justificam o uso de uma técnica versus a outra. Eu quero escolher um e ficar com ele seguindo em frente.

Escrevo códigos corporativos que são mantidos e compartilhados entre várias equipes e quero saber qual é a melhor prática ao escrever javascript sustentável.

Eu tendem a preferir funções anônimas de auto-execução, mas estou curioso sobre o que o voto da comunidade é sobre essas técnicas.

Protótipo:

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Função anônima de fechamento automático:

//Self-Executing Anonymous Function 
(function( skillet, $, undefined ) {
    //Private Property
    var isHot = true;

    //Public Property
    skillet.ingredient = "Bacon Strips";

    //Public Method
    skillet.fry = function() {
        var oliveOil;

        addItem( "\t\n Butter \n\t" );
        addItem( oliveOil );
        console.log( "Frying " + skillet.ingredient );
    };

    //Private Method
    function addItem( item ) {
        if ( item !== undefined ) {
            console.log( "Adding " + $.trim(item) );
        }
    }     
}( window.skillet = window.skillet || {}, jQuery ));   
//Public Properties      
console.log( skillet.ingredient ); //Bacon Strips  

//Public Methods 
skillet.fry(); //Adding Butter & Fraying Bacon Strips 

//Adding a Public Property 
skillet.quantity = "12"; console.log( skillet.quantity ); //12   

//Adding New Functionality to the Skillet 
(function( skillet, $, undefined ) {
    //Private Property
    var amountOfGrease = "1 Cup";

    //Public Method
    skillet.toString = function() {
        console.log( skillet.quantity + " " + 
                     skillet.ingredient + " & " + 
                     amountOfGrease + " of Grease" );
        console.log( isHot ? "Hot" : "Cold" );
     };     

}( window.skillet = window.skillet || {}, jQuery ));
//end of skillet definition


try {
    //12 Bacon Strips & 1 Cup of Grease
    skillet.toString(); //Throws Exception 
} catch( e ) {
    console.log( e.message ); //isHot is not defined
}

Sinto que devo mencionar que a Função Anônima de Auto-Execução é o padrão usado pela equipe do jQuery.

Atualização Quando fiz essa pergunta, não percebi a importância do que estava tentando entender. O verdadeiro problema em questão é se deve ou não usar new para criar instâncias de seus objetos ou usar padrões que não exigem construtores / uso da newpalavra - chave.

Eu adicionei minha própria resposta, porque, na minha opinião, devemos usar padrões que não usam a newpalavra - chave.

Para mais informações, consulte minha resposta.

Robotsushi
fonte
1
você pode dar exemplos curtos das duas técnicas que você está descrevendo?
Ei
Não subestime o protótipo por causa da minha amostra simples.
Robotsushi
1
não está executando a si próprio = /
Hey Ei
2
eu não vejo nenhum parênteses para fechar a expressão ou chamá-lo ...
Hey
1
(+1) Os namespaces são ignorados por muitos desenvolvedores.
umlcat

Respostas:

22

Funções anônimas auto-executáveis ​​são usadas para automatizar a execução de scripts sem conectar-se a eventos externos (por exemplo, window.onload).

Neste exemplo, ele é usado para formar o padrão clássico do Módulo, cujo objetivo principal é introduzir um espaço para nome no ambiente global e fornecer encapsulamento para quaisquer propriedades internas que não são "exportadas" ou anexadas ao espaço para nome.

A modificação de um protótipo de objetos, por outro lado, é usada para estabelecer herança (ou estender nativos). Esse padrão é usado para produzir objetos 1: n com métodos ou propriedades comuns.

Você não deve escolher um padrão em preferência ao outro, pois eles executam tarefas diferentes . Em termos de espaço para nome, a Função de Auto-Execução é uma escolha apropriada.

sunwukung
fonte
7
Observe que "funções anônimas auto-executáveis" são comumente conhecidas como IIFE (expressões de função invocadas imediatamente) .
voithos 23/01
Eu os evito e, para ser sincero, não gosto do IIFEs. Eles são uma bagunça para depurar e quebrar o contorno do código no Eclipse. Se você precisar de um espaço para nome, cole-o em um objeto, se precisar executá-lo, basta chamá-lo e o encapsulamento não me renderá nada.
Daniel Sokolowski
4

Aqui está o padrão que eu comecei a usar (usei variações dele até ontem):

function MyClass() {
    // attributes
    var privateVar = null;

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

MyClass.prototype = new ParentClass(); // if required

Algumas reflexões sobre isso:

  1. Você não deve obter nenhum (anonymous)rastreio nos traços da pilha do depurador, pois tudo está nomeado (sem funções anônimas).
  2. É o padrão mais limpo que eu já vi
  3. Você pode agrupar facilmente sua API exposta sem ter suas implementações acopladas à declaração (ou seja, alguém pode facilmente inserir sua interface de classe pública sem precisar rolar)

A única vez que eu usaria prototypemais realmente é definir herança.

Demian Brecht
fonte
5
Existem alguns problemas com isso. Um novo objeto de função é criado para cada "método" com cada chamada do construtor. Além disso, chamar um construtor para obter uma cópia do objeto protótipo para herança é desaprovado. Use Object.create (ParentClass.prototype), ou um calço para Object.create comofunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hey
@GGG: Sim, você está correto com o seu primeiro ponto. Eu diria (e deveria ter mencionado na minha postagem) que cada caso de uso específico de uma implementação deve ser pensado. Meu problema com o prototypemétodo, como você sugere, é que (a menos que exista um método que eu não esteja familiarizado, que pode ser o caso), você perde a capacidade de encapsular atributos, o que significa que tudo é aberto como público (o que não é o fim do mundo). , apenas uma preferência pessoal).
Demian Brecht
Além disso, depois de visualizar o seguinte trabalho realizado em jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ), eu aceitaria o aumento de desempenho sobre o custo de memória de cópias adicionais quase todos os dias (novamente, muito subjetivo ao caso de uso específico).
9603 Demian Brecht
Agora, tendo dito tudo isso, estou apenas no meio da ECMA-262, então pode haver um monte de coisas que eu não estou vendo. Além disso, não tomo a palavra de Crockford como evangelho. Sim, ele é um deles. dos especialistas da área (um dos principais, é claro), mas nem sempre o faz 100% certo em todos os momentos. Existem outros especialistas por aí (eu não sendo um deles;)) que têm opiniões contraditórias com argumentos convincentes.
9603 Demian Brecht
2
você pode estar interessado nessa coisa em que estou trabalhando .
Ei
3

Uso protótipos porque são mais limpos e seguem padrões de herança padrão. As funções de auto-chamada são ótimas para o desenvolvimento do navegador ou para uma situação em que você não sabe onde o código está sendo executado, mas, caso contrário, é apenas ruído.

Exemplo:

var me;

function MyObject () {
    this.name = "Something";
}

MyObject.prototype.speak = function speak () {
    return "Hello, my name is " + this.name;
};

me = new MyObject();
me.name = "Joshua";
alert(me.speak());
Josh K
fonte
1
Funções anônimas auto-executáveis ​​são muito úteis para permitir que você tenha funções privadas acessíveis a uma classe.
Zee
1

Eu iria com a função de execução automática, mas com uma pequena diferença:

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

     return function MyClass() {
         this.methodOne = methodOne;
         this.methodTwo = methodTwo;
         this.publicProperty = publicProperty;
     };
})();

Se achar essa abordagem muito mais limpa, como eu separo a variável global retornada de qualquer parâmetro de entrada (como jQuery) (a maneira como você escreveu isso é equivalente a retornar nulo e usar um parâmetro ref em C #, o que acho um pouco errado, ou passando um ponteiro para um ponteiro e redesignando-o em C ++). Se eu fosse anexar métodos ou propriedades adicionais à classe, usaria herança prototípica (exemplo com o método $ .extend do jQuery, mas é fácil o suficiente estender sua própria extensão ()):

var additionalClassMethods = (function () {
    var additionalMethod = function () { alert('Test Method'); };
    return { additionalMethod: additionalMethod };
})();

$.extend(MyClass.prototype, additionalClassMethods);

var m = new MyClass();
m.additionalMethod(); // Pops out "Test Method"

Dessa forma, você tem uma distinção clara entre os métodos adicionados e os métodos originais.

Ed James
fonte
1
Eu sou o único que acha que usar NFEs como essa é uma má idéia?
Ei
1

Exemplo ao vivo

(function _anonymouswrapper(undefined) {

    var Skillet = {
        constructor: function (options) {
            options && extend(this, options);
            return this; 
        },
        ingredient: "Bacon Strips",
        _isHot: true,
        fry: function fry(oliveOil) {
            this._addItem("\t\n Butter \n\t");
            this._addItem(oliveOil);
            this._addItem(this.ingredient);
            console.log("Frying " + this.ingredient);
        },
        _addItem: function addItem(item) {
            console.log("Adding " + item.toString().trim());
        }
    };

    var skillet = Object.create(Skillet).constructor();

    console.log(skillet.ingredient);
    skillet.fry("olive oil");

    var PrintableSkillet = extend(Object.create(Skillet), {
        constructor: function constructor(options) {
            options && extend(this, options);
            return this;
        },
        _amountOfGrease: "1 Cup",
        quantity: 12,
        toString: function toString() {
            console.log(this.quantity + " " +
                        this.ingredient + " & " +
                        this._amountOfGrease + " of Grease");
            console.log(this._isHot ? "Hot" : "Cold");
        }
    });

    var skillet = Object.create(PrintableSkillet).constructor();

    skillet.toString();

    function extend(target, source) {
        Object.getOwnPropertyNames(source).forEach(function (name) {
            var pd = Object.getOwnPropertyDescriptor(source, name);
            Object.defineProperty(target, name, pd);
        });
        return target;
    }
}());

Você pode usar um IIFE para emular o "escopo do módulo" em torno do seu código. Então você pode simplesmente usar objetos como você normalmente.

Não "emule" o estado privado usando fechamentos, pois isso tem uma grande penalidade de memória.

Se você está escrevendo um aplicativo corporativo e deseja manter o uso da memória abaixo de 1 GB, evite usar desnecessariamente fechamentos para armazenar o estado.

Raynos
fonte
Você tem alguns erros de digitação no companheiro de código. Também não sou positivo quanto à sua afirmação de fechamento causando automaticamente o uso excessivo de memória, acho que depende de quão intensamente você os usa e de quão bem você está lidando com problemas de escopo (misturar coisas de dentro de um fechamento com coisas de fora é geralmente ruim, por exemplo (seu uso do console global é um bom exemplo disso, o código acima seria muito mais eficiente se você passasse no console como uma variável).
Ed James
@ EdWoodcock, imaginei que o código era de buggy, apenas refatorado e corrigido.
Raynos
@ EdWoodcock, a diferença de eficiência entre local consolee global consoleé uma micro otimização. Vale a pena fazê-lo você pode minimizar console, mas isso é uma questão diferente
Raynos
Os fechamentos @EdWoodcock são lentos se você duplicar objetos neles. O que é lento é criar funções dentro de funções quando não é necessário. fechamentos também têm pequena sobrecarga de memória para armazenar estado em comparação com o armazenamento de estado em objetos diretamente
Raynos
Sim, só queria salientar, já que sua resposta é uma maneira razoável de fazer as coisas (embora não seja a maneira que eu escolheria usar). (Eu teria feito uma edição, mas ainda não tenho certeza sobre a etiqueta das pessoas neste site específico do SE).
Ed James
0

Atualização Agora, tenho um entendimento muito melhor do javascript e sinto que posso resolver adequadamente a questão. Eu acho que foi um tópico javascript mal redigido, mas muito importante para resolver.

O padrão de função anônima de execução automática não é aquele que requer o uso da nova palavra-chave se você evitar o uso dessa função fora das funções. Concordo com a idéia de que usar o novo é uma técnica antiga e, em vez disso, devemos nos esforçar para usar padrões que evitem o uso do novo.

A função de execução automática anônima atende a esse critério.

A resposta a esta pergunta é subjetiva, porque existem muitos estilos de codificação em javascript. No entanto, com base em minha pesquisa e experiência, eu recomendaria optar por usar a função anônima auto-executável para definir suas APIs e evitar o uso de novas sempre que possível.

Robotsushi
fonte
1
O que há de ruim na nova palavra-chave? Estou curioso.
Ally
0

Aqui está como eu faria isso IIFE SelfExecutingFunctionpara estender o Myclasswith Myclass.anotherFunction();

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

    //Returning the anonymous object {} all the methods and properties are reflected in Myclass constructor that you want public those no included became hidden through closure; 
     return {
         methodOne = methodOne;
         methodTwo = methodTwo;
         publicProperty = publicProperty;
     };
})();

@@@@@@@@@@@@@@@@@@@@@@@@
//then define another IIFE SEF to add var function=anothermethod(){};
(function(obj){
return obj.anotherfunction=function(){console.log("Added to Myclass.anotherfunction");};
})(Myclass);

//the last bit : (function(obj){})(Myclass); obj === Myclass obj is an alias to pass the Myclass to IIFE

var myclass = new Myclass();
myclass.anotherfunction(); //"Added to Myclass.anotherfunction"
uma linha
fonte
1
Programadores é um tour de perguntas conceituais, as respostas devem explicar as coisas. Jogar dumps de código em vez de explicação é como copiar código do IDE para o quadro branco: pode parecer familiar e até às vezes compreensível, mas parece estranho ... apenas estranho. O quadro branco não tem compilador
gnat