Самоисполняющаяся анонимная функция против прототипа

26

В Javascript есть несколько четко известных методов для создания и управления классами / пространствами имен в javascript.

Мне любопытно, какие ситуации оправдывают использование одного метода против другого. Я хочу выбрать один и придерживаться его, двигаясь вперед.

Я пишу корпоративный код, который поддерживается и совместно используется несколькими командами, и я хочу знать, что является лучшим методом при написании поддерживаемого JavaScript?

Я склонен отдавать предпочтение самореализующимся анонимным функциям, однако мне любопытно, что сообщество проголосует за эти методы.

Прототип :

function obj()
{
}

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

Самозакрывающаяся анонимная функция:

//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
}

Я чувствую, что должен упомянуть, что самоисполняющаяся анонимная функция - это шаблон, используемый командой jQuery.

Обновление Когда я задал этот вопрос, я действительно не понял важность того, что я пытался понять. Реальная проблема под рукой - стоит ли использовать new для создания экземпляров ваших объектов или использовать шаблоны, которые не требуют конструкторов / использования newключевого слова.

Я добавил свой собственный ответ, потому что, по моему мнению, мы должны использовать шаблоны, которые не используют newключевое слово.

Для получения дополнительной информации, пожалуйста, смотрите мой ответ.

Robotsushi
источник
1
Можете ли вы привести короткие примеры двух техник, которые вы описываете?
Привет,
Не стоит недооценивать прототип из-за моего простого образца.
Робоцуши
1
это не выполняется само по себе = /
Привет
2
я не вижу скобок, чтобы закрыть выражение или назвать его ...
Привет,
1
(+1) Пространства имен упускаются из виду для многих разработчиков.
umlcat

Ответы:

22

Самоисполняющиеся анонимные функции используются для автоматизации выполнения скрипта без привязки к внешним событиям (например, window.onload).

В этом примере он используется для формирования классического шаблона Module, основная цель которого состоит в том, чтобы ввести пространство имен в глобальную среду и обеспечить инкапсуляцию для любых внутренних свойств, которые не «экспортированы» или не присоединены к пространству имен.

Изменение прототипа объекта, с другой стороны, используется для установления наследования (или расширения нативных объектов ). Этот шаблон используется для создания объектов 1: n с общими методами или свойствами.

Не следует выбирать один шаблон предпочтительнее другого, поскольку они выполняют разные задачи. С точки зрения пространства имен, Самоисполняющаяся Функция является подходящим выбором.

sunwukung
источник
7
Обратите внимание, что «самоисполняющиеся анонимные функции» обычно называются выражениями функций, вызываемыми немедленно (IIFE) .
Войтос
Я избегаю их и, честно говоря, я не получаю любовь с IIFEs. Они бесполезны для отладки и нарушения структуры кода в Eclipse. Если вам нужно пространство имен, прикрепите его к объекту, если вам нужно выполнить его, просто вызовите его, и инкапсуляция мне ничего не даст.
Даниэль Соколовский
4

Вот образец, который я только начал использовать (использовал варианты до вчерашнего дня):

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

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

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

Несколько мыслей по этому поводу:

  1. Вы не должны получать никаких (anonymous)следов в ваших следах стека отладчика, поскольку все названо (без анонимных функций).
  2. Это самый чистый образец, который я когда-либо видел
  3. Вы можете легко сгруппировать ваш открытый API без привязки их реализаций к объявлению (то есть кто-то может легко получить доступ к интерфейсу вашего открытого класса без необходимости прокрутки)

Единственное время, которое я бы использовал prototypeбольше - определение наследования.

Демиан Брехт
источник
5
Есть несколько проблем с этим. Новый функциональный объект создается для каждого «метода» с каждым вызовом конструктора. Кроме того, вызывается вызов конструктора для получения копии объекта-прототипа для наследования. Используйте Object.create (ParentClass.prototype) или прокладку для Object.create какfunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Привет
@GGG: Да, вы правы с первым пунктом. Я бы сказал (и должен был упомянуть в своем посте), что каждый конкретный вариант использования реализации должен быть продуман. Моя проблема с prototypeметодом, как вы предлагаете, заключается в том, что (если нет метода, с которым я не знаком, что может быть так), вы теряете способность инкапсулировать атрибуты, то есть все открыто как общедоступное (что не конец света). Просто личное предпочтение).
Демиан Брехт
Кроме того, после просмотра следующей работы, проделанной над jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ), я бы взял увеличение производительности по сравнению с затратами на память для дополнительных копий почти в любой день (опять же, очень субъективно в конкретном случае использования).
Демиан Брехт
Теперь, сказав все это, я только на полпути через ECMA-262, так что может быть куча вещей, которые я не вижу ... Кроме того, я не воспринимаю слово Крокфорда как Евангелие ... Да, он один из экспертов в этой области (один из главных, конечно), но это не всегда делает его на 100% правильным во все времена. Есть другие эксперты (я не являюсь одним из них;)), которые имеют противоречивые мнения с убедительными аргументами.
Демиан Брехт
2
Вас может заинтересовать то, над чем я работаю .
Привет,
3

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

