Essa maneira de definir objetos JS tem algum propósito?

87

Estou mantendo algum código legado e percebi que o seguinte padrão para definir objetos é usado:

var MyObject = {};

(function (root) {

    root.myFunction = function (foo) {
        //do something
    };

})(MyObject);

Existe algum propósito para isso? É equivalente a apenas fazer o seguinte?

var MyObject = {

    myFunction : function (foo) {
        //do something
    };

};

Não estou prestes a embarcar em uma busca sagrada para refatorar toda a base de código de acordo com meus gostos, mas eu realmente gostaria de entender a razão por trás dessa maneira indireta de definir objetos.

Obrigado!

Sebastián Vansteenkiste
fonte
1
No seu exemplo exato, não há diferença. Se você expandi-lo, pode haver uma diferença, mas também haverá diferentes abordagens que entram em jogo.
Travis J
Não faz diferença, os objetos são passados ​​como uma cópia de uma referência, por assim dizer, então, mesmo ao definir o myFunction dentro do IIFE, ele ainda estará acessível fora dele.
adeneo
1
@adeneo Não para este exemplo, por myFunctionpoderia usar algumas variáveis ​​definidas fora de si mesmo que não seriam acessíveis de fora. Veja minha resposta
Juan Mendes
2
possível duplicata de Como esse padrão JavaScript é chamado e por que é usado? (não tenho certeza se devo fechar). Consulte também a Declaração de namespace JavaScript ou esta .
Bergi

Respostas:

116

É chamado de padrão de módulo http://toddmotto.com/mastering-the-module-pattern/

O principal motivo é para você criar métodos e variáveis ​​verdadeiramente privados. No seu caso, não é significativo porque não esconde nenhum detalhe de implementação.

Aqui está um exemplo em que faz sentido usar o padrão de módulo.

var MyNameSpace = {};

(function(ns){
    // The value variable is hidden from the outside world
    var value = 0;

    // So is this function
    function adder(num) {
       return num + 1;
    }

    ns.getNext = function () {
       return value = adder(value);
    }
})(MyNameSpace);

var id = MyNameSpace.getNext(); // 1
var otherId = MyNameSpace.getNext(); // 2
var otherId = MyNameSpace.getNext(); // 3

Considerando que se você apenas usasse um objeto reto addere valuese tornasse público

var MyNameSpace = {
    value: 0,
    adder: function(num) {
       return num + 1;
    },
    getNext: function() {
       return this.value = this.adder(this.value);
    }
}

E você poderia quebrá-lo fazendo coisas como

MyNameSpace.getNext(); // 1
MyNameSpace.value = 0;
MyNameSpace.getNext(); // 1 again
delete MyNameSpace.adder;
MyNameSpace.getNext(); // error undefined is not a function

Mas com a versão do módulo

MyNameSpace.getNext(); // 1
 // Is not affecting the internal value, it's creating a new property
MyNameSpace.value = 0;
MyNameSpace.getNext(); // 2, yessss
// Is not deleting anything
delete MyNameSpace.adder;
MyNameSpace.getNext(); // no problemo, outputs 3
Juan Mendes
fonte
2
Isso realmente não responde à questão de qual é a diferença entre as duas alternativas?
20
@torazaburo O exemplo do OP não foi um bom exemplo, eu forneci um exemplo real que mostra quando usar o padrão de módulo.
Juan Mendes
Isso ns.getNext: function () {não vai compilar.
punund
Eu teria, se tivesse certeza de como consertar. Achei que houvesse alguma construção a ser evitada delete MyNameSpace.getNext.
punund
2
@punund JS não tem compilador, ele tem interpretador:)
frogatto
22

O objetivo é limitar a acessibilidade de funções dentro do encerramento para ajudar a evitar que outros scripts executem código nele. Ao envolvê-lo em um encerramento, você está redefinindo o escopo de execução de todo o código dentro do encerramento e criando efetivamente um escopo privado. Veja este artigo para mais informações:

http://lupomontero.com/using-javascript-closures-to-create-private-scopes/

Do artigo:

Um dos problemas mais conhecidos em JavaScript é sua dependência de um escopo global, o que basicamente significa que todas as variáveis ​​declaradas fora de uma função vivem no mesmo espaço de nomes: o objeto de janela sinistro. Devido à natureza das páginas da web, muitos scripts de diferentes fontes podem (e irão) ser executados na mesma página compartilhando um escopo global comum e isso pode ser uma coisa realmente muito ruim, pois pode levar a colisões de nomes (variáveis ​​com os mesmos nomes sendo substituídos) e problemas de segurança. Para minimizar o problema, podemos usar os poderosos encerramentos do JavaScript para criar escopos privados, onde podemos ter certeza de que nossas variáveis ​​são invisíveis para outros scripts na página.



Código:

var MyObject = {};

