Quali vantaggi offre la sintassi `class` di ES2015 (ES6)?


87

Ho molte domande sulle classi ES6.

Qual è il vantaggio di usare class sintassi? Ho letto che pubblico / privato / statico farà parte di ES7, è un motivo?

Inoltre, è classun diverso tipo di OOP o è ancora l'eredità prototipica di JavaScript? Posso modificarlo usando.prototype ? O è solo lo stesso oggetto ma due modi diversi per dichiararlo.

Ci sono vantaggi in termini di velocità? Forse è più facile mantenere / capire se hai una grande applicazione come una grande app?


Solo la mia opinione: la nuova sintassi è molto più facile da capire e non richiederebbe molta esperienza precedente (soprattutto perché la maggior parte delle persone proviene da un linguaggio OOP). Ma vale la pena conoscere il modello a oggetti del prototipo e non imparerai mai che utilizzando la nuova sintassi, inoltre, sarà più difficile lavorare sul codice del prototipo a meno che tu non lo sappia già. Quindi sceglierei di scrivere tutto il mio codice con la vecchia sintassi quando ho questa scelta
Zach Smith,

Risposte:


120

La nuova classsintassi è, per ora , principalmente zucchero sintattico. (Ma, sai, il buon tipo di zucchero.) Non c'è niente in ES2015-ES2020 che classpuò farlo che tu non possa fare con le funzioni del costruttore e Reflect.construct(incluse le sottoclassi Errore Array¹). (Si è probabile che ci saranno alcune cose in ES2021 che si possono fare con classche non si può fare altrimenti: campi privati , i metodi privati , e campi statici / metodi statici privati .)

Inoltre, è classun diverso tipo di OOP o è ancora l'eredità prototipica di JavaScript?

È la stessa eredità prototipica che abbiamo sempre avuto, solo con una sintassi più pulita e più conveniente se ti piace usare le funzioni di costruzione ( new Foo, ecc.). (In particolare nel caso di derivazione da Arrayo Error, cosa che non si poteva fare in ES5 e versioni precedenti. Ora è possibile con Reflect.construct[ spec , MDN ], ma non con il vecchio stile ES5.)

Posso modificarlo usando .prototype?

Sì, puoi ancora modificare l' prototypeoggetto nel costruttore della classe dopo aver creato la classe. Ad esempio, questo è perfettamente legale:

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

Ci sono vantaggi in termini di velocità?

Fornendo un idioma specifico per questo, suppongo sia possibile che il motore possa essere in grado di eseguire un lavoro migliore di ottimizzazione. Ma sono già terribilmente bravi a ottimizzare, non mi aspetterei una differenza significativa.

Quali vantaggi offre la classsintassi di ES2015 (ES6) ?

In breve: se non usi le funzioni di costruzione in primo luogo, preferendo Object.createo simili, classnon ti è utile.

Se usi le funzioni di costruzione, ci sono alcuni vantaggi a class:

  • La sintassi è più semplice e meno soggetta a errori.

  • È molto più facile (e di nuovo, meno soggetto a errori) impostare gerarchie di ereditarietà utilizzando la nuova sintassi rispetto alla vecchia.

  • classti difende dall'errore comune di non riuscire a usarlo newcon la funzione di costruzione (facendo in modo che il costruttore generi un'eccezione se thisnon è un oggetto valido per il costruttore).

  • Chiamare la versione del prototipo genitore di un metodo è molto più semplice con la nuova sintassi rispetto alla vecchia ( super.method()invece di ParentConstructor.prototype.method.call(this)o Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Ecco un confronto della sintassi per una gerarchia:

// ***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() {
        // ...
    }
}

Esempio:

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() {
    // ...
};

Esempio dal vivo:

Come puoi vedere, ci sono molte cose ripetute e prolisse che è facile sbagliare e noioso da ridigitare (motivo per cui ho scritto uno script per farlo , nel corso della giornata).


¹ "Non c'è niente in ES2015-ES2018 che classpossa fare ciò che non puoi fare con le funzioni di costruzione e Reflect.construct(incluse le sottoclassi ErroreArray )"

Esempio:


9
Non so se questo sia un confronto equo. Puoi ottenere un oggetto simile senza tutto il cruft. Utilizza il modo JS di collegare gli oggetti attraverso catene di prototipi piuttosto che approssimare l'ereditarietà classica. Ho creato una sintesi che mostra un semplice esempio basato sul tuo per mostrare a farlo.
Jason Cust

