Jak „poprawnie” utworzyć niestandardowy obiekt w JavaScript?


471

Zastanawiam się, jak najlepiej utworzyć obiekt JavaScript, który ma właściwości i metody.

Widziałem przykłady, w których osoba korzystała, var self = thisa następnie używaself. we wszystkich funkcjach, aby upewnić się, że zakres jest zawsze poprawny.

Potem widziałem przykłady użycia .prototype dodawania właściwości, podczas gdy inni robią to bezpośrednio.

Czy ktoś może mi podać właściwy przykład obiektu JavaScript z pewnymi właściwościami i metodami?


13
Nie ma „najlepszego” sposobu.
Tryptyk

To nie selfjest zastrzeżone słowo? Jeśli nie, powinno być; ponieważ selfjest predefiniowaną zmienną odnoszącą się do bieżącego okna. self === window
Shaz

2
@Shaz: nie jest to słowo zastrzeżone, windowpodobnie jak inne właściwości w Object Object w przeglądarce, takie jak documentlub frames; z pewnością możesz ponownie użyć identyfikatora jako nazwy zmiennej. Chociaż tak, stylistycznie wolę var that= thisunikać wszelkich możliwych nieporozumień. Chociaż window.selfjest to ostatecznie bezcelowe, więc rzadko ma powód, aby go dotykać.
bobince

7
Po zminimalizowaniu JS przypisanie thisdo zmiennej lokalnej (np. self) Zmniejsza rozmiary plików.
Patrick Fisher

Nowy link Classjs
Nikola

Odpowiedzi:


889

Istnieją dwa modele implementacji klas i instancji w JavaScript: sposób prototypowania i sposób zamykania. Oba mają zalety i wady, i istnieje wiele rozszerzonych odmian. Wielu programistów i bibliotek ma różne podejścia i funkcje użytkowe do zarządzania klasami, aby opisywać niektóre bardziej brzydsze części języka.

W rezultacie w mieszanym towarzystwie będziesz mieć mieszankę metaklas, wszystkie zachowują się nieco inaczej. Co gorsza, większość materiałów instruktażowych dotyczących języka JavaScript jest okropna i służy pośredniemu kompromisowi obejmującemu wszystkie bazy, pozostawiając cię bardzo zdezorientowanym. (Prawdopodobnie autor jest również zdezorientowany. Model obiektowy JavaScript różni się bardzo od większości języków programowania, aw wielu miejscach jest źle zaprojektowany).

Zacznijmy od prototypowego sposobu . Jest to najbardziej natywny skrypt JavaScript, jaki można uzyskać: jest minimalny narzut kodu i instanceof będzie działał z instancjami tego rodzaju obiektu.

function Shape(x, y) {
    this.x= x;
    this.y= y;
}

Możemy dodać metody do utworzonej instancji new Shape, pisząc je do prototypewyszukiwania tej funkcji konstruktora:

Shape.prototype.toString= function() {
    return 'Shape at '+this.x+', '+this.y;
};

Teraz, aby podklasować, tyle, ile można nazwać JavaScript. Robimy to, całkowicie zastępując tę ​​dziwną magiczną prototypewłaściwość:

function Circle(x, y, r) {
    Shape.call(this, x, y); // invoke the base class's constructor function to take co-ords
    this.r= r;
}
Circle.prototype= new Shape();

przed dodaniem do niego metod:

Circle.prototype.toString= function() {
    return 'Circular '+Shape.prototype.toString.call(this)+' with radius '+this.r;
}

Ten przykład zadziała i zobaczysz podobny kod w wielu samouczkach. Ale stary, to new Shape()jest brzydkie: tworzymy instancję klasy podstawowej, nawet jeśli nie ma zostać utworzony żaden prawdziwy Kształt. Zdarza się pracy w tym prostym przypadku, ponieważ JavaScript jest tak niechlujny: pozwala zerowe argumenty mają być przekazywane w, w tym przypadku xi ystać undefinedi są przypisane do prototypu this.xithis.y . Gdyby funkcja konstruktora robiła coś bardziej skomplikowanego, padłaby płasko na twarz.

Musimy więc znaleźć sposób na stworzenie prototypowego obiektu zawierającego metody i inne elementy, które chcemy na poziomie klasy, bez wywoływania funkcji konstruktora klasy podstawowej. Aby to zrobić, musimy zacząć pisać kod pomocniczy. To najprostsze podejście, jakie znam:

function subclassOf(base) {
    _subclassOf.prototype= base.prototype;
    return new _subclassOf();
}
function _subclassOf() {};

To przenosi członków klasy podstawowej w prototypie do nowej funkcji konstruktora, która nic nie robi, a następnie używa tego konstruktora. Teraz możemy napisać po prostu:

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.prototype= subclassOf(Shape);

