Samo-wykonująca się funkcja anonimowa a prototyp

26

W Javascript istnieje kilka wyraźnie widocznych technik tworzenia i zarządzania klasami / przestrzeniami nazw w javascript.

Jestem ciekawy, jakie sytuacje uzasadniają użycie jednej techniki w porównaniu do drugiej. Chcę wybrać jeden i trzymać się go z przodu.

Piszę kod korporacyjny, który jest utrzymywany i współdzielony przez wiele zespołów, i chcę wiedzieć, jaka jest najlepsza praktyka podczas pisania możliwego do utrzymania javascript?

Wolę samoczynne wykonywanie anonimowych funkcji, ale jestem ciekawy, co głosuje społeczność w sprawie tych technik.

Prototyp:

function obj()
{
}

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

Samozamykająca się funkcja anonimowa:

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

Uważam, że powinienem wspomnieć, że Self-Executing Anonymous Function to wzorzec używany przez zespół jQuery.

Aktualizacja Kiedy zadałem to pytanie, naprawdę nie dostrzegłem znaczenia tego, co próbowałem zrozumieć. Prawdziwym problemem jest to, czy użyć nowego do tworzenia instancji swoich obiektów, czy też używać wzorców, które nie wymagają konstruktorów / użycia newsłowa kluczowego.

Dodałem własną odpowiedź, ponieważ moim zdaniem powinniśmy korzystać ze wzorów, które nie używają newsłowa kluczowego.

Aby uzyskać więcej informacji, zobacz moją odpowiedź.

Robotsushi
źródło
1
czy możesz podać krótkie przykłady dwóch technik, które opisujesz?
Hej,
Nie lekceważ prototypu z powodu mojej prostej próbki.
Robotsushi,
1
to się nie wykonuje = /
Hej
2
nie widzę nawiasów, które mogłyby zamknąć wyrażenie lub nazwać je ...
Hej,
1
(+1) Przestrzenie nazw są pomijane przez wielu programistów.
umlcat,

Odpowiedzi:

22

Samoczynne anonimowe funkcje służą do automatyzacji wykonywania skryptów bez przechwytywania zdarzeń zewnętrznych (np. Window.onload).

W tym przykładzie jest on używany do utworzenia klasycznego wzorca modułu, którego głównym celem jest wprowadzenie przestrzeni nazw do środowiska globalnego i zapewnienie enkapsulacji dla wszystkich właściwości wewnętrznych, które nie są „eksportowane” lub dołączane do przestrzeni nazw.

Z drugiej strony modyfikacja prototypu obiektów służy do ustanowienia dziedziczenia (lub rozszerzenia tubylców). Ten wzór służy do tworzenia obiektów 1: n o wspólnych metodach lub właściwościach.

Nie powinieneś wybierać jednego wzorca zamiast drugiego, ponieważ wykonują one różne zadania. Jeśli chodzi o przestrzeń nazw, właściwym wyborem jest funkcja samowykonywania.

sunwukung
źródło
7
Zauważ, że „samoczynnie wykonujące się funkcje anonimowe” są powszechnie znane jako wyrażenia funkcji natychmiast wywołane (IIFE) .
voithos
Unikam ich i szczerze mówiąc, nie czerpię miłości z IIFE. Są nieporządne przy debugowaniu i łamaniu konturu kodu w środowisku Eclipse. Jeśli potrzebujesz przestrzeni nazw, przyklej ją do obiektu, jeśli chcesz ją wykonać, po prostu ją wywołaj, a enkapsulacja tak naprawdę nic mi nie da.
Daniel Sokołowski
4

Oto wzór, który właśnie zacząłem używać (do wczoraj stosowałem jego odmiany):

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

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

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

Kilka przemyśleń na ten temat:

  1. Nie powinieneś otrzymywać żadnych (anonymous)śladów w śladzie stosu debuggera, ponieważ wszystko jest nazwane (bez anonimowych funkcji).
  2. To najczystszy wzór, jaki widziałem
  3. Możesz z łatwością pogrupować ujawniony interfejs API bez powiązania jego implementacji z deklaracją (co oznacza, że ​​ktoś może łatwo wywołać interfejs klasy publicznej bez konieczności przewijania)

Jedynym czasem, którego prototypeużyłbym naprawdę, jest zdefiniowanie dziedziczenia.

Demian Brecht
źródło
5
Jest z tym kilka problemów. Nowy obiekt funkcji jest tworzony dla każdej „metody” przy każdym wywołaniu konstruktora. Również wywoływanie konstruktora w celu uzyskania kopii obiektu prototypowego do dziedziczenia jest niezadowolone. Użyj Object.create (ParentClass.prototype) lub podkładki dla Object.create, takiej jakfunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hej
@GGG: Tak, masz rację z pierwszym punktem. Powiedziałbym (i powinienem wspomnieć w moim poście), że każdy konkretny przypadek zastosowania implementacji powinien zostać przemyślany. Mój problem z prototypemetodą, którą sugerujesz, to (chyba że istnieje metoda, której nie znam, co może się zdarzyć), że tracisz zdolność do enkapsulacji atrybutów, co oznacza, że ​​wszystko jest jawne (co nie jest końcem świata , tylko osobiste preferencje).
Demian Brecht
Ponadto, po obejrzeniu następującej pracy wykonanej na jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ), wziąłbym wzrost wydajności ponad koszt pamięci dodatkowych kopii prawie każdego dnia (ponownie, bardzo subiektywne w stosunku do konkretnego przypadku użycia).
Demian Brecht
Teraz, powiedziawszy to wszystko, jestem w połowie drogi przez ECMA-262, więc może być kilka rzeczy, których nie widzę. Poza tym nie traktuję słowa Crockforda jako ewangelii. Tak, on jest jednym ekspertów w tej dziedzinie (oczywiście jednego z najlepszych), ale nie zawsze sprawia, że ​​ma on 100% racji. Są inni eksperci (ja nie jestem jednym z nich;)), którzy mają sprzeczne opinie z przekonującymi argumentami.
Demian Brecht
2
możesz być zainteresowany tym, nad czym pracuję .
Cześć,
3

