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.


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.


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.


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

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.


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.


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.


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"

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.