zamiast tego new Shape() zła. Mamy teraz akceptowalny zestaw prymitywów do zbudowanych klas.

Istnieje kilka udoskonaleń i rozszerzeń, które możemy rozważyć w ramach tego modelu. Na przykład tutaj jest wersja z cukrem syntaktycznym:

Function.prototype.subclass= function(base) {
    var c= Function.prototype.subclass.nonconstructor;
    c.prototype= base.prototype;
    this.prototype= new c();
};
Function.prototype.subclass.nonconstructor= function() {};

...

function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r= r;
}
Circle.subclass(Shape);

Każda wersja ma tę wadę, że nie można odziedziczyć funkcji konstruktora, tak jak ma to miejsce w wielu językach. Więc nawet jeśli twoja podklasa nie wnosi nic do procesu budowy, musisz pamiętać o wywołaniu konstruktora podstawowego z dowolnymi argumentami, których żądała baza. Można to nieco zautomatyzować za pomocą apply, ale nadal musisz napisać:

function Point() {
    Shape.apply(this, arguments);
}
Point.subclass(Shape);

Dlatego powszechnym rozszerzeniem jest rozbicie elementów inicjalizacyjnych na ich własną funkcję, a nie na sam konstruktor. Ta funkcja może następnie dziedziczyć po podstawie:

function Shape() { this._init.apply(this, arguments); }
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

function Point() { this._init.apply(this, arguments); }
Point.subclass(Shape);
// no need to write new initialiser for Point!

Teraz mamy po prostu ten sam konstruktor funkcji konstruktora dla każdej klasy. Być może możemy przenieść tę funkcję do własnej funkcji pomocnika, abyśmy nie musieli jej pisać, na przykład zamiast Function.prototype.subclassobracać ją i pozwalać funkcji klasy podstawowej wyrzucać podklasy:

Function.prototype.makeSubclass= function() {
    function Class() {
        if ('_init' in this)
            this._init.apply(this, arguments);
    }
    Function.prototype.makeSubclass.nonconstructor.prototype= this.prototype;
    Class.prototype= new Function.prototype.makeSubclass.nonconstructor();
    return Class;
};
Function.prototype.makeSubclass.nonconstructor= function() {};

...

Shape= Object.makeSubclass();
Shape.prototype._init= function(x, y) {
    this.x= x;
    this.y= y;
};

Point= Shape.makeSubclass();

Circle= Shape.makeSubclass();
Circle.prototype._init= function(x, y, r) {
    Shape.prototype._init.call(this, x, y);
    this.r= r;
};

... który zaczyna wyglądać trochę bardziej jak inne języki, choć z nieco nieporadną składnią. Jeśli chcesz, możesz dodać kilka dodatkowych funkcji. Może chcesz makeSubclasswziąć i zapamiętać nazwę klasy i podać jej domyślną nazwę toString. Być może chcesz, aby konstruktor wykrył przypadkowe wywołanie bez newoperatora (co w innym przypadku często powodowałoby bardzo irytujące debugowanie):

Function.prototype.makeSubclass= function() {
    function Class() {
        if (!(this instanceof Class))
            throw('Constructor called without "new"');
        ...

Być może chcesz przekazać wszystkich nowych członków i makeSubclassdodać ich do prototypu, aby zaoszczędzić Ci Class.prototype...dużo pisania . Robi to wiele systemów klasowych, np .:

Circle= Shape.makeSubclass({
    _init: function(x, y, z) {
        Shape.prototype._init.call(this, x, y);
        this.r= r;
    },
    ...
});

Istnieje wiele potencjalnych funkcji, które możesz uznać za pożądane w systemie obiektowym i nikt tak naprawdę nie zgadza się na jedną konkretną formułę.


Zatem sposób zamknięcia . Pozwala to uniknąć problemów związanych z dziedziczeniem opartym na prototypach JavaScript, ponieważ w ogóle nie używa się dziedziczenia. Zamiast:

function Shape(x, y) {
    var that= this;

    this.x= x;
    this.y= y;

    this.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };
}

function Circle(x, y, r) {
    var that= this;

    Shape.call(this, x, y);
    this.r= r;

    var _baseToString= this.toString;
    this.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+that.r;
    };
};

var mycircle= new Circle();

Teraz każda pojedyncza instancja Shapebędzie miała własną kopię plikutoString metody (i dowolne inne metody lub inne elementy klasy, które dodamy).

Złą rzeczą w tym, że każda instancja ma własną kopię każdego członka klasy jest to, że jest mniej wydajna. Jeśli masz do czynienia z dużą liczbą podklasowanych instancji, dziedziczenie prototypowe może ci lepiej służyć. Również wywoływanie metody klasy podstawowej jest nieco denerwujące, jak widać: musimy pamiętać, jaka była metoda, zanim konstruktor podklasy ją nadpisał, w przeciwnym razie się zgubi.

