Mam wiele pytań dotyczących klas ES6.
Jakie są zalety używania class
składni? Czytałem, że publiczna / prywatna / statyczna będzie częścią ES7, czy to jest powód?
Co więcej, czy jest class
to 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?
javascript
ecmascript-6
ssbb
źródło
źródło
Odpowiedzi:
Nowa
class
składnia to na razie głównie cukier syntaktyczny. (Ale wiesz, dobry rodzaj cukru.) W ES2015-ES2020 nie ma niczegoclass
, czego nie można zrobić z funkcjami konstruktoraReflect.construct
(łącznie z podklasamiError
iArray
¹). (Jest to prawdopodobnie będzie kilka rzeczy w ES2021, że można zrobić zclass
, że nie można zrobić inaczej: prywatne pola , metody prywatne i Pola statyczne / prywatne metody statyczne ).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 Foo
itp.). (Szczególnie w przypadku wywodzenia się zArray
lubError
, czego nie mogłeś zrobić w ES5 i wcześniejszych. Możesz teraz zReflect.construct
[ spec , MDN ], ale nie ze starym stylem ES5.)Tak,
prototype
po 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); };
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.
W skrócie: jeśli w pierwszej kolejności nie używasz funkcji konstruktora, preferowanie
Object.create
lubclass
coś 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.
class
chroni Cię przed typowym błędem polegającym na niepowodzeniu użycianew
funkcji konstruktora (przez wywołanie przez konstruktora wyjątku, jeślithis
nie 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()
zamiastParentConstructor.prototype.method.call(this)
lubObject.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:
Pokaż fragment kodu
// ***ES2015+** class Person { constructor(first, last) { this.first = first; this.last = last; } personMethod() { return `Result from personMethod: this.first = ${this.first}, this.last = ${this.last}`; } } class Employee extends Person { constructor(first, last, position) { super(first, last); this.position = position; } personMethod() { const result = super.personMethod(); return result + `, this.position = ${this.position}`; } employeeMethod() { // ... } } class Manager extends Employee { constructor(first, last, position, department) { super(first, last, position); this.department = department; } personMethod() { const result = super.personMethod(); return result + `, this.department = ${this.department}`; } managerMethod() { // ... } } const m = new Manager("Joe", "Bloggs", "Special Projects Manager", "Covert Ops"); console.log(m.personMethod());
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:
Pokaż fragment kodu
// **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() { return "Result from personMethod: this.first = " + this.first + ", this.last = " + this.last; }; 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.personMethod = function() { var result = Person.prototype.personMethod.call(this); return result + ", this.position = " + this.position; }; 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); return result + ", this.department = " + this.department; }; Manager.prototype.managerMethod = function() { // ... }; var m = new Manager("Joe", "Bloggs", "Special Projects Manager", "Covert Ops"); console.log(m.personMethod());
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
class
nie ma nic, czego nie można zrobić za pomocą funkcji konstruktora iReflect.construct
(w tym podklasyError
iArray
)”Przykład:
Pokaż fragment kodu
// Creating an Error subclass: function MyError(...args) { return Reflect.construct(Error, args, this.constructor); } MyError.prototype = Object.create(Error.prototype); MyError.prototype.constructor = MyError; MyError.prototype.myMethod = function() { console.log(this.message); }; // Example use: function outer() { function inner() { const e = new MyError("foo"); console.log("Callng e.myMethod():"); e.myMethod(); console.log(`e instanceof MyError? ${e instanceof MyError}`); console.log(`e instanceof Error? ${e instanceof Error}`); throw e; } inner(); } outer();
.as-console-wrapper { max-height: 100% !important; }
źródło
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 oclass
uproszczenie dziedziczenia pseudo-klasycznego w JavaScript.Symbol
pomylić). 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 semantykanew
poprawia czytelność; iObject.create
ma sens w wielu innych miejscach, zwł. sytuacje elewacyjne. Są komplementarne. :-)class
skł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ą.)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.getBar
itp.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_NAME
nie 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
extends
pomocnikiem, 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.
źródło