Какие преимущества дает синтаксис ES2015 (ES6) `class`?

87

У меня много вопросов по классам ES6.

Какая польза от использования class синтаксиса? Я читал, что публичные / частные / статические будут частью ES7, это причина?

Более того, является classли ООП другим видом или это все еще прототипное наследование JavaScript? Могу ли я изменить его с помощью .prototype? Или это один и тот же объект, но двумя разными способами его объявить.

Есть ли преимущества в скорости? Может быть, проще поддерживать / понимать, если у вас есть большое приложение, такое как большое приложение?

ssbb
источник
Просто мое собственное мнение: новый синтаксис намного проще для понимания и не требует большого опыта (тем более, что большинство людей пришли с языка ООП). Но объектную модель прототипа стоит знать, и вы никогда не узнаете, что, используя новый синтаксис, также будет сложнее работать над кодом прототипа, если вы этого еще не знаете. Поэтому я бы предпочел написать весь свой собственный код со старым синтаксисом, когда у меня был бы такой выбор,
Зак Смит

Ответы:

120

Новый classсинтаксис, для теперь , в основном синтаксического сахара. (Но, знаете, хороший вид сахара.) В ES2015-ES2020 classнет ничего такого, что нельзя было бы сделать с функциями конструктора и Reflect.construct(включая подклассы Errorи Array¹). (Это является , вероятно , будут какие - то вещи в ES2021 , что вы можете сделать с , classчто вы не можете сделать иначе: частные поля , частные методы и статические поля / частные статические методы .)

Более того, является classли ООП другим видом или это все еще прототипное наследование JavaScript?

Это то же прототипное наследование, которое у нас всегда было, только с более чистым и удобным синтаксисом, если вам нравится использовать функции конструктора ( new Fooи т. Д.). (В особенности в случае наследования от Arrayили Error, чего нельзя было сделать в ES5 и ранее. Теперь вы можете использовать Reflect.construct[ spec , MDN ], но не в старом стиле ES5.)

Могу ли я изменить его с помощью .prototype?

Да, вы все равно можете изменить prototypeобъект в конструкторе класса после создания класса. Например, это совершенно законно:

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

Есть ли преимущества в скорости?

Предоставляя для этого определенную идиому, я полагаю, что возможно, что движок сможет лучше выполнять оптимизацию. Но они уже ужасно хороши в оптимизации, я бы не ожидал значительной разницы.

Какие преимущества дает синтаксис ES2015 (ES6) class?

Вкратце: если вы в первую очередь не используете функции-конструкторы, предпочитаемые Object.createили подобные им, они вам classне пригодятся.