[Również dlatego, że nie ma tutaj dziedziczenia, instanceofoperator nie będzie działał; gdybyś tego potrzebował, musisz stworzyć własny mechanizm wąchania klas. Podczas gdy ty mógł bawić obiekty prototyp w podobny sposób jak z prototypowego dziedziczenia, jest to trochę skomplikowane i naprawdę nie warto go tak aby uzyskaćinstanceof pracę.]

Zaletą każdej instancji mającej własną metodę jest to, że metoda może być następnie powiązana z konkretną instancją, która jest jej właścicielem. Jest to przydatne ze względu na dziwny sposób wiązania JavaScript thisw wywołaniach metod, który ma efekt, że jeśli odłączysz metodę od jej właściciela:

var ts= mycircle.toString;
alert(ts());

wtedy thisw metodzie nie będzie instancji Circle zgodnie z oczekiwaniami (w rzeczywistości będzie to windowobiekt globalny , powodujący powszechne problemy z debugowaniem). W rzeczywistości zwykle dzieje się tak, gdy metoda jest pobierana i przypisywana do setTimeout, onclicklubEventListener w ogóle.

Prototypowym sposobem jest dołączenie zamknięcia dla każdego takiego zadania:

setTimeout(function() {
    mycircle.move(1, 1);
}, 1000);

lub w przyszłości (lub teraz, jeśli włamiesz się do Function.prototype), możesz to również zrobić za pomocą function.bind():

setTimeout(mycircle.move.bind(mycircle, 1, 1), 1000);

jeśli twoje instancje są wykonywane w sposób zamykający, wiązanie odbywa się za darmo przez zamknięcie nad zmienną instancji (zwykle wywoływaną thatlub self, choć osobiście odradzam tę ostatnią, ponieważ selfma ona już inne, inne znaczenie w JavaScript). Nie otrzymujesz argumentów 1, 1z powyższego fragmentu za darmo, więc nadal będziesz potrzebować kolejnego zamknięcia lubbind() jeśli musisz to zrobić.

Istnieje również wiele wariantów metody zamknięcia. Możesz thiscałkowicie pominąć , tworząc nowy thati zwracając go zamiast newoperatora:

function Shape(x, y) {
    var that= {};

    that.x= x;
    that.y= y;

    that.toString= function() {
        return 'Shape at '+that.x+', '+that.y;
    };

    return that;
}

function Circle(x, y, r) {
    var that= Shape(x, y);

    that.r= r;

    var _baseToString= that.toString;
    that.toString= function() {
        return 'Circular '+_baseToString(that)+' with radius '+r;
    };

    return that;
};

var mycircle= Circle(); // you can include `new` if you want but it won't do anything

Który sposób jest „właściwy”? Obie. Który jest „najlepszy”? To zależy od twojej sytuacji. FWIW Mam tendencję do tworzenia prototypów w celu rzeczywistego dziedziczenia JavaScript, gdy robię rzeczy zdecydowanie OO, i zamykania dla prostych efektów stronicowania.

Oba sposoby są jednak przeciwne do intuicji większości programistów. Oba mają wiele potencjalnych niechlujnych odmian. Poznasz oba (jak również wiele schematów pośrednich i ogólnie zepsutych), jeśli będziesz używać kodu / bibliotek innych osób. Nie ma jednej ogólnie akceptowanej odpowiedzi. Witamy w cudownym świecie obiektów JavaScript.

[Jest to część 94 Dlaczego JavaScript nie jest moim ulubionym językiem programowania.]


13
Bardzo przyjemne stopniowe przechodzenie od „klasy” def do tworzenia instancji obiektów. I miły akcent na obejściu new.
Crescent Fresh

8
Wygląda na to, że JavaScript nie jest twoim ulubionym językiem, ponieważ chcesz go używać tak, jakby zawierał klasy.
Jonathan Feinberg

59
Oczywiście, że tak, podobnie jak wszyscy: model klasy i instancji jest bardziej naturalny dla większości typowych problemów, przed którymi stają dziś programiści. Zgadzam się, że teoretycznie dziedziczenie oparte na prototypach może potencjalnie oferować bardziej elastyczny sposób działania, ale JavaScript całkowicie nie spełnia tej obietnicy. Jej niezgrabny system funkcji konstruktora daje nam najgorsze z obu światów, utrudniając dziedziczenie klasowe, a jednocześnie nie zapewnia prototypów elastyczności i prostoty. Krótko mówiąc, to kupa.
bobince

4
Bob, myślę, że to niesamowita odpowiedź - od dłuższego czasu zmagam się z tymi dwoma wzorami i myślę, że kodowałeś coś bardziej zwięźle niż Resig i wyjaśniłeś z większym wglądem niż Crockford. Nie mogę się pochwalić wyższą pochwałą ...
James Westgate

