Tenho muitas dúvidas sobre as classes ES6.
Qual é a vantagem de usar class
sintaxe? Eu li que público / privado / estático fará parte do ES7, esse é um motivo?
Além disso, é class
um tipo diferente de OOP ou ainda é a herança prototípica do JavaScript? Posso modificá-lo usando .prototype
? Ou é apenas o mesmo objeto, mas duas maneiras diferentes de declará-lo.
Existem benefícios de velocidade? Talvez seja mais fácil manter / entender se você tiver um grande aplicativo como um grande aplicativo?
javascript
ecmascript-6
ssbb
fonte
fonte
Respostas:
A nova
class
sintaxe é, por enquanto , principalmente açúcar sintático. (Mas, você sabe, o bom tipo de açúcar.) Não há nada no ES2015-ES2020 queclass
possa fazer que você não possa fazer com funções de construtor eReflect.construct
(incluindo subclassesError
eArray
¹). (Ela é provável que haverá algumas coisas em ES2021 que você pode fazer comclass
que você não pode fazer o contrário: campos privados , métodos privados e campos estáticos / métodos estáticos privados .)É a mesma herança prototípica que sempre tivemos, apenas com uma sintaxe mais limpa e conveniente se você gosta de usar funções construtoras (
new Foo
, etc.). (Particularmente no caso de derivar deArray
ouError
, o que você não poderia fazer no ES5 e anteriores. Agora você pode comReflect.construct
[ spec , MDN ], mas não com o antigo estilo ES5.)Sim, você ainda pode modificar o
prototype
objeto no construtor da classe depois de criar a classe. Por exemplo, isso é perfeitamente legal:class Foo { constructor(name) { this.name = name; } test1() { console.log("test1: name = " + this.name); } } Foo.prototype.test2 = function() { console.log("test2: name = " + this.name); };
Ao fornecer um idioma específico para isso, suponho que seja possível que o motor seja capaz de fazer um trabalho melhor de otimização. Mas eles já são muito bons em otimizar, eu não esperaria uma diferença significativa.
Resumidamente: se você não usa funções construtoras em primeiro lugar, preferir
Object.create
ou semelhante,class
não é útil para você.Se você usar funções construtoras, existem alguns benefícios para
class
:A sintaxe é mais simples e menos sujeita a erros.
É muito mais fácil (e, novamente, menos sujeito a erros) configurar hierarquias de herança usando a nova sintaxe do que com a antiga.
class
o defende do erro comum de não usarnew
a função do construtor (fazendo com que o construtor lance uma exceção sethis
não for um objeto válido para o construtor).Chamar a versão do protótipo pai de um método é muito mais simples com a nova sintaxe do que a antiga (em
super.method()
vez deParentConstructor.prototype.method.call(this)
ouObject.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)
).Aqui está uma comparação de sintaxe para uma hierarquia:
// ***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() { // ... } }
Exemplo:
Exibir trecho de código
// ***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() { // ... };
Exemplo ao vivo:
Exibir trecho de código
// **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());
Como você pode ver, há muitas coisas repetidas e prolixas que são fáceis de errar e chatas de redigitar (é por isso que escrevi um script para fazer isso , naquela época).
¹ "Não há nada no ES2015-ES2018 que
class
possa fazer que você não possa fazer com funções de construtor eReflect.construct
(incluindo subclassesError
eArray
)"Exemplo:
Exibir trecho de código
// 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; }
fonte
class
. JavaScript é flexível o suficiente para que você não precise usar funções construtoras se não quiser, que é o que você está fazendo, mas isso é diferente declass
- o objetivoclass
é simplificar a herança pseudo-clássica em JavaScript.Symbol
confundir). Mas a grande coisa, novamente, é que se você não quiser usá-los, não precisa. Eu descobri em meu trabalho que as funções construtoras fazem sentido em muitos lugares e a semântica denew
melhorar a clareza; eObject.create
faz sentido em muitos outros lugares, esp. situações de fachada. Eles são complementares. :-)class
sintaxe não torna o JavaScript mais tipado do que antes. Como eu disse na resposta, é principalmente uma sintaxe (muito) mais simples para o que já estava lá. Havia uma necessidade absoluta para isso, as pessoas estavam constantemente errando a velha sintaxe. Também não significa que você precise usar a herança mais do que antes. (E, claro, se você nunca configurou "classes" com a sintaxe antiga, não há necessidade de fazer isso com a nova sintaxe também.)As classes ES6 são um açúcar sintático para o sistema de classes prototípico que usamos hoje. Eles tornam seu código mais conciso e autodocumentado, o que é motivo suficiente para usá-los (na minha opinião).
Usando Babel para transpilar esta classe ES6:
class Foo { constructor(bar) { this._bar = bar; } getBar() { return this._bar; } }
vai te dar algo como:
var Foo = (function () { function Foo(bar) { this._bar = bar; } Foo.prototype.getBar = function () { return this._bar; } return Foo; })();
A segunda versão não é muito mais complicada, é mais código para manter. Quando você envolve a herança, esses padrões se tornam ainda mais complicados.
Como as classes são compiladas com os mesmos padrões prototípicos que usamos, você pode fazer a mesma manipulação de protótipo nelas. Isso inclui adicionar métodos e similares em tempo de execução, acessar métodos em
Foo.prototype.getBar
, etc.Há algum suporte básico para privacidade no ES6 hoje, embora seja baseado na não exportação de objetos que você não deseja ter acesso. Por exemplo, você pode:
const BAR_NAME = 'bar'; export default class Foo { static get name() { return BAR_NAME; } }
e
BAR_NAME
não estará disponível para referência direta de outros módulos.Muitas bibliotecas tentaram oferecer suporte ou resolver isso, como o Backbone com seus
extends
auxiliar que pega um hash não validado de funções e propriedades semelhantes a métodos, mas não há um sistema consistente para expor a herança prototípica que não envolve mexer no protótipo.Conforme o código JS se torna mais complicado e as bases de código maiores, começamos a desenvolver muitos padrões para lidar com coisas como herança e módulos. O IIFE usado para criar um escopo privado para módulos tem muitos colchetes e parênteses; perder um deles pode resultar em um script válido que faz algo totalmente diferente (pular o ponto-e-vírgula após um módulo pode passar o próximo módulo para ele como um parâmetro, o que raramente é bom).
tl; dr: é açúcar para o que já fazemos e deixa sua intenção clara no código.
fonte