Jakie korzyści zapewnia składnia klasy ES2015 (ES6)?

87

Mam wiele pytań dotyczących klas ES6.

Jakie są zalety używania classskładni? Czytałem, że publiczna / prywatna / statyczna będzie częścią ES7, czy to jest powód?

Co więcej, czy jest classto inny rodzaj OOP, czy nadal jest to prototypowe dziedziczenie JavaScript? Czy mogę go zmodyfikować za pomocą .prototype? Czy jest to tylko ten sam obiekt, ale dwa różne sposoby jego zadeklarowania.

Czy są korzyści związane z szybkością? Może łatwiej jest utrzymać / zrozumieć, jeśli masz dużą aplikację, taką jak duża aplikacja?

ssbb
źródło
Tylko moja własna opinia: nowsza składnia jest znacznie łatwiejsza do zrozumienia i nie wymagałaby dużego doświadczenia (zwłaszcza, że ​​większość ludzi wywodzi się z języka OOP). Ale prototypowy model obiektowy jest wart poznania i nigdy nie dowiesz się, że używanie nowej składni będzie trudniejsze do pracy nad prototypowym kodem, chyba że już go znasz. Więc zdecydowałbym się napisać cały swój własny kod ze starą składnią, kiedy mam taki wybór
Zach Smith,

Odpowiedzi:

120

Nowa classskładnia to na razie głównie cukier syntaktyczny. (Ale wiesz, dobry rodzaj cukru.) W ES2015-ES2020 nie ma niczego class, czego nie można zrobić z funkcjami konstruktora Reflect.construct(łącznie z podklasami Errori Array¹). (Jest to prawdopodobnie będzie kilka rzeczy w ES2021, że można zrobić z class, że nie można zrobić inaczej: prywatne pola , metody prywatne i Pola statyczne / prywatne metody statyczne ).

Co więcej, czy jest classto inny rodzaj OOP, czy nadal jest to prototypowe dziedziczenie JavaScript?

Jest to to samo prototypowe dziedziczenie, które zawsze mieliśmy, tylko z czystszą i wygodniejszą składnią, jeśli lubisz używać funkcji konstruktorów ( new Fooitp.). (Szczególnie w przypadku wywodzenia się z Arraylub Error, czego nie mogłeś zrobić w ES5 i wcześniejszych. Możesz teraz z Reflect.construct[ spec , MDN ], ale nie ze starym stylem ES5.)

Czy mogę go zmodyfikować za pomocą .prototype?

Tak, prototypepo utworzeniu klasy nadal możesz modyfikować obiekt w konstruktorze klasy. Np. Jest to całkowicie legalne:

class Foo {
    constructor(name) {
        this.name = name;
    }
    
    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

Czy są korzyści związane z szybkością?

Poprzez zapewnienie idiom specyficzne dla tego, Przypuszczam, że to możliwe , że silnik może być w stanie zrobić lepszą optymalizację pracy. Ale już teraz są okropnie dobrzy w optymalizacji, nie spodziewałbym się znaczącej różnicy.

Jakie korzyści classzapewnia składnia ES2015 (ES6) ?

W skrócie: jeśli w pierwszej kolejności nie używasz funkcji konstruktora, preferowanie Object.createlub classcoś podobnego nie jest dla ciebie przydatne.

Jeśli używasz funkcji konstruktora, istnieją pewne korzyści z class:

  • Składnia jest prostsza i mniej podatna na błędy.

  • O wiele łatwiej (i znowu mniej podatne na błędy) jest konfigurowanie hierarchii dziedziczenia przy użyciu nowej składni niż w przypadku starej.

  • classchroni Cię przed typowym błędem polegającym na niepowodzeniu użycia newfunkcji konstruktora (przez wywołanie przez konstruktora wyjątku, jeśli thisnie jest prawidłowym obiektem dla konstruktora).