4
Zawsze wydawało mi się, że grafowanie klasycznych paradygmatów dziedziczenia na prototypowych językach, takich jak javascript, to kwadratowy kołek i okrągły otwór. Czy zdarza się, że jest to naprawdę konieczne, czy jest to tylko sposób, w jaki ludzie mogą wcisnąć język tak, jak chcą, zamiast po prostu używać języka do tego, czym jest?
slf 24.10.11

90

Używam tego wzoru dość często - przekonałem się, że daje mi to dość dużą elastyczność, kiedy go potrzebuję. W użyciu jest raczej podobny do klas w stylu Java.

var Foo = function()
{

    var privateStaticMethod = function() {};
    var privateStaticVariable = "foo";

    var constructor = function Foo(foo, bar)
    {
        var privateMethod = function() {};
        this.publicMethod = function() {};
    };

    constructor.publicStaticMethod = function() {};

    return constructor;
}();

Używa anonimowej funkcji, która jest wywoływana podczas tworzenia, zwracając nową funkcję konstruktora. Ponieważ funkcja anonimowa jest wywoływana tylko raz, możesz tworzyć w niej prywatne zmienne statyczne (są one wewnątrz zamknięcia, widoczne dla innych członków klasy). Funkcja konstruktora jest w zasadzie standardowym obiektem Javascript - definiujesz w nim atrybuty prywatne, a atrybuty publiczne są dołączane do thiszmiennej.

Zasadniczo takie podejście łączy podejście Crockforda ze standardowymi obiektami Javascript, aby stworzyć mocniejszą klasę.

Możesz go używać tak jak każdego innego obiektu JavaScript:

Foo.publicStaticMethod(); //calling a static method
var test = new Foo();     //instantiation
test.publicMethod();      //calling a method

4
To wygląda interesująco, ponieważ jest raczej zbliżone do mojej „domowej murawy”, którą jest C #. Myślę też, że zaczynam rozumieć, dlaczego privateStaticVariable jest naprawdę prywatny (ponieważ jest zdefiniowany w zakresie funkcji i utrzymywany przy życiu, dopóki istnieją odniesienia do niego?)
Michael Stum

Ponieważ nie jest używany, thisczy nadal trzeba go utworzyć new?
Jordan Parmer,

W rzeczywistości przyzwyczaja się w funkcji, która this pojawiaconstructor się Foow przykładzie.
ShZ

4
Problem polega na tym, że każdy obiekt otrzymuje własną kopię wszystkich funkcji prywatnych i publicznych.
virtualnobi

2
@virtualnobi: Wzór ten nie uniemożliwia pisanie metod protytpe: constructor.prototype.myMethod = function () { ... }.
Nicolas Le Thierry d'Ennequin

25

Douglas Crockford obszernie omawia ten temat w The Good Parts . Zaleca, aby unikać tworzenia nowego obiektu przez nowego operatora. Zamiast tego proponuje stworzenie niestandardowych konstruktorów. Na przykład:

var mammal = function (spec) {     
   var that = {}; 
   that.get_name = function (  ) { 
      return spec.name; 
   }; 
   that.says = function (  ) { 
      return spec.saying || ''; 
   }; 
   return that; 
}; 

var myMammal = mammal({name: 'Herb'});

W JavaScript funkcja jest obiektem i może być używana do konstruowania obiektów razem z nowym operatorem. Zgodnie z konwencją funkcje przeznaczone do użycia jako konstruktory zaczynają się od dużej litery. Często widzisz takie rzeczy jak:

function Person() {
   this.name = "John";
   return this;
}

var person = new Person();
alert("name: " + person.name);**

W przypadku pominięcia użyć nowego operatora podczas uruchamianiu tego nowego obiektu, co masz jest zwykłym wywołanie funkcji, a to wiąże się z globalnego obiektu zamiast do nowego obiektu.


5
Czy to ja, czy też myślę, że Crockford nie ma absolutnie żadnego sensu z powodu krytykowania nowego operatora?
meder omuraliev

3
@meder: Nie tylko ty. Przynajmniej myślę, że z nowym operatorem nie ma nic złego. I neww var that = {};każdym razie jest to ukryte .
Tim Down,

17
Crockford jest zepsutym staruszkiem i często się z nim nie zgadzam, ale przynajmniej promuje krytyczne spojrzenie na JavaScript i warto posłuchać, co ma do powiedzenia.
bobince

2
@bince: Zgoda. Jego pisanie o zamknięciach otworzyło mi oczy na wiele rzeczy około 5 lat temu i zachęca do przemyślanego podejścia.
Tim Down

