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


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ключевое слово.

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


1
Можете ли вы привести короткие примеры двух техник, которые вы описываете?
— Привет,

Не стоит недооценивать прототип из-за моего простого образца.
— Робоцуши

1
это не выполняется само по себе = /
— Привет

2
я не вижу скобок, чтобы закрыть выражение или назвать его ...
— Привет,

1
(+1) Пространства имен упускаются из виду для многих разработчиков.
— umlcat

Ответы:


22

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

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

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

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


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 ГБ, избегайте ненужного использования замыканий для сохранения состояния.


У вас есть пара опечаток в коде. Я не уверен в том, что вы утверждаете, что замыкания автоматически приводят к чрезмерному использованию памяти, я думаю, это зависит от того, насколько интенсивно вы их используете и насколько хорошо вы справляетесь с областями видимости (как правило, смешивание вещей изнутри замыкания с вещами извне плохо, например (ваше использование глобальной консоли является хорошим примером этого, приведенный выше код будет гораздо более эффективным, если вы передадите консоль как переменную)).
— Эд Джеймс

@EdWoodcock Я подумал, что код содержит ошибки, просто рефакторинг и исправил его.
— Raynos

@ EdWoodcock разница в эффективности между локальной consoleи глобальной consoleявляется микрооптимизацией . Это стоит того, чтобы вы могли свести к минимуму, consoleно это другой вопрос
— Raynos

@EdWoodcock закрывает медленно, если ваши дублирующие объекты в них. Что медленно, так это создание функций внутри функций, когда они не нужны. замыкания также имеют небольшие накладные расходы памяти для хранения состояния по сравнению с хранением состояния на объектах напрямую
— Raynos

Да, просто хотел указать на это, так как ваш ответ - разумный способ действовать (хотя и не так, как я бы выбрал). (Я бы сделал правку, но я все еще не уверен насчет этикета на этом конкретном сайте SE).
— Эд Джеймс

0

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

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

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

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


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.