Если вы все же используете функции-конструкторы, есть некоторые преимущества class:

  • Синтаксис проще и менее подвержен ошибкам.

  • Это гораздо проще (и снова, менее подвержены ошибкам) , чтобы создать иерархии наследования , используя новый синтаксис , чем со старым.

  • classзащищает вас от распространенной ошибки, связанной newс thisневозможностью использования функции конструктора (за счет того, что конструктор генерирует исключение, если он не является допустимым объектом для конструктора).

  • Вызвать версию метода родительского прототипа с новым синтаксисом намного проще, чем со старым ( super.method()вместо ParentConstructor.prototype.method.call(this)или Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Вот сравнение синтаксиса для иерархии:

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

Пример:

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

Живой пример:

Как видите, там много повторяющихся и многословных вещей, в которых легко ошибиться и скучно перепечатывать (именно поэтому я написал сценарий для этого в свое время).


¹ "В ES2015-ES2018 classнет ничего такого, что нельзя было бы сделать с функциями конструктора и Reflect.construct(включая подклассы ErrorиArray

Пример:

TJ Crowder
источник
9
Не знаю, справедливо ли это сравнение. Вы можете достичь подобной цели без всякой ерунды. Он использует JS-способ связывания объектов через цепочки прототипов, а не приближение классического наследования. Я привел суть, показав простой пример, основанный на вашем, чтобы показать, как это сделать.
Джейсон Каст
8
@JasonCust: Да, это справедливое сравнение, потому что я показываю способ ES5 делать то, с чем ES6 делает class. JavaScript достаточно гибок, поэтому вам не нужно использовать функции конструктора, если вы этого не хотите, что вы делаете, но это отличается от class- весь смысл в classупрощении псевдоклассического наследования в JavaScript.
TJ Crowder
3
@JasonCust: Да, это по-другому. Я бы не назвал это «более естественным» (я знаю, что Кайл знает, но это Кайл). Конструкторские функции являются фундаментальными для JavaScript, посмотрите на все встроенные типы (за исключением того, что фракция антиконструкторов каким-то образом сумела запутаться Symbol). Но, опять же, самое замечательное в том, что если вы не хотите их использовать, вам и не нужно. В своей работе я обнаружил, что функции-конструкторы имеют смысл во многих местах, а семантика newулучшает ясность; и Object.createимеет смысл во многих других местах, особенно. фасадные ситуации. Они дополняют друг друга. :-)
TJ Crowder
4
Мех. Я не куплюсь на необходимость этого. Я отошел от C #, чтобы уйти от oop, и теперь oop ползет по javascript. Я не люблю типы. все должно быть var / object / function. вот и все. больше никаких посторонних ключевых слов. а наследование - это афера. если вы хотите увидеть хорошую борьбу между типами и нетипами. взгляните на мыло против отдыха. и все мы знаем, кто это выиграл.
Shai UI
3
@foreyez: classсинтаксис не делает JavaScript более типизированным, чем это было раньше. Как я сказал в ответе, это в основном (намного) более простой синтаксис для того, что уже было. В этом была абсолютная необходимость, люди постоянно ошибались в старом синтаксисе. Это также не означает, что вам нужно использовать наследование больше, чем раньше. (И, конечно же, если вы никогда не настраивали «классы» со старым синтаксисом, нет необходимости делать это и с новым синтаксисом.)
TJ Crowder
22

Классы ES6 - это синтаксический сахар для прототипной системы классов, которую мы используем сегодня. Они делают ваш код более лаконичным и самодокументированным, что является достаточной причиной для их использования (на мой взгляд).

Использование Babel для преобразования этого класса ES6:

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

  getBar() {
    return this._bar;
  }
}

даст вам что-то вроде:

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

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

  return Foo;
})();

Вторая версия не намного сложнее, это больше кода, который нужно поддерживать. Когда вы включаете наследование, эти шаблоны становятся еще более сложными.

Поскольку классы компилируются по тем же прототипным шаблонам, которые мы использовали, вы можете выполнять с ними те же манипуляции с прототипами. Это включает добавление методов и тому подобное во время выполнения, доступ к методам Foo.prototype.getBarи т. Д.

Сегодня в ES6 есть некоторая базовая поддержка конфиденциальности, хотя она основана на том, что не экспортирует объекты, которые вам не нужны. Например, вы можете:

const BAR_NAME = 'bar';

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

и BAR_NAMEне будет доступен для других модулей для непосредственной ссылки.

Многие библиотеки пытались поддержать или решить эту проблему, например Backbone с их extends помощником, который принимает непроверенный хэш функций и свойств, подобных методам, но нет системы составов для раскрытия прототипного наследования, которое не связано с работой с прототипом.

По мере того, как код JS становится более сложным, а кодовая база увеличивается, мы начали разрабатывать множество шаблонов для обработки таких вещей, как наследование и модули. IIFE, используемый для создания частной области видимости для модулей, имеет множество фигурных скобок и скобок; Отсутствие одного из них может привести к созданию действительного сценария, который делает что-то совершенно другое (пропуск точки с запятой после модуля может передать ему следующий модуль в качестве параметра, что редко бывает хорошо).

tl; dr: это сахар для того, что мы уже делаем, и проясняет ваши намерения в коде.

ssube
источник