  • Wywołanie wersji metody prototypu nadrzędnego jest znacznie prostsze w przypadku nowej składni niż starej ( super.method()zamiast ParentConstructor.prototype.method.call(this)lub Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Oto porównanie składni dla hierarchii:

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

Przykład:

vs.

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

Przykład na żywo:

Jak widać, jest tam wiele powtarzających się i rozwlekłych rzeczy, które łatwo się pomylić i nudne do przepisania (dlatego napisałem kiedyś skrypt, który to robi ).


¹ „W ES2015-ES2018 classnie ma nic, czego nie można zrobić za pomocą funkcji konstruktora i Reflect.construct(w tym podklasy Errori Array)”

Przykład:

TJ Crowder
źródło
9
Nie wiem, czy to uczciwe porównanie. Możesz osiągnąć podobny przedmiot bez całego cruft. Używa metody JS do łączenia obiektów za pośrednictwem łańcuchów prototypów, zamiast aproksymowania klasycznego dziedziczenia. Zrobiłem podsumowanie pokazujące prosty przykład oparty na twoim, aby to zrobić.
Jason Cust,
8
@JasonCust: Tak, to uczciwe porównanie, ponieważ pokazuję, jak ES5 robi to, z czym ES6 robi class. JavaScript jest na tyle elastyczny, że nie musisz używać funkcji konstruktora, jeśli nie chcesz, co właśnie robisz, ale to coś innego niż class- chodzi o classuproszczenie dziedziczenia pseudo-klasycznego w JavaScript.
TJ Crowder,
3
@JasonCust: Tak, to inny sposób. Nie nazwałbym tego „bardziej rodzimym” (wiem, że Kyle tak, ale to jest Kyle). Funkcje konstruktorów są fundamentalne dla JavaScript, spójrz na wszystkie typy wbudowane (z wyjątkiem tego, że frakcja antykonstruktorów jakoś zdołała się Symbolpomylić). Ale znowu wspaniałą rzeczą jest to, że jeśli nie chcesz ich używać, nie musisz tego robić. W mojej pracy stwierdzam, że funkcje konstruktorów mają sens w wielu miejscach, a semantyka newpoprawia czytelność; i Object.createma sens w wielu innych miejscach, zwł. sytuacje elewacyjne. Są komplementarne. :-)
TJ Crowder
4
meh. Nie kupuję takiej potrzeby. Odszedłem od C #, aby uciec od oop, a teraz oop skrada się na javascript. Nie lubię typów. wszystko powinno być zmienną / obiektem / funkcją. Otóż ​​to. koniec ze zbędnymi słowami kluczowymi. a dziedziczenie to oszustwo. jeśli chcesz zobaczyć dobrą walkę między typami i nietypami. spójrz na mydło vs reszta. i wszyscy wiemy, kto to wygrał.
foreyez
3
@foreyez: classskładnia nie sprawia, że ​​JavaScript jest bardziej wpisywany niż wcześniej. Jak powiedziałem w odpowiedzi, jest to głównie (znacznie) prostsza składnia dla tego, co już tam było. Istniała absolutna potrzeba, ludzie ciągle mylili starą składnię. Nie oznacza to również, że musisz korzystać z dziedziczenia bardziej niż wcześniej. (I oczywiście, jeśli nigdy nie skonfigurowałeś „klas” ze starą składnią, nie ma też takiej potrzeby, aby robić to z nową składnią.)
TJ Crowder,
22

Klasy ES6 są cukrem syntaktycznym dla prototypowego systemu klas, którego używamy dzisiaj. Sprawiają, że twój kod jest bardziej zwięzły i samodokumentujący, co jest wystarczającym powodem, aby z nich korzystać (moim zdaniem).

Używanie Babel do transpozycji tej klasy ES6:

class Foo {
  constructor(bar) {
    this._bar = bar;
  }

  getBar() {
    return this._bar;
  }
}

da ci coś takiego:

var Foo = (function () {
  function Foo(bar) {    
    this._bar = bar;
  }

  Foo.prototype.getBar = function () {
    return this._bar;
  }

  return Foo;
})();

Druga wersja nie jest dużo bardziej skomplikowana, zawiera więcej kodu do utrzymania. Kiedy zaangażujesz się w dziedziczenie, te wzorce stają się jeszcze bardziej skomplikowane.

Ponieważ klasy kompilują się do tych samych prototypowych wzorców, których używaliśmy, możesz wykonać na nich taką samą manipulację prototypem. Obejmuje to dodawanie metod i tym podobnych w czasie wykonywania, uzyskiwanie dostępu do metod Foo.prototype.getBaritp.

Obecnie ES6 zapewnia podstawowe wsparcie dla prywatności, chociaż opiera się ono na nieeksportowaniu obiektów, których nie chcesz udostępniać. Na przykład możesz:

const BAR_NAME = 'bar';

export default class Foo {
  static get name() {
    return BAR_NAME;
  }
}

i BAR_NAMEnie będą dostępne dla innych modułów do bezpośredniego odniesienia.

Wiele bibliotek próbowało to wesprzeć lub rozwiązać, na przykład Backbone ze swoim extendspomocnikiem, który pobiera niezatwierdzoną wartość skrótu funkcji i właściwości podobnych do metod, ale nie ma systemu polegającego na ujawnianiu prototypowego dziedziczenia, który nie wymaga grzebania w prototypie.

Ponieważ kod JS staje się bardziej skomplikowany, a bazy kodów większe, zaczęliśmy opracowywać wiele wzorców obsługujących takie rzeczy, jak dziedziczenie i moduły. IIFE używany do tworzenia prywatnego zakresu dla modułów ma wiele nawiasów klamrowych i paren; brak jednego z nich może skutkować prawidłowym skryptem, który robi coś zupełnie innego (pomijanie średnika po tym, jak moduł może przekazać do niego następny moduł jako parametr, co rzadko jest dobre).

tl; dr: to cukier w tym, co już robimy, i jasno pokazuje twój zamiar w kodzie.

ssube
źródło