Чи має такий спосіб визначення об’єктів JS якусь мету?


87

Я підтримую деякий застарілий код, і я помітив, що використовується такий шаблон для визначення об’єктів:

var MyObject = {};

(function (root) {

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

})(MyObject);

Чи є якась мета цього? Чи еквівалентно просто виконувати наступне?

var MyObject = {

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

};

Я не збираюся приступати до святого прагнення до рефакторингу всієї кодової бази на мій смак, але я б дуже хотів зрозуміти причину того обхідного способу визначення об'єктів.

Дякую!


1
У вашому точному прикладі немає різниці. Якщо ви розширите його, можливо, буде різниця, але тоді також будуть різні підходи, які також застосовуються.
Тревіс Дж.

Не має значення, об’єкти передаються як копія посилання, так би мовити, тому навіть при визначенні функції myFunction всередині IIFE, вона все одно доступна поза нею.
adeneo

1
@adeneo Не для цього прикладу, by myFunctionміг використовувати деякі змінні, визначені поза собою, які не були б доступними ззовні. Дивіться мою відповідь
Хуан Мендес,

2
можливий дублікат Як називається цей шаблон JavaScript і чому він використовується? (не впевнений, чи слід закривати). Див. Також Декларацію простору імен JavaScript або цю .
Бергі

Відповіді:


116

Це називається шаблон модуля http://toddmotto.com/mastering-the-module-pattern/

Основна причина полягає в тому, що ви створюєте справді приватні методи та змінні. У вашому випадку це не має сенсу, оскільки не приховує жодних деталей реалізації.

Ось приклад, коли має сенс використовувати шаблон модуля.

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

Тоді якби ви просто використовували прямий об’єкт adderі valueставали публічним

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

І ви можете зламати це, роблячи подібні речі

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

Але з версією модуля

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

2
Це насправді не відповідає на питання, яка різниця між двома альтернативами?

20
@torazaburo Приклад OP не був хорошим прикладом, я навів реальний приклад, який показує, коли використовувати шаблон модуля.
Хуан Мендес,

Це ns.getNext: function () {не компілюється.
punund

Я б мав, якби був впевнений, як це виправити. Я думав, що є якась конструкція для запобігання delete MyNameSpace.getNext.
punund

2
@punund JS не має компілятора, він має перекладача:)
frogatto

22

Мета полягає в обмеженні доступності функцій в рамках закриття, щоб запобігти виконанню іншими скриптами коду на ньому. Обертаючи його навколо закриття, ви перевизначаєте область виконання для всього коду всередині закриття та ефективно створюєте приватну область. Для отримання додаткової інформації дивіться цю статтю:

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

Зі статті:

Однією з найвідоміших проблем JavaScript є його залежність від глобальної області дії, що в основному означає, що будь-які змінні, які ви оголошуєте поза функцією, живуть в одному просторі імен: зловісний об'єкт вікна. Через природу веб-сторінок багато сценаріїв з різних джерел можуть (і будуть) запускатись на одній і тій же сторінці із загальним глобальним обсягом, і це може бути насправді дуже погано, оскільки може призвести до зіткнень імен (змінних з однаковими іменами проблеми із безпекою. Щоб мінімізувати проблему, ми можемо використовувати потужні засоби закриття JavaScript для створення приватних областей, де ми можемо бути впевнені, що наші змінні невидимі для інших сценаріїв на сторінці.



Код:

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

1
myFunctionу другій версії не є глобальним. Мета фактично полягає у забезпеченні сфери, де можна було б визначити внутрішні допоміжні функції.
Бармар

myFunctionзнаходиться у глобальній області дії, оскільки вона визначена в рамках глобального об’єкта myObject. У другій версії може виконуватися будь-який інший код програми myFunction. У першій версії доступ має лише код, який знаходиться в закритому вікніmyFunction
Джонатан Кроу

Ні, myFunctionможе виконуватися лише як MyObject.myFunction(), така ж, як і перша версія.
Бармар,

@JonathanCrowe Приклад OP не є хорошим прикладом, він викриває все, що знаходиться всередині модуля, так що так, він стає доступним зовні. Дивіться мою відповідь на корисний випадок модуля
Хуан Мендес,

@JuanMendes хороший момент, приклад OP не надто широко використовує шаблон модуля
Джонатан Кроу

6

переваги:

  1. підтримує змінні в приватному масштабі.

  2. Ви можете розширити функціональність існуючого об'єкта.

  3. продуктивність підвищується.

Я думаю, що вищезазначених трьох простих пунктів достатньо, щоб дотримуватися цих правил. І щоб це було простіше, це не що інше, як написання внутрішніх функцій.


6

У конкретному випадку, який ви показуєте, немає суттєвої різниці з точки зору функціональності та видимості.

Цілком ймовірно, що оригінальний кодер прийняв цей підхід як свого роду шаблон, що дозволяє йому визначати приватні змінні, які можуть бути використані у визначенні таких речей, як myFunction:

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

Це дозволяє уникнути обчислення seconds_per_dayкожного разу, коли викликається функція, одночасно утримуючи її від забруднення глобальної області дії.

Однак нічого суттєво не відрізняється від цього і просто кажуть

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

Оригінальний кодер, можливо, вважав за краще мати можливість додавати функції до об'єкта, використовуючи декларативний синтаксис root.myFunction = function, а не синтаксис об'єкта / властивості myFunction: function. Але ця різниця головним чином полягає у перевазі.

Однак структура, прийнята оригінальним кодером, має ту перевагу, що властивості / методи можна легко додати деінде в коді:

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

Підсумок: немає необхідності застосовувати цей підхід, якщо вам це не потрібно, але також не потрібно його змінювати, оскільки він чудово працює і насправді має деякі переваги.


6
  1. Перший шаблон може бути використаний як модуль, який приймає об'єкт і повертає цей об'єкт з деякими змінами. Іншими словами, ви можете визначити такі модулі наступним чином.

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

    І використовувати його як:

    var obj = {};
    module(obj);
    

    Отже, перевагою може бути повторне використання цього модуля для подальшого використання.


  1. На першому шаблоні ви можете визначити приватну область для зберігання ваших приватних речей, таких як приватні властивості та методи. Наприклад, розглянемо цей фрагмент:

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

  1. Цей шаблон можна використовувати для додавання методу або властивості до всіх типів об'єктів, таких як масиви, літерали об'єктів, функції.

    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
    

На мою думку, головною перевагою може бути друга (створення приватної сфери)


5

Цей шаблон забезпечує область, у якій ви можете визначити допоміжні функції, які не видно в глобальній області:

(function (root) {

    function doFoo() { ... };

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

})(MyObject);

doFoo є локальним для анонімної функції, на нього не можна посилатися ззовні.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.