20
Zgadzam się z Crockford. Problem z nowym operatorem polega na tym, że JavaScript sprawi, że kontekst „tego” będzie zupełnie inny niż w przypadku wywołania funkcji w inny sposób. Pomimo odpowiedniej konwencji wielkości liter, pojawiają się problemy z większymi bazami kodu, ponieważ programiści zapominają używać nowego, nie używają wielkich liter itp. Aby być pragmatycznym, możesz zrobić wszystko, co musisz zrobić bez nowego słowa kluczowego - więc po co go używać i wprowadzić więcej punktów awarii w kodzie? JS to prototypowy, nie oparty na klasach język. Dlaczego więc chcemy, aby zachowywał się jak język statycznie pisany? Na pewno nie.
Joshua Ramirez

13

Aby kontynuować od odpowiedzi Bobina

W es6 możesz teraz stworzyć class

Teraz możesz zrobić:

class Shape {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return `Shape at ${this.x}, ${this.y}`;
    }
}

Tak więc rozciągnij się do koła (jak w drugiej odpowiedzi), możesz:

class Circle extends Shape {
    constructor(x, y, r) {
        super(x, y);
        this.r = r;
    }

    toString() {
        let shapeString = super.toString();
        return `Circular ${shapeString} with radius ${this.r}`;
    }
}

W ES6 jest trochę czystszy i trochę łatwiejszy do odczytania.


Oto dobry przykład tego w akcji:


6

Możesz to również zrobić w ten sposób, używając struktur:

function createCounter () {
    var count = 0;

    return {
        increaseBy: function(nb) {
            count += nb;
        },
        reset: function {
            count = 0;
        }
    }
}

Następnie :

var counter1 = createCounter();
counter1.increaseBy(4);

6
Nie podoba mi się to, ponieważ białe znaki są ważne. Nawiasy po zwrocie muszą znajdować się w tym samym wierszu, aby zapewnić zgodność z różnymi przeglądarkami.
geowa4

5

Innym sposobem może być http://jsfiddle.net/nnUY4/ (nie wiem, czy ten rodzaj obsługi obiektów tworzących i ujawniających funkcje jest zgodny z jakimś określonym wzorcem)

// Build-Reveal

var person={
create:function(_name){ // 'constructor'
                        //  prevents direct instantiation 
                        //  but no inheritance
    return (function() {

        var name=_name||"defaultname";  // private variable

        // [some private functions]

        function getName(){
            return name;
        }

        function setName(_name){
            name=_name;
        }

        return {    // revealed functions
            getName:getName,    
            setName:setName
        }
    })();
   }
  }

  // … no (instantiated) person so far …

  var p=person.create(); // name will be set to 'defaultname'
  p.setName("adam");        // and overwritten
  var p2=person.create("eva"); // or provide 'constructor parameters'
  alert(p.getName()+":"+p2.getName()); // alerts "adam:eva"

4

Kiedy używa się sztuczki zamykania „tego” podczas wywoływania konstruktora, ma to na celu napisanie funkcji, która może być wykorzystana jako wywołanie zwrotne przez inny obiekt, który nie chce wywoływać metody na obiekcie. Nie ma to związku z „poprawieniem zakresu”.

Oto waniliowy obiekt JavaScript:

function MyThing(aParam) {
    var myPrivateVariable = "squizzitch";

    this.someProperty = aParam;
    this.useMeAsACallback = function() {
        console.log("Look, I have access to " + myPrivateVariable + "!");
    }
}

// Every MyThing will get this method for free:
MyThing.prototype.someMethod = function() {
    console.log(this.someProperty);
};

Możesz wiele wyczytać z tego, co Douglas Crockford ma do powiedzenia na temat JavaScript. John Resig jest również genialny. Powodzenia!


1
Uh, zamykanie się thisma wszystko wspólnego z „poprawieniem zakresu”.
Roatin Marth

3
Jonathan ma rację. Zakres funkcji js jest taki, jak ją zaprojektujesz. Self = ta sztuczka jest jednym ze sposobów na powiązanie jej z konkretną instancją, aby nie uległa zmianie, gdy zostanie wywołana w innym kontekście. Ale czasami tego właśnie chcesz. Zależy od kontekstu.
Marco,

Myślę, że wszyscy mówicie to samo. self=thischociaż nie wymusza thistrwałości, łatwo umożliwia „prawidłowe” ustalanie zakresu przez zamknięcie.
Crescent Fresh

2
Powodem, dla którego to robisz = jest zapewnienie zagnieżdżonym funkcjom dostępu do zakresu tego, co istnieje w funkcji konstruktora. Gdy funkcje zagnieżdżone znajdują się wewnątrz funkcji konstruktora, ich zakres „ten” powraca do zakresu globalnego.
Joshua Ramirez

4

Closurejest wszechstronny. bobince dobrze podsumował podejście prototypowe do zamknięcia podczas tworzenia obiektów. Można jednak naśladować niektóre aspekty OOPużywania zamknięcia w funkcjonalny sposób programowania. Pamiętaj, że funkcje są obiektami w JavaScript ; więc użyj funkcji jako obiektu w inny sposób.

