¿Qué beneficios ofrece la sintaxis de la clase ES2015 (ES6)?

87

Tengo muchas preguntas sobre las clases de ES6.

¿Cuál es el beneficio de utilizar la classsintaxis? Leí que público / privado / estático será parte de ES7, ¿es esa una razón?

Además, ¿es classun 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?

ssbb
fuente
Solo mi propia opinión: la sintaxis más nueva es mucho más fácil de entender y no requeriría mucha experiencia previa (especialmente porque la mayoría de las personas provienen de un lenguaje OOP). Pero vale la pena conocer el modelo de objeto prototipo y nunca aprenderá que usar la nueva sintaxis, además, será más desafiante trabajar en código prototipo a menos que ya lo sepa. Entonces elegiría escribir todo mi propio código con la sintaxis anterior cuando tenga esa opción
Zach Smith

Respuestas:

120

La nueva classsintaxis es, por ahora , principalmente azúcar sintáctica. (Pero, ya sabes, el buen tipo de azúcar). No hay nada en ES2015-ES2020 que classpueda hacer que no puedas hacer con las funciones de constructor y Reflect.construct(incluidas las subclases Errory Array¹). (Se es probable que haya algunas cosas en ES2021 que se puede hacer con classque no se puede hacer de otra manera: los campos privados , los métodos privados , y los campos estáticos / métodos estáticos privados .)

Además, ¿es classun tipo diferente de POO o sigue siendo la herencia prototípica de JavaScript?

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 de Arrayo Error, lo que no podía hacer en ES5 y versiones anteriores. Ahora puede hacerlo con Reflect.construct[ spec , MDN ], pero no con el antiguo estilo ES5).

¿Puedo modificarlo usando .prototype?

Sí, aún puede modificar el prototypeobjeto 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);
};

¿Hay beneficios de velocidad?

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.

¿Qué beneficios ofrece la sintaxis de ES2015 (ES6) class?

Brevemente: si no usa funciones de constructor en primer lugar, preferir Object.createo similar, classno 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.

  • classlo defiende del error común de no usar newcon la función del constructor (al hacer que el constructor arroje una excepción si thisno 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 de ParentConstructor.prototype.method.call(this)o Object.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:

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:

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 classpueda hacer que usted no pueda hacer con las funciones del constructor y Reflect.construct(incluidas las subclases Errory Array)"

Ejemplo:

TJ Crowder
fuente
9
No sé si esta es una comparación justa. Puede lograr un objeto similar sin toda la cruft. Utiliza la forma JS de vincular objetos a través de cadenas de prototipos en lugar de aproximarse a la herencia clásica. Hice un resumen que muestra un ejemplo simple basado en el tuyo para mostrar cómo hacer esto.
Jason Cust
8
@JasonCust: Sí, es una comparación justa, porque estoy mostrando la forma ES5 de hacer lo que hace ES6 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 de class: el objetivo classes simplificar la herencia pseudoclásica en JavaScript.
TJ Crowder
3
@JasonCust: Sí, es una forma diferente. No lo llamaría "más nativo" (sé que Kyle lo hace, pero ese es Kyle). Las funciones de constructor son fundamentales para JavaScript, mire todos los tipos integrados (excepto que la facción anti-constructor de alguna manera se las arregló para Symbolestropearse). 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 de newmejorar la claridad; y Object.createtiene sentido en muchos otros lugares, especialmente. situaciones de fachada. Son complementarios. :-)
TJ Crowder
4
meh. No compro la necesidad de esto. Me alejé de c # para alejarme de oop y ahora oop se arrastra en javascript. No me gustan los tipos. todo debe ser var / object / function. Eso es. no más palabras clave superfluas. y la herencia es una estafa. si quieres ver una buena pelea entre tipos y no tipos. eche un vistazo a jabón vs descanso. y todos sabemos quién ganó eso.
foreyez
3
@foreyez: la classsintaxis 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).
TJ Crowder
22

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_NAMEno estará disponible para que otros módulos hagan referencia directamente.

Muchas bibliotecas han intentado respaldar o resolver esto, como Backbone con su extendsayudante 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.

ssube
fuente