Tengo muchas preguntas sobre las clases de ES6.
¿Cuál es el beneficio de utilizar la class
sintaxis? Leí que público / privado / estático será parte de ES7, ¿es esa una razón?
Además, ¿es class
un tipo diferente de POO o sigue siendo la herencia prototípica de JavaScript? ¿Puedo modificarlo usando .prototype
? ¿O es simplemente el mismo objeto pero dos formas diferentes de declararlo?
¿Hay beneficios de velocidad? ¿Quizás es más fácil de mantener / entender si tiene una gran aplicación como una gran aplicación?
javascript
ecmascript-6
ssbb
fuente
fuente
Respuestas:
La nueva
class
sintaxis es, por ahora , principalmente azúcar sintáctica. (Pero, ya sabes, el buen tipo de azúcar). No hay nada en ES2015-ES2020 queclass
pueda hacer que no puedas hacer con las funciones de constructor yReflect.construct
(incluidas las subclasesError
yArray
¹). (Se es probable que haya algunas cosas en ES2021 que se puede hacer conclass
que no se puede hacer de otra manera: los campos privados , los métodos privados , y los campos estáticos / métodos estáticos privados .)Es la misma herencia prototípica que siempre hemos tenido, solo que con una sintaxis más limpia y conveniente si te gusta usar funciones constructoras (
new Foo
, etc.). (Particularmente en el caso de derivar deArray
oError
, lo que no podía hacer en ES5 y versiones anteriores. Ahora puede hacerlo conReflect.construct
[ spec , MDN ], pero no con el antiguo estilo ES5).Sí, aún puede modificar el
prototype
objeto en el constructor de la clase una vez que haya creado la clase. Por ejemplo, esto es perfectamente 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); };
Al proporcionar un idioma específico para esto, supongo que es posible que el motor pueda hacer un mejor trabajo optimizando. Pero ya son muy buenos optimizando, no esperaría una diferencia significativa.
Brevemente: si no usa funciones de constructor en primer lugar, preferir
Object.create
o similar,class
no es útil para usted.Si usa funciones de constructor, existen algunos beneficios para
class
:La sintaxis es más simple y menos propensa a errores.
Es mucho más fácil (y de nuevo, menos propenso a errores) configurar jerarquías de herencia usando la nueva sintaxis que con la anterior.
class
lo defiende del error común de no usarnew
con la función del constructor (al hacer que el constructor arroje una excepción sithis
no es un objeto válido para el constructor).Llamar a la versión del prototipo principal de un método es mucho más simple con la nueva sintaxis que con la anterior (en
super.method()
lugar deParentConstructor.prototype.method.call(this)
oObject.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)
).Aquí hay una comparación de sintaxis para una jerarquía:
// ***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() { // ... } }
Ejemplo:
Mostrar fragmento 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() { // ... };
Ejemplo en vivo:
Mostrar fragmento 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 puede ver, hay muchas cosas repetidas y detalladas que es fácil equivocarse y aburrirse de volver a escribir (por eso escribí un guión para hacerlo , en el pasado).
¹ "No hay nada en ES2015-ES2018 que
class
pueda hacer que usted no pueda hacer con las funciones del constructor yReflect.construct
(incluidas las subclasesError
yArray
)"Ejemplo:
Mostrar fragmento 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; }
fuente
class
. JavaScript es lo suficientemente flexible como para que no tenga que usar funciones de constructor si no lo desea, que es lo que está haciendo, pero eso es diferente declass
: el objetivoclass
es simplificar la herencia pseudoclásica en JavaScript.Symbol
estropearse). Pero lo mejor, de nuevo, es que si no quieres usarlos, no tienes que hacerlo. Encuentro en mi trabajo que las funciones de constructor tienen sentido en muchos lugares y la semántica denew
mejorar la claridad; yObject.create
tiene sentido en muchos otros lugares, especialmente. situaciones de fachada. Son complementarios. :-)class
sintaxis no hace que JavaScript sea más tipeado de lo que estaba antes. Como dije en la respuesta, en su mayoría es una sintaxis (mucho) más simple para lo que ya estaba allí. Había una necesidad absoluta para eso, la gente constantemente se equivocaba con la vieja sintaxis. Tampoco significa que tenga que usar la herencia más de lo que lo hacía antes. (Y, por supuesto, si nunca configura "clases" con la sintaxis anterior, tampoco es necesario que lo haga con la nueva sintaxis).Las clases de ES6 son azúcar sintáctico para el sistema de clases prototípico que usamos hoy. Hacen que su código sea más conciso y autodocumentado, razón suficiente para usarlos (en mi opinión).
Usando Babel para transpilar esta clase ES6:
class Foo { constructor(bar) { this._bar = bar; } getBar() { return this._bar; } }
te dará algo como:
var Foo = (function () { function Foo(bar) { this._bar = bar; } Foo.prototype.getBar = function () { return this._bar; } return Foo; })();
La segunda versión no es mucho más complicada, es más código para mantener. Cuando se involucra la herencia, esos patrones se vuelven aún más complicados.
Debido a que las clases se compilan con los mismos patrones prototípicos que hemos estado usando, puede realizar la misma manipulación de prototipos en ellas. Eso incluye agregar métodos y similares en tiempo de ejecución, acceder a métodos
Foo.prototype.getBar
, etc.Hoy en día, ES6 ofrece un soporte básico para la privacidad, aunque se basa en no exportar los objetos que no desea que sean accesibles. Por ejemplo, puede:
const BAR_NAME = 'bar'; export default class Foo { static get name() { return BAR_NAME; } }
y
BAR_NAME
no estará disponible para que otros módulos hagan referencia directamente.Muchas bibliotecas han intentado respaldar o resolver esto, como Backbone con su
extends
ayudante que toma un hash no validado de funciones y propiedades similares a métodos, pero no existe un sistema consistente para exponer la herencia prototípica que no implique jugar con el prototipo.A medida que el código JS se vuelve más complicado y las bases de código más grandes, hemos comenzado a desarrollar muchos patrones para manejar cosas como la herencia y los módulos. El IIFE utilizado para crear un ámbito privado para los módulos tiene muchas llaves y paréntesis; faltar uno de esos puede resultar en un script válido que hace algo completamente diferente (omitir el punto y coma después de un módulo puede pasarle el siguiente módulo como parámetro, lo que rara vez es bueno).
tl; dr: es azúcar para lo que ya hacemos y deja clara su intención en el código.
fuente