Używam prototypów, ponieważ są one czystsze i zgodne ze standardowymi wzorcami dziedziczenia. Funkcje samo-wywołujące są idealne do tworzenia przeglądarki lub sytuacji, w której nie wiesz, gdzie jest wykonywany kod, ale w przeciwnym razie jest to tylko szum.

Przykład:

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());
Josh K.
źródło
1
Samoczynnie wykonujące się anonimowe funkcje są bardzo przydatne, ponieważ umożliwiają dostęp do prywatnych funkcji dla klasy.
Zee
1

Wybrałbym funkcję samowykonywania, ale z niewielką różnicą:

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

Jeśli uważam, że to podejście jest znacznie bardziej przejrzyste, ponieważ oddzielam zwracaną zmienną globalną od jakichkolwiek parametrów wejściowych (takich jak jQuery) (sposób, w jaki ją napisałeś, jest równoważny zwracaniu void i użyciu parametru ref w C #, co uważam za nieco wyłączone, lub przekazywanie wskaźnika do wskaźnika i ponowne przypisywanie go w C ++). Gdybym wtedy miał dołączyć dodatkowe metody lub właściwości do klasy, użyłbym dziedziczenia prototypowego (na przykład metodą $ .extend jQuery'ego, ale łatwo jest stworzyć własne rozszerzenie ()):

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"

W ten sposób masz wyraźne rozróżnienie między dodanymi metodami a metodami oryginalnymi.

Ed James
źródło
1
Czy jestem jedynym, który uważa, że ​​używanie NFE w ten sposób jest złym pomysłem?
Hej,
1

Przykład na żywo

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

Za pomocą IIFE można emulować „zakres modułu” wokół kodu. Następnie możesz po prostu używać obiektów tak jak zwykle.

Nie „emuluj” stanu prywatnego za pomocą zamknięć, ponieważ ma to dużą karę pamięci.

Jeśli piszesz aplikację korporacyjną i chcesz utrzymać zużycie pamięci poniżej 1 GB, unikaj niepotrzebnego używania zamknięć do przechowywania stanu.

Raynos
źródło
Masz kilka literówek w kodzie kodu. Nie jestem przekonany co do twojego twierdzenia, że ​​zamknięcia automatycznie powodują nadmierne zużycie pamięci, myślę, że zależy to od tego, jak intensywnie je wykorzystujesz i od tego, jak dobrze radzisz sobie z problemami z zakresu (mieszanie rzeczy z wnętrza zamknięcia z rzeczami z zewnątrz jest ogólnie źle, na przykład (użycie globalnej konsoli jest tego dobrym przykładem, powyższy kod byłby znacznie bardziej wydajny, gdybyś przekazał konsolę jako zmienną).
Ed James
@EdWoodcock Doszedłem do wniosku, że kod jest błędny, po prostu przebudowałem go i naprawiłem.
Raynos
@EdWoodcock różnica wydajności między lokalną consolea globalną consolestanowi mikrooptymalizację. Warto to zrobić, aby zminimalizować, consoleale to inna sprawa
Raynos
Zamknięcia @EdWoodcock są powolne, jeśli duplikujesz w nich obiekty. Powolne jest tworzenie funkcji wewnątrz funkcji, gdy nie są potrzebne. zamknięcia mają również niewielki narzut pamięci do przechowywania stanu w porównaniu do przechowywania stanu na obiektach bezpośrednio
Raynos
Tak, chciałem tylko to podkreślić, ponieważ twoja odpowiedź jest rozsądnym sposobem postępowania (choć nie w taki sposób, w jaki zdecydowałbym się użyć). (Dokonałbym edycji, ale nadal nie jestem pewien co do etykiety tych na tej konkretnej stronie SE).
Ed James
0

Aktualizacja Mam teraz znacznie lepsze zrozumienie javascript i uważam, że mogę właściwie odpowiedzieć na to pytanie. Myślę, że był to źle sformułowany, ale bardzo ważny temat javascript.

Wzorzec samowystarczalnej funkcji anonimowej nie jest tym, który wymaga użycia nowego słowa kluczowego, jeśli unikniesz użycia tego poza funkcjami. Zgadzam się z pomysłem, że używanie nowego jest starą techniką i powinniśmy raczej starać się stosować wzorce, które unikają użycia nowego.

Samoczynnie wykonująca się funkcja anonimowa spełnia te kryteria.

Odpowiedź na to pytanie jest subiektywna, ponieważ w javascript jest tak wiele stylów kodowania. Jednak na podstawie moich badań i doświadczenia zaleciłbym wybranie użycia funkcji anonimowego wykonywania w celu zdefiniowania interfejsów API i unikania używania nowych, gdy tylko jest to możliwe.

Robotsushi
źródło
1
Co jest złego w nowym słowie kluczowym? Jestem ciekawy.
Ally
0

Oto jak bym go o to IIFE SelfExecutingFunction, aby przedłużyć Myclassz 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"
jedna linia
źródło
1
Programiści są w trasie koncepcyjne pytania Oczekuje się, że odpowiedzi wyjaśnią różne rzeczy. Rzucanie zrzutów kodu zamiast objaśnień przypomina kopiowanie kodu z IDE na tablicę: może wyglądać znajomo, a czasem nawet być zrozumiałe, ale wydaje się dziwne ... po prostu dziwne. Tablica nie ma kompilatora
gnat