8
@ JasonCust: Sì, è un confronto equo, perché sto mostrando il modo ES5 per fare ciò con cui ES6 fa class. JavaScript è abbastanza flessibile da non dover utilizzare le funzioni di costruzione se non lo si desidera, che è ciò che si sta facendo, ma è diverso da class- il punto centrale classè semplificare l'ereditarietà pseudo-classica in JavaScript.
TJ Crowder

3
@ JasonCust: Sì, è un modo diverso. Non lo chiamerei "più nativo" (so che Kyle lo fa, ma è Kyle). Le funzioni del costruttore sono fondamentali per JavaScript, guarda tutti i tipi incorporati (tranne che la fazione anti-costruttore è riuscita in qualche modo a Symbolincasinare). Ma la cosa grandiosa, ancora una volta, è che se non vuoi usarli, non devi. Trovo nel mio lavoro che le funzioni del costruttore abbiano senso in molti posti e la semantica di newmigliorare la chiarezza; e Object.createha senso in molti altri posti, esp. situazioni di facciata. Sono complementari. :-)
TJ Crowder

4
meh. Non ne compro la necessità. Mi sono allontanato da c # per allontanarmi da oop e ora oop sta strisciando su javascript. Non mi piacciono i tipi. tutto dovrebbe essere un var / oggetto / funzione. questo è tutto. niente più parole chiave estranee. e l'eredità è una truffa. se vuoi vedere una bella lotta tra tipi e non tipi. dai un'occhiata a soap vs rest. e sappiamo tutti chi l'ha vinto.
Shai UI

3
@foreyez: la classsintassi non rende JavaScript più digitato di quanto non fosse prima. Come ho detto nella risposta, è per lo più solo una sintassi (molto) più semplice per ciò che era già presente. C'era assolutamente bisogno di questo, le persone sbagliavano costantemente la vecchia sintassi. Inoltre non significa che devi usare l'ereditarietà più di quanto facevi prima. (E ovviamente, se non hai mai impostato "classi" con la vecchia sintassi, non è necessario farlo nemmeno con la nuova sintassi.)
TJ Crowder

22

Le classi ES6 sono zucchero sintattico per il prototipo del sistema di classi che usiamo oggi. Rendono il tuo codice più conciso e auto-documentante, il che è un motivo sufficiente per usarli (secondo me).

Utilizzo di Babel per trasferire questa classe ES6:

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

  getBar() {
    return this._bar;
  }
}

ti darà qualcosa come:

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

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

  return Foo;
})();

La seconda versione non è molto più complicata, è più codice da mantenere. Quando si coinvolge l'ereditarietà, questi schemi diventano ancora più complicati.

Poiché le classi vengono compilate fino agli stessi modelli prototipici che abbiamo usato, puoi eseguire la stessa manipolazione del prototipo su di esse. Ciò include l'aggiunta di metodi e simili in fase di esecuzione, l'accesso ai metodi suFoo.prototype.getBar , ecc.

Oggi c'è un supporto di base per la privacy in ES6, sebbene si basi sulla non esportazione degli oggetti che non si desidera siano accessibili. Ad esempio, puoi:

const BAR_NAME = 'bar';

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

e BAR_NAME non sarà disponibile per altri moduli a cui fare riferimento direttamente.

Molte librerie hanno provato a supportare o risolvere questo problema, come Backbone con il loro file extends helper che accetta un hash non convalidato di funzioni e proprietà simili a metodi, ma non esiste un sistema consistente per esporre l'ereditarietà prototipica che non implichi il masticare con il prototipo.

Man mano che il codice JS diventa più complicato e le basi di codice più grandi, abbiamo iniziato a sviluppare molti modelli per gestire cose come ereditarietà e moduli. L'IIFE utilizzato per creare uno scope privato per i moduli ha molte parentesi graffe e parentesi; la mancanza di uno di questi può risultare in uno script valido che fa qualcosa di completamente diverso (saltare il punto e virgola dopo che un modulo può passargli il modulo successivo come parametro, il che raramente è buono).

tl; dr: è zucchero per quello che già facciamo e rende chiaro il tuo intento nel codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.