(function (root) {
    function myPrivateFunction() {
       return "I can only be called from within the closure";
    }

    root.myFunction = function (foo) {
        //do something
    };    

    myPrivateFunction(); // returns "I can only be called from within the closure"

})(MyObject);


myPrivateFunction(); // throws error - undefined is not a function
Jonathan Crowe
fonte
1
myFunctionnão está no escopo global na segunda versão. O objetivo é realmente fornecer um escopo onde funções auxiliares internas possam ser definidas.
Barmar
myFunctionestá no escopo global porque é definido no objeto global myObject. Na segunda versão, qualquer outro código do aplicativo pode ser executado myFunction. Na primeira versão, apenas o código dentro do fechamento tem acesso amyFunction
Jonathan Crowe
Não, myFunctionsó pode ser executado como MyObject.myFunction(), igual à primeira versão.
Barmar
@JonathanCrowe O exemplo do OP não é um bom exemplo, está expondo tudo dentro do módulo, então sim, está se tornando acessível fora. Veja minha resposta para um caso de módulo útil
Juan Mendes
@JuanMendes bom ponto, o exemplo de OP não é um grande uso do padrão de módulo
Jonathan Crowe
6

vantagens:

  1. mantém variáveis ​​em escopo privado.

  2. você pode estender a funcionalidade do objeto existente.

  3. o desempenho é aumentado.

Acho que os três pontos simples acima são apenas o suficiente para seguir essas regras. E para mantê-lo simples, nada mais é do que escrever funções internas.

Mateen
fonte
6

No caso particular que você mostra, não há diferença significativa, em termos de funcionalidade ou visibilidade.

É provável que o codificador original tenha adotado essa abordagem como uma espécie de modelo, permitindo-lhe definir variáveis ​​privadas que poderiam ser usadas na definição de coisas como myFunction:

var MyObject = {};
(function(root) {
    var seconds_per_day = 24 * 60 * 60;   // <-- private variable
    root.myFunction = function(foo) {
        return seconds_per_day;
    };
})(MyObject);

Isso evita calcular seconds_per_daycada vez que a função é chamada, ao mesmo tempo que evita que ela polua o escopo global.

No entanto, não há nada essencialmente diferente disso e apenas dizer

var MyObject = function() {
    var seconds_per_day = 24 * 60 * 60;
    return {
        myFunction: function(foo) {
            return seconds_per_day;
        }
    };
}();

O codificador original pode ter preferido ser capaz de adicionar funções ao objeto usando a sintaxe declarativa de root.myFunction = function, em vez da sintaxe de objeto / propriedade de myFunction: function. Mas essa diferença é principalmente uma questão de preferência.

No entanto, a estrutura obtida pelo codificador original tem a vantagem de que as propriedades / métodos podem ser facilmente adicionados em outro lugar no código:

var MyObject = {};
(function(root) {
    var seconds_per_day = 24 * 60 * 60;
    root.myFunction = function(foo) {
        return seconds_per_day;
    };
})(MyObject);

(function(root) {
    var another_private_variable = Math.pi;
    root.myFunction2 = function(bar) { };
})(MyObject);

Resumindo, não há necessidade de adotar essa abordagem se não for necessário, mas também não há necessidade de alterá-la, uma vez que funciona perfeitamente bem e, na verdade, tem algumas vantagens.


fonte
6
  1. O primeiro padrão pode ser usado como um módulo que pega um objeto e retorna esse objeto com algumas modificações. Em outras palavras, você pode definir esses módulos da seguinte maneira.

    var module = function (root) {
        root.myFunction = function (foo) {
            //do something
        };
    }

    E use-o como:

    var obj = {};
    module(obj);

    Portanto, uma vantagem pode ser a reutilização deste módulo para usos posteriores.


  1. No primeiro padrão, você pode definir um escopo privado para armazenar suas coisas privadas, como propriedades e métodos privados. Por exemplo, considere este snippet:

    (function (root) {
    
        // A private property
        var factor = 3;
    
        root.multiply = function (foo) {
            return foo * factor;
        };
    })(MyObject);

  1. Esse padrão pode ser usado para adicionar um método ou propriedade a todos os tipos de objetos, como arrays, literais de objeto, funções.

    function sum(a, b) {
        return a + b;
    }
    
    (function (root) {
        // A private property
        var factor = 3;
        root.multiply = function (foo) {
            return foo * factor;
        };
    })(sum);
    
    console.log(sum(1, 2)); // 3
    console.log(sum.multiply(4)); // 12

Na minha opinião, a principal vantagem poderia ser a segunda (criar um escopo privado)

Frogatto
fonte
5

Este padrão fornece um escopo no qual você pode definir funções auxiliares que não são visíveis no escopo global:

(function (root) {

    function doFoo() { ... };

    root.myFunction = function (foo) {
        //do something
        doFoo();
        //do something else
    };

})(MyObject);

doFoo é local para a função anônima, não pode ser referenciado de fora.

Barmar
fonte