Oto przykład zamknięcia:

function outer(outerArg) {
    return inner(innerArg) {
        return innerArg + outerArg; //the scope chain is composed of innerArg and outerArg from the outer context 
    }
}

Jakiś czas temu natknąłem się na artykuł Mozilli na temat Zamknięcia. Oto, co rzuca mi się w oczy: „Zamknięcie pozwala powiązać niektóre dane (środowisko) z funkcją działającą na tych danych. Ma to oczywiste podobieństwo do programowania obiektowego, w którym obiekty pozwalają nam powiązać niektóre dane (właściwości obiektu ) z jedną lub więcej metodami ”. To był pierwszy raz, kiedy czytałem paralelizm między zamknięciem a klasycznym OOP bez odniesienia do prototypu.

W jaki sposób?

Załóżmy, że chcesz obliczyć podatek VAT od niektórych produktów. Podatek VAT prawdopodobnie pozostanie stabilny przez cały okres składania wniosku. Jednym ze sposobów, aby to zrobić w OOP (pseudo kod):

public class Calculator {
    public property VAT { get; private set; }
    public Calculator(int vat) {
        this.VAT = vat;
    }
    public int Calculate(int price) {
        return price * this.VAT;
    }
}

Zasadniczo przekazujesz wartość VAT swojemu konstruktorowi, a twoja metoda obliczania może działać na podstawie zamknięcia . Teraz zamiast używać klasy / konstruktora, przekaż swój podatek VAT jako argument do funkcji. Ponieważ jedyne, co Cię interesuje, to samo obliczenie, zwraca nową funkcję, którą jest metoda obliczania:

function calculator(vat) {
    return function(item) {
        return item * vat;
    }
}
var calculate = calculator(1.10);
var jsBook = 100; //100$
calculate(jsBook); //110

W swoim projekcie określ wartości najwyższego poziomu, które są dobrymi kandydatami do tego, jaki podatek VAT jest do obliczenia. Zasadniczo za każdym razem, gdy przekazujesz te same argumenty, istnieje sposób na ulepszenie go za pomocą zamknięcia. Nie trzeba tworzyć tradycyjnych obiektów.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures


3

Tworzenie obiektu

Najłatwiejszym sposobem utworzenia obiektu w JavaScript jest użycie następującej składni:

var test = {
  a : 5,
  b : 10,
  f : function(c) {
    return this.a + this.b + c;
  }
}

console.log(test);
console.log(test.f(3));

Działa to doskonale do przechowywania danych w uporządkowany sposób.

W przypadku bardziej złożonych przypadków użycia często lepiej jest jednak utworzyć instancje funkcji:

function Test(a, b) {
  this.a = a;
  this.b = b;
  this.f = function(c) {
return this.a + this.b + c;
  };
}

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Umożliwia to tworzenie wielu obiektów, które mają ten sam „plan”, podobnie jak w przypadku korzystania z klas np. Jawa.

Można to jednak zrobić bardziej efektywnie, używając prototypu.

Ilekroć różne instancje funkcji współużytkują te same metody lub właściwości, możesz przenieść je do prototypu tego obiektu. W ten sposób każde wystąpienie funkcji ma dostęp do tej metody lub właściwości, ale nie musi być duplikowane dla każdego wystąpienia.

W naszym przypadku sensowne jest przeniesienie metody fdo prototypu:

function Test(a, b) {
  this.a = a;
  this.b = b;
}

Test.prototype.f = function(c) {
  return this.a + this.b + c;
};

var test = new Test(5, 10);
console.log(test);
console.log(test.f(3));

Dziedzictwo

Prostym, ale skutecznym sposobem dziedziczenia w JavaScript, jest użycie następującego dwuliniowego:

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

Jest to podobne do robienia tego:

B.prototype = new A();

Główna różnica między nimi polega na tym, że konstruktor Anie jest uruchamiany podczas używania Object.create, co jest bardziej intuicyjne i bardziej podobne do dziedziczenia klasowego.

Zawsze możesz wybrać opcjonalne uruchomienie konstruktora Apodczas tworzenia nowej instancji Bpoprzez dodanie jej do konstruktora B:

function B(arg1, arg2) {
    A(arg1, arg2); // This is optional
}

Jeśli chcesz przekazać wszystkie argumenty Bdo A, możesz również użyć Function.prototype.apply():

function B() {
    A.apply(this, arguments); // This is optional
}

Jeśli chcesz wstawić inny obiekt do łańcucha konstruktorów B, możesz połączyć Object.createz Object.assign:

B.prototype = Object.assign(Object.create(A.prototype), mixin.prototype);
B.prototype.constructor = B;