Пример:

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
Самоисполняющиеся анонимные функции очень полезны для того, чтобы у вас были частные функции, доступные для класса.
Зее
1

Я бы пошел с самовыполняющейся функцией, но с небольшой разницей:

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

Если этот подход окажется чище, я отделяю возвращаемую глобальную переменную от любых входных параметров (таких как jQuery) (способ, которым вы написали его, эквивалентен возвращению void и использованию параметра ref в C #, который я немного отключил, или передавая указатель на указатель и переназначая его в C ++). Если бы тогда я собирался присоединить дополнительные методы или свойства к классу, я бы использовал наследование прототипа (например, с помощью метода jQuery $ .extend, но достаточно легко выполнить свой собственный extend ()):

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"

Таким образом, у вас есть четкое различие между добавленными методами и оригинальными.

Эд Джеймс
источник
1
Я единственный, кто считает, что использование NFE - это плохая идея?
Привет,
1

Живой пример

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

Вы можете использовать IIFE, чтобы эмулировать «область видимости модуля» вокруг вашего кода. Тогда вы можете просто использовать объекты, как обычно.

Не «эмулируйте» приватное состояние, используя замыкания, так как это приводит к большим потерям памяти.

Если вы пишете корпоративное приложение и хотите сохранить объем используемой памяти менее 1 ГБ, избегайте ненужного использования замыканий для сохранения состояния.

Raynos
источник
У вас есть пара опечаток в коде. Я не уверен в том, что вы утверждаете, что замыкания автоматически приводят к чрезмерному использованию памяти, я думаю, это зависит от того, насколько интенсивно вы их используете и насколько хорошо вы справляетесь с областями видимости (как правило, смешивание вещей изнутри замыкания с вещами извне плохо, например (ваше использование глобальной консоли является хорошим примером этого, приведенный выше код будет гораздо более эффективным, если вы передадите консоль как переменную)).
Эд Джеймс
@EdWoodcock Я подумал, что код содержит ошибки, просто рефакторинг и исправил его.
Raynos
@ EdWoodcock разница в эффективности между локальной consoleи глобальной consoleявляется микрооптимизацией . Это стоит того, чтобы вы могли свести к минимуму, consoleно это другой вопрос
Raynos
@EdWoodcock закрывает медленно, если ваши дублирующие объекты в них. Что медленно, так это создание функций внутри функций, когда они не нужны. замыкания также имеют небольшие накладные расходы памяти для хранения состояния по сравнению с хранением состояния на объектах напрямую
Raynos
Да, просто хотел указать на это, так как ваш ответ - разумный способ действовать (хотя и не так, как я бы выбрал). (Я бы сделал правку, но я все еще не уверен насчет этикета на этом конкретном сайте SE).
Эд Джеймс
0

Обновление Теперь у меня намного лучшее понимание javascript и я чувствую, что могу правильно ответить на этот вопрос. Я думаю, что это была плохо сформулированная, но очень важная тема для javascript.

Шаблон Self Executing Anonymous Function не требует использования ключевого слова new, если вы избегаете его использования вне функций. Я согласен с идеей, что использование нового - это старая техника, и вместо этого мы должны стремиться использовать шаблоны, избегающие использования новой.

Самоисполняющаяся анонимная функция соответствует этому критерию.

Ответ на этот вопрос субъективен, потому что в javascript так много стилей кодирования. Однако, основываясь на моих исследованиях и опыте, я бы порекомендовал выбрать использование самоисполняющейся анонимной функции для определения ваших API и, по возможности, избегать использования новых.

Robotsushi
источник
1
Что плохого в новом ключевом слове? Мне любопытно.
Союзник
0

Вот как я мог бы пойти на это, IIFE SelfExecutingFunctionчтобы расширить Myclassс 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
Программисты путешествуют по концептуальным вопросам, ответы на которые, как ожидается, объяснят. Создание дампов кода вместо объяснения похоже на копирование кода из IDE на доску: это может показаться знакомым и даже иногда понятным, но это кажется странным ... просто странным. У доски нет компилятора
gnat