Quais são os benefícios da sintaxe de `classe` do ES2015 (ES6)?

87

Tenho muitas dúvidas sobre as classes ES6.

Qual é a vantagem de usar classsintaxe? Eu li que público / privado / estático fará parte do ES7, esse é um motivo?

Além disso, é classum 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?

ssbb
fonte
Apenas minha própria opinião: a sintaxe mais recente é muito mais fácil de entender e não exigiria muita experiência anterior (especialmente porque a maioria das pessoas veio de uma linguagem OOP). Mas vale a pena conhecer o modelo de objeto de protótipo e você nunca aprenderá que usando a nova sintaxe, também, será mais desafiador trabalhar no código de protótipo, a menos que você já saiba. Então, eu escolheria escrever todo meu próprio código com a sintaxe antiga quando tivesse essa escolha
Zach Smith

Respostas:

120

A nova classsintaxe é, por enquanto , principalmente açúcar sintático. (Mas, você sabe, o bom tipo de açúcar.) Não há nada no ES2015-ES2020 que classpossa fazer que você não possa fazer com funções de construtor e Reflect.construct(incluindo subclasses Errore Array¹). (Ela é provável que haverá algumas coisas em ES2021 que você pode fazer com classque você não pode fazer o contrário: campos privados , métodos privados e campos estáticos / métodos estáticos privados .)

Além disso, é classum tipo diferente de OOP ou ainda é a herança prototípica do JavaScript?

É 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 de Arrayou Error, o que você não poderia fazer no ES5 e anteriores. Agora você pode com Reflect.construct[ spec , MDN ], mas não com o antigo estilo ES5.)

Posso modificá-lo usando .prototype?

Sim, você ainda pode modificar o prototypeobjeto 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);
};

Existem benefícios de velocidade?

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.

Quais os benefícios que a classsintaxe ES2015 (ES6) oferece?

Resumidamente: se você não usa funções construtoras em primeiro lugar, preferir Object.createou semelhante, classnã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.

  • classo defende do erro comum de não usar newa função do construtor (fazendo com que o construtor lance uma exceção se thisnã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 de ParentConstructor.prototype.method.call(this)ou Object.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:

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:

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 classpossa fazer que você não possa fazer com funções de construtor e Reflect.construct(incluindo subclasses Errore Array)"

Exemplo:

TJ Crowder
fonte
9
Não sei se esta é uma comparação justa. Você pode alcançar um objeto semelhante sem todo o lixo. Ele usa a maneira JS de vinculação de objetos por meio de cadeias de protótipos em vez de se aproximar da herança clássica. Fiz um resumo mostrando um exemplo simples baseado no seu para mostrar como fazer isso.
Jason Cust
8
@JasonCust: Sim, é uma comparação justa, porque estou mostrando a maneira ES5 de fazer o que o ES6 faz 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 de class- o objetivo classé simplificar a herança pseudo-clássica em JavaScript.
TJ Crowder
3
@JasonCust: Sim, é uma maneira diferente. Eu não chamaria de "mais nativo" (eu sei que Kyle sim, mas esse é Kyle). Funções de construtor são fundamentais para JavaScript, olhe para todos os tipos embutidos (exceto que a facção anti-construtor de alguma forma conseguiu se Symbolconfundir). 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 de newmelhorar a clareza; e Object.createfaz sentido em muitos outros lugares, esp. situações de fachada. Eles são complementares. :-)
TJ Crowder
4
meh. Eu não acredito na necessidade disso. Eu me afastei do c # para ficar longe do oop e agora oop está rastejando no javascript. Eu não gosto de tipos. tudo deve ser uma var / objeto / função. é isso aí. sem palavras-chave irrelevantes. e herança é uma farsa. se você quiser ver uma boa luta entre tipos e não tipos. dê uma olhada em sabão vs descanso. e todos nós sabemos quem ganhou isso.
foreyez
3
@foreyez: a classsintaxe 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.)
TJ Crowder
22

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_NAMEnã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.

ssube
fonte