Próbny

function A(name) {
  this.name = name;
}

A.prototype = Object.create(Object.prototype);
A.prototype.constructor = A;

function B() {
  A.apply(this, arguments);
  this.street = "Downing Street 10";
}

B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;

function mixin() {

}

mixin.prototype = Object.create(Object.prototype);
mixin.prototype.constructor = mixin;

mixin.prototype.getProperties = function() {
  return {
    name: this.name,
    address: this.street,
    year: this.year
  };
};

function C() {
  B.apply(this, arguments);
  this.year = "2018"
}

C.prototype = Object.assign(Object.create(B.prototype), mixin.prototype);
C.prototype.constructor = C;

var instance = new C("Frank");
console.log(instance);
console.log(instance.getProperties());


Uwaga

Object.createmoże być bezpiecznie używany w każdej nowoczesnej przeglądarce, w tym IE9 +. Object.assignnie działa w żadnej wersji IE ani niektórych przeglądarkach mobilnych. Zaleca się do wypełniania Object.create i / lubObject.assign jeśli chcesz ich używać i obsługiwać przeglądarki, które ich nie implementują.

Możesz znaleźć polifill Object.create tutaj i jeden Object.assign tutaj .


0
var Person = function (lastname, age, job){
this.name = name;
this.age = age;
this.job = job;
this.changeName = function(name){
this.lastname = name;
}
}
var myWorker = new Person('Adeola', 23, 'Web Developer');
myWorker.changeName('Timmy');

console.log("New Worker" + myWorker.lastname);

4
Co to dodaje do licznych już obszernych odpowiedzi?
blm

Podoba mi się ta odpowiedź, ponieważ jest zwięzła i pokazuje trzy części implementacji: 1) Zdefiniuj obiekt, 2) Utwórz instancję obiektu, 3) Użyj instancji - pokazuje wszystko na pierwszy rzut oka zamiast analizowania poprzez wszystkie powyższe pełne odpowiedzi (które oczywiście są niezwykle dobrymi odpowiedziami ze wszystkimi istotnymi szczegółami, których byśmy chcieli) - rodzaj prostego podsumowania tutaj
G-Man

0

Oprócz zaakceptowanej odpowiedzi z 2009 roku. Jeśli możesz atakować nowoczesne przeglądarki, możesz skorzystać z Object.defineProperty .

Metoda Object.defineProperty () definiuje nową właściwość bezpośrednio na obiekcie lub modyfikuje istniejącą właściwość na obiekcie i zwraca obiekt. Źródło: Mozilla

var Foo = (function () {
    function Foo() {
        this._bar = false;
    }
    Object.defineProperty(Foo.prototype, "bar", {
        get: function () {
            return this._bar;
        },
        set: function (theBar) {
            this._bar = theBar;
        },
        enumerable: true,
        configurable: true
    });
    Foo.prototype.toTest = function () {
        alert("my value is " + this.bar);
    };
    return Foo;
}());

// test instance
var test = new Foo();
test.bar = true;
test.toTest();

Aby zobaczyć listę kompatybilności z komputerami i urządzeniami mobilnymi, zobacz listę kompatybilności przeglądarki Mozilla . Tak, IE9 + obsługuje to tak samo, jak Safari Mobile.


0

Możesz także spróbować tego

    function Person(obj) {
    'use strict';
    if (typeof obj === "undefined") {
        this.name = "Bob";
        this.age = 32;
        this.company = "Facebook";
    } else {
        this.name = obj.name;
        this.age = obj.age;
        this.company = obj.company;
    }

}

Person.prototype.print = function () {
    'use strict';
    console.log("Name: " + this.name + " Age : " + this.age + " Company : " + this.company);
};

var p1 = new Person({name: "Alex", age: 23, company: "Google"});
p1.print();

0
Wzór, który dobrze mi służy
var Klass = function Klass() {
    var thus = this;
    var somePublicVariable = x
      , somePublicVariable2 = x
      ;
    var somePrivateVariable = x
      , somePrivateVariable2 = x
      ;

    var privateMethod = (function p() {...}).bind(this);

    function publicMethod() {...}

    // export precepts
    this.var1 = somePublicVariable;
    this.method = publicMethod;

    return this;
};

Po pierwsze, można zmienić swoje preferencje dodanie metody do instancji zamiast konstruktora prototypeobiektu . Prawie zawsze deklaruję metody wewnątrz konstruktora, ponieważ używam porwania konstruktora bardzo często do celów związanych z dziedziczeniem i dekoratorami.

Oto jak decyduję, gdzie są zapisywane deklaracje:

  • Nigdy nie deklaruj metody bezpośrednio w obiekcie kontekstu ( this)
  • Niech vardeklaracje mają pierwszeństwofunction deklaracjami
  • Niech prymitywy mają pierwszeństwo przed obiektami ( {}i[] )
  • Niech publicdeklaracje mają pierwszeństwoprivate deklaracjami
  • Wolę Function.prototype.bindna thus, self,vm ,etc
  • Unikaj deklarowania klasy w innej klasie, chyba że:
    • Powinno być oczywiste, że oba są nierozłączne
    • Klasa wewnętrzna implementuje wzorzec poleceń
    • Klasa wewnętrzna implementuje wzorzec Singleton
    • Klasa wewnętrzna implementuje wzorzec stanu
    • Klasa wewnętrzna implementuje kolejny Wzorzec Projektu, który to uzasadnia
  • Zawsze wracaj thisz zakresu leksykalnego obszaru zamknięcia.

Oto dlaczego te pomoce:

Porwanie Konstruktora
var Super = function Super() {
    ...
    this.inherited = true;
    ...
};
var Klass = function Klass() {
    ...
    // export precepts
    Super.apply(this);  // extends this with property `inherited`
    ...
};
Projektowanie modeli
var Model = function Model(options) {
    var options = options || {};

    this.id = options.id || this.id || -1;
    this.string = options.string || this.string || "";
    // ...

    return this;
};
var model = new Model({...});
var updated = Model.call(model, { string: 'modified' });
(model === updated === true);  // > true
Wzorce projektowe
var Singleton = new (function Singleton() {
    var INSTANCE = null;

    return function Klass() {
        ...
        // export precepts
        ...

        if (!INSTANCE) INSTANCE = this;
        return INSTANCE;
    };
})();
var a = new Singleton();
var b = new Singleton();
(a === b === true);  // > true

Jak widać, naprawdę nie ma takiej potrzeby, thusponieważ wolę Function.prototype.bind( .calllub .apply) więcej niżthus . W naszej Singletonklasie nawet nie nazywamy tego, thusponieważ INSTANCEprzekazuje więcej informacji. Wracamy Model, abyśmy thismogli wywołać Konstruktora, .callaby zwrócić instancję, którą do niej przekazaliśmy. Nadmiarowo przypisaliśmy ją do zmiennej updated, chociaż jest przydatna w innych scenariuszach.

Oprócz tego wolę konstruować literały obiektów za pomocą new słowa kluczowego zamiast {nawiasów}:

Preferowane
var klass = new (function Klass(Base) {
    ...
    // export precepts
    Base.apply(this);  //
    this.override = x;
    ...
})(Super);
Nie preferowane
var klass = Super.apply({
    override: x
});

Jak widać, ten ostatni nie ma możliwości przesłonięcia właściwości „przesłonięcia” swojej nadklasy.

Jeśli dodam metody do prototypeobiektu klasy , wolę literał obiektu - z lub bez użycianew słowa kluczowego:

Preferowane
Klass.prototype = new Super();
// OR
Klass.prototype = new (function Base() {
    ...
    // export precepts
    Base.apply(this);
    ...
})(Super);
// OR
Klass.prototype = Super.apply({...});
// OR
Klass.prototype = {
    method: function m() {...}
};
Nie preferowane
Klass.prototype.method = function m() {...};

0

Chciałbym wspomnieć, że możemy użyć tytułu lub ciągu znaków, aby zadeklarować obiekt.
Istnieją różne sposoby wywoływania każdego z nich. Patrz poniżej:

var test = {

  useTitle : "Here we use 'a Title' to declare an Object",
  'useString': "Here we use 'a String' to declare an Object",
  
  onTitle : function() {
    return this.useTitle;
  },
  
  onString : function(type) {
    return this[type];
  }
  
}

console.log(test.onTitle());
console.log(test.onString('useString'));


-1

Zasadniczo w JS nie ma pojęcia klasy, dlatego używamy funkcji jako konstruktora klas, który jest odpowiedni dla istniejących wzorców projektowych.

//Constructor Pattern
function Person(name, age, job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.doSomething = function(){
    alert('I am Happy');
}
}

Do tej pory JS nie ma pojęcia, że ​​chcesz utworzyć obiekt, więc oto nowe słowo kluczowe.

var person1 = new Person('Arv', 30, 'Software');
person1.name //Arv

Ref: Professional JS dla programistów stron internetowych - Nik Z


Akceptacja zwrotna: z uzasadnionego powodu byłaby bardziej informacyjna i byłaby okazją do poprawy.
Airwind711,

Jest to pojęcie classw JS, jak pan wspomniał w swojej pozycji za pomocą functionsłowa kluczowego. To nie jest wzorzec projektowy, ale celowa funkcja języka. Nie głosowałem cię w tej sprawie, ale wygląda na to, że zrobił to ktoś inny z powodu zwięzłości i niemal nieistotności pytania. Mam nadzieję, że ta opinia pomoże.
Cody
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.