Содержание

Классы | JavaScript Camp

В JavaScript используется модель прототипного наследования: каждый объект наследует поля (свойства) и методы объекта-прототипа.

class​

Для определения класса используется ключевое🗝️ слово class:

class MyClass {
// методы класса
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}

Такой синтаксис называется объявлением класса.

Методы в классе не разделяются запятой

Синтаксис классов отличается от литералов объектов. Внутри классов запятые не требуются.

Класс может не иметь названия. С помощью выражения класса можно присвоить класс переменной :

const UserClass = class {
// тело класса
}

Классы можно экспортировать в виде модулей. Вот пример экспорта по умолчанию:

export default class User {
// тело класса
}

А вот пример именованного экспорта:

export class User {
// тело класса
}

Класс становится полезным, когда вы создаете экземпляр класса. Экземпляр — это объект, содержащий данные и поведение, описанные классом.

Оператор new создает экземпляр класса в JavaScript таким образом: instance = new Class().

Например, вы можете создать экземпляр класса User 👤 с помощью оператора new:

const myUser = new User()

new User() создает экземпляр класса

User 👤.

Видео​

Инициализация: constructor()​

constructor(…) это специальный метод в теле класса, который инициализирует экземпляр. Это место, где вы можете установить начальные значения для полей или выполнить любые настройки объектов.

В следующем примере конструктор устанавливает начальное значение поля name:

class User {
constructor(name) {
this.name = name
}
}

constructor класса User использует один параметр name, который используется для установки начального значения поля this. name

.

Внутри конструктора значение this равно вновь созданному экземпляру.

Аргументы, используемые для создания экземпляра класса, становятся параметрами конструктора :

function learnJavaScript() { class User { constructor(name) { name // => ‘Jon Snow’ this.name = name } } const user = new User(‘Jon Snow’) //Здесь можно менять значение return user.name }

Loading…

Параметр name внутри конструктора имеет значение Jon Snow.

Если вы не определяете конструктор для класса, создается конструктор по умолчанию. Конструктор по умолчанию является пустой функцией⚙️, которая не изменяет экземпляр.

В классе может быть только один метод с именем constructor.

Отказ от классов​

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

Проблемы?​

Пишите в Discord или телеграмм чат, а также подписывайтесь на наши новости

Вопросы:​

Какое ключевое🗝️ слово для определения класса?

  1. constructor()
  2. class
  3. this

Методы внутри класса разделяются ли запятой.

  1. true
  2. false

Сколько методов constructor() может находится в одном классе?

  1. Неограниченно
  2. До десяти
  3. Только один

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

Ссылки:​

  1. MDN web docs
  2. Learn JavaScript

Contributors ✨​

Thanks goes to these wonderful people (emoji key):


Philipp Dvinyaninov

Dmitriy Vasilev
💵

Resoner2005
🐛 🎨 🖋

Navernoss
🖋 🐛 🎨

Как работают классы в JavaScript. Вдохновение How JavaScript Classes Work… | by Hydrock | Front Stories

Published in

·

3 min read

·

Jun 4, 2018

Вдохновение How JavaScript Classes Work от Thon Ly

Введение

Введение классов (Classes) в JavaScript должно было сделать создание объектов более очевидным. Но на самом деле, классы в js — это всего лишь синтаксический сахар, и это может ввести в заблуждение, особенно людей из более «традиционного» программирования. Под капотом, классы в JS являются объектами. Классов в «классическом» смысле не существует.

Например, класс JavaScript для создания экземпляра автомобиля теперь имеет вот такой простой синтаксис:

После создания, объект будет иметь свойства maker, model и метод drive.

Чтобы создать класс Tesla, наследуемый от класса Car, делаем так:

К сожалению, такая простота может скрыть истинное понимание языка. Чтобы понять, что на самом деле происходит, мы попытаемся воссоздать класс Tesla, который наследуется от класса Car, используя только ванильный JavaScript.

Конструктор используется для установки свойств объекта в момент создания экземпляра. Хотя это и похоже на другие языки, в JavaScript это просто функция.

Когда мы вызываем функцию с ключевым оператором new, создается новый, пустой объект. В данном случае, this, ссылаться на вновь созданный объект. Отрабатывает код функции, и объект возвращается.

Методы класса — также обычные функции. Вполне возможно определить методы внутри конструктора, но это было бы неправильно, так как каждый новый объект, созданный из конструктора, будет иметь копии определений методов. Более эффективно определить методы в прототипе конструктора, который существует именно для этого.

Тут стоит напомнить о прототипах и наследовании. Как только вы создали/определили функцию, так же создается специальный объект который будет находиться по ссылке имя_функции.prototype. После создания экземпляра объекта, у него будет ссылка __proto__ который ссылается на тот самый прототип.

То есть имя_функции.prototype === экземпляр.__proto__

Итак, делаем метод:

Именно ключевой оператор new задает свойство __proto__ у вновь созданного объекта ссылкой на свойство prototype функции конструктора. Таким вот образом, возможен вызов метода объекта, даже если он не находится в самом объекте. Движок не найдя метод в объекте, будет продолжать искать его в цепочке прототипов __proto__, даже если придется пройти путь до самого корневого элемента.

Более того, JavaScript использует цепочку прототипов для создания наследования. Чтобы класс Tesla наследовался от класса Car, мы определяем функцию конструктор Tesla, и вызываем функцию конструктор Car с текущим контекстом, указывающим на новый объект от Tesla. Это эквивалентно вызову super().

И еще раз, подробнее. В момент вызова Tesla с new — создается экземпляр — объект. this ссылается на этот объект. Далее вызывается конструктор Car посредством явного указания контекста call, и так как this ссылается на вновь созданные объект, все свойства конструктора Car применяются именно к новому объекту.

Но этого еще недостаточно. Необходимо связать прототип конструктора Tesla c прототипом конструктора Car. Используем Object.create(). Мы создадим у конструктора Tesla новый прототип который в свою очередь будет ссылаться (__proto__) на прототип конструктора Car.

Также необходимо восстановить свойство constructor указывающий на функцию конструктор, так как при создании прототипа мы затерли значение созданное при объявлении функции. Используем метод defineProperty для объявления свойства constructor, с дескриптором, свойства которого содержит “enumerable: false”, так как изначально свойство не является перечисляемым.

Наконец, мы можем определить метод charge() на прототипе конструктора Tesla.

Итак, создали экземпляр Tesla. Объект будет иметь свойства созданные конструктором Car и Tesla. При вызове метода drive(), движок не найдя его в объекте и прототипе объекта пойдет по цепочке __proto__ и найдет нужный метод в прототипе Car. Метод charge() уже находится в прототипе Tesla.

Заключение

Когда мы просто определяем класс в JS, многое происходит скрытно. Синтаксис очень похож на другие языки, но по факту — это совсем разные вещи. «Классы» — это объекты, а любой объект может наследовать свойства любого другого объекта.

Я надеюсь, вам понравился пост. Если да, похлопайте 👏, чтобы помочь другим найти эту информацию. И не стесняйтесь оставлять комментарии — буду рад любым замечаниям!

Разбираемся в путанице классов ES6

JavaScript — странный язык. Хотя он вдохновлен Smalltalk, он использует синтаксис, подобный C. Он сочетает в себе аспекты парадигм процедурного, функционального и объектно-ориентированного программирования (ООП). Он имеет многочисленные, часто избыточные подходы к решению почти любой мыслимой проблемы программирования и не имеет строгого мнения о том, какой из них предпочтительнее. Он слабо и динамически типизирован, с лабиринтным подходом к приведению типов, который сбивает с толку даже опытных разработчиков.

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

Проблема с классами ES6

Худший нарушитель, безусловно, является новым для последней версии JavaScript, ECMAScript 6 (ES6): классов . Некоторые разговоры о классах откровенно настораживают и раскрывают глубоко укоренившееся непонимание того, как на самом деле работает язык:

«JavaScript наконец-то настоящий объектно-ориентированный язык, когда в нем есть классы!»

Или:

«Классы освобождают нас от мыслей о сломанной модели наследования JavaScript».

Или даже:

«Классы — это более безопасный и простой подход к созданию типов в JavaScript».

Эти утверждения меня не беспокоят, потому что они подразумевают, что с прототипным наследованием что-то не так; отбросим эти аргументы. Эти утверждения беспокоят меня, потому что ни одно из них не соответствует действительности, и они демонстрируют последствия подхода JavaScript «все для всех» к проектированию языка: он чаще наносит вред программисту, чем помогает ему понять язык. Прежде чем идти дальше, давайте проиллюстрируем.

Тест на JavaScript №1: в чем существенная разница между этими блоками кода?

 функция PrototypeGreeting (приветствие = "Привет", имя = "Мир") {
  this.greeting = приветствие
  это.имя = имя
}
PrototypeGreeting.prototype.greet = функция () {
  вернуть `${this.greeting}, ${this.name}!`
}
const GreetProto = new PrototypeGreeting("Привет", "люди")
console.log(приветствоватьProto.приветствовать())
 
 класс ClassicalGreeting {
  конструктор (приветствие = "Привет", имя = "Мир") {
    this.greeting = приветствие
    это.имя = имя
  }
  приветствовать() {
    вернуть `${this.greeting}, ${this.name}!`
  }
}
const classyGreeting = new ClassicalGreeting("Привет", "люди")
console. log(classyGreeting.greet())
 

Ответ здесь нет ни одного . По сути, они делают одно и то же, вопрос только в том, использовался ли синтаксис класса ES6.

Правда, второй пример выразительнее. Только по этой причине можно утверждать, что класс

— хорошее дополнение к языку. К сожалению, проблема немного более тонкая.

JavaScript Pop Quiz #2: Что делает следующий код?

 функция Прото() {
  this.name = 'Прототип'
  вернуть это;
}
Proto.prototype.getName = функция () {
  вернуть это.имя
}
класс MyClass расширяет Proto {
  конструктор () {
    супер()
    this.name = 'МойКласс'
  }
}
константный экземпляр = новый MyClass()
console.log(экземпляр.getName())
Proto.prototype.getName = function() { return 'Переопределено в Proto'}
console.log(экземпляр.getName())
MyClass.prototype.getName = function() {return 'Переопределено в MyClass'}
console.log(экземпляр.getName())
instance.getName = function() { return 'Переопределено в экземпляре' }
console.
log(экземпляр.getName())

Правильный ответ: вывод на консоль:

 > MyClass
> Переопределено в Proto
> Переопределено в MyClass
> Переопределено в экземпляре
 

Если вы ответили неправильно, вы не понимаете, что такое класс на самом деле. Это не твоя вина. Подобно Array , class — это не особенность языка, это синтаксическое мракобесие . Он пытается скрыть прототипическую модель наследования и сопутствующие ей неуклюжие идиомы и подразумевает, что JavaScript делает то, чего на самом деле не делает.

Возможно, вам говорили, что

класс был введен в JavaScript, чтобы классические ООП-разработчики, пришедшие с таких языков, как Java, чувствовали себя более комфортно с моделью наследования классов JavaScript/ES6. Если вы являетесь одним из этих разработчиков, этот пример, вероятно, привел вас в ужас. Должно. Это показывает, что ключевое слово JavaScript class не дает никаких гарантий, которые должен предоставлять класс. Он также демонстрирует одно из ключевых отличий модели наследования прототипов: прототипы — это экземпляров объектов , а не типов .

Прототипы JavaScript и классы

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

Дочерний элемент класса ES6 — это другое определение типа , которое расширяет родителя новыми свойствами и методами, экземпляры которых, в свою очередь, могут быть созданы во время выполнения. Дочерним элементом прототипа является другой объект экземпляр , который делегирует родительскому элементу любые свойства, не реализованные в дочернем.

Примечание: вам может быть интересно, почему я упомянул методы класса, а не методы прототипа. Это потому, что в JavaScript нет концепции методов. Функции — это первоклассные функции в JavaScript, и они могут иметь свойства или быть свойствами других объектов.

Конструктор класса создает экземпляр класса. Конструктор в JavaScript — это обычная старая функция, которая возвращает объект. Единственная особенность конструктора JavaScript заключается в том, что при вызове с помощью новое ключевое слово , оно назначает свой прототип в качестве прототипа возвращаемого объекта. Если это звучит для вас немного запутанно, вы не одиноки — это так, и это большая часть того, почему прототипы плохо изучены.

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

Рассмотрим следующее:

 let parent = { foo: 'foo' }
пусть ребенок = {}
Object.setPrototypeOf (дочерний, родительский)
console.log(child.
foo) // 'foo' child.foo = 'бар' console.log(child.foo) // 'бар' console.log(parent.foo) // 'foo' удалить child.foo console.log(child.foo) // 'foo' parent.foo = 'баз' console.log(child.foo) // 'баз'

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

В предыдущем примере child.foo был undefined , он ссылался на parent.foo . Как только мы определили foo для child , child.foo имело значение 'bar' , но parent.foo сохранило свое исходное значение. Как только мы

удаляем child.foo , он снова ссылается на parent.foo , что означает, что когда мы меняем значение родителя, child.foo ссылается на новое значение.

Давайте посмотрим, что только что произошло (для большей ясности мы представим, что это Строки , а не строковые литералы, здесь разница не имеет значения):

То, как это работает под капотом, и особенно особенности new и this , это тема для отдельного разговора, но у Mozilla есть подробная статья о цепочке наследования прототипов JavaScript, если вы хотите узнать больше.

Ключевым выводом является то, что прототипы не определяют тип ; они сами являются экземплярами и могут изменяться во время выполнения со всеми вытекающими последствиями.

Все еще со мной? Вернемся к разбору классов JavaScript.

Популярный тест по JavaScript № 3: Как вы реализуете конфиденциальность в классах?

Наш прототип и свойства класса выше не столько «инкапсулированы», сколько «ненадежно свисают из окна». Мы должны исправить это, но как?

Здесь нет примеров кода. Ответ заключается в том, что вы не можете.

В JavaScript нет понятия конфиденциальности, но есть замыкания:

 function SecretiveProto() {
  const secret = "Класс - ложь!"
  this.spillTheBeans = функция () {
    console.log(секрет)
  }
}
const blabbermouth = новый SecretiveProto()
пытаться {
  console.log(болтун.секрет)
}
поймать (е) {
  // TypeError: SecretiveClass.secret не определен
}
blabbermouth.spillTheBeans() // "Класс - ложь!"
 

Вы понимаете, что только что произошло? Если нет, то вы не разбираетесь в замыканиях. На самом деле все в порядке — они не такие пугающие, как их изображают, они очень полезные, и вам нужно время, чтобы узнать о них.

Давайте посмотрим на другую половину вопроса о классе JavaScript и функции.

JavaScript Pop Quiz #4: Что эквивалентно приведенному выше, используя ключевое слово

class ?

Извините, это еще один вопрос с подвохом. Вы можете сделать в основном то же самое, но это выглядит так:

 класс секретный класс {
  конструктор () {
    const secret = "Я ложь!"
    this.spillTheBeans = функция () {
      console.log(секрет)
    }
  }
  свободные губы () {
    console.log(секрет)
  }
}
const лжец = новый SecretiveClass()
пытаться {
  console.log(лжец.секрет)
}
поймать (е) {
  console.log(e) // TypeError: SecretiveClass.secret не определен
}
liar.spillTheBeans() // "Я ложь!"
 

Дайте мне знать, если это выглядит проще или понятнее, чем в SecretiveProto . По моему личному мнению, это несколько хуже — это нарушает идиоматическое использование числа 9. 0005 класса в JavaScript, и он работает не так, как можно было бы ожидать, скажем, от Java. Это станет ясно из следующего:

JavaScript Pop Quiz #5: Что делает

SecretiveClass::looseLips() ?

Давайте выясним:

 попробуй {
  лжец.looseLips()
}
поймать (е) {
  // ReferenceError: секрет не определен
}
 

Ну… это было неловко.

Тест на JavaScript № 6: Что предпочитают опытные разработчики JavaScript — прототипы или классы?

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

 function secretFactory() {
  const secret = "Предпочитайте композицию наследованию, `new` считается вредным, и конец близок!"
  const SpillTheBeans = () => console.log(секрет)
  возвращаться {
    разливTheBeans
  }
}
const утечка = секретная фабрика ()
утечка.spillTheBeans()
 

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

Во-первых, вы можете деструктурировать его, потому что вам не нужно беспокоиться о контексте this :

 const { SpillTheBeans } = secretFactory()
разливTheBeans() // Предпочитаем композицию наследованию, (...)
 

Очень мило. Помимо того, что мы избегаем новых и этого дурачества , это позволяет нам взаимозаменяемо использовать наши объекты с модулями CommonJS и ES6. Это также немного упрощает композицию:

 функция spyFactory (цель инфильтрации) {
  возвращаться {
    эксфильтрация: infilterTarget.spillTheBeans
  }
}
const blackHat = spyFactory(утечка)
blackHat.exfiltrate() // Предпочтение композиции наследованию, (...)
console.log(blackHat.infilterTarget) // undefined (похоже, нам это сошло с рук)
 

Клиентам blackHat не нужно беспокоиться о том, откуда взялся exfiltrate , а spyFactory не нужно возиться с Function::bind жонглирование контекстом или глубоко вложенные свойства. Имейте в виду, нам не нужно сильно беспокоиться о на в простом синхронном процедурном коде, но это вызывает всевозможные проблемы в асинхронном коде, которых лучше избегать.

Немного подумав, spyFactory можно превратить в сложнейший шпионский инструмент, способный справиться со всеми видами целей проникновения, или, другими словами, в фасад.

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

Вернемся к примеру с приветствием, чтобы посмотреть, как мы реализуем его с фабрикой: возвращаться { приветствие: () => `${приветствие}, ${имя}!` } } console.log(greeterFactory(«Привет», «люди»).greet()) // Привет, ребята!

Вы могли заметить, что эти фабрики становятся все более лаконичными, но не волнуйтесь — они делают то же самое. Учебные колеса отрываются, ребята!

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

Так безопаснее, часто быстрее и проще писать такой код. Зачем нам снова нужны занятия? О, конечно же, повторное использование. Что произойдет, если нам нужны недовольные и восторженные варианты приветствующих? Ну, если мы используем ClassicalGreeting класс, мы, вероятно, сразу переходим к выдумыванию иерархии классов. Мы знаем, что нам нужно будет параметризовать пунктуацию, поэтому проведем небольшой рефакторинг и добавим несколько дочерних элементов:

 // Класс приветствия
класс ClassicalGreeting {
  конструктор (приветствие = "Привет", имя = "Мир", пунктуация = "!") {
    this. greeting = приветствие
    это.имя = имя
    это.пунктуация = пунктуация
  }
  приветствовать() {
    вернуть `${this.greeting}, ${this.name}${this.punctuation}`
  }
}
// Недовольное приветствие
класс UnhappyGreeting расширяет ClassicalGreeting {
  конструктор (приветствие, имя) {
    супер(приветствие, имя, " :(")
  }
}
const classyUnhappyGreeting = new UnhappyGreeting("Привет", "всем")
console.log(classyUnhappyGreeting.greet()) // Всем привет :(
// Восторженное приветствие
класс EnthusiasticGreeting расширяет ClassicalGreeting {
  конструктор (приветствие, имя) {
супер(приветствие, имя, "!!")
  }
  приветствовать() {
вернуть super.greet().toUpperCase()
  }
}
константное приветствиеWithEnthusiasm = новое EnthusiasticGreeting()
console.log(greetingWithEnthusiasm.greet()) // ПРИВЕТ, МИР!!
 

Это прекрасный подход, пока кто-нибудь не придет и не попросит функцию, которая не вписывается в иерархию, и все это перестанет иметь какой-либо смысл. Закрепите эту мысль, пока мы пытаемся написать ту же функциональность с фабриками:

 const GreeterFactory = (greeting = "Hello", name = "World", punctuation = "!") => ({
  приветствие: () => `${приветствие}, ${имя}${знак препинания}`
})
// Делает приветствующего несчастным
const unhappy = (приветствие) => (приветствие, имя) => приветствие (приветствие, имя, ":()")
console. log(unhappy(greeterFactory)("Привет", "всем").greet()) // Всем привет :(
// Воодушевляет приветствующего
const восторженный = (приветствие) => (приветствие, имя) => ({
  приветствие: () => приветствие(приветствие, имя, "!!").greet().toUpperCase()
})
console.log(восторженный(greeterFactory)().greet()) // ПРИВЕТ, МИР!!
 

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

Затем приходит ваш клиент и говорит: «Мне нужен новый встречающий, который недоволен и хочет, чтобы об этом узнал весь зал!»

 console.log(восторженный(недовольный(greeterFactory))().greet()) // ПРИВЕТ, МИР :(
 

Если бы нам нужно было использовать этот восторженно-недовольный приветствующий более одного раза, мы могли бы облегчить себе задачу:

 const AgressiveGreeterFactory = восторженный(недовольный(greeterFactory))
console. log(aggressiveGreeterFactory("Ты опоздал", "Джим").greet())
 

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

Дело в том, что в JavaScript вы не получаете такой автоматической безопасности. Фреймворки JavaScript, которые подчеркивают использование класса класса , делают много «волшебства», чтобы скрыть подобные проблемы и заставить классы JS вести себя должным образом. Взгляните как-нибудь на исходный код Polymer ElementMixin , уверяю вас. Это арканные уровни аркана JavaScript, и я имею в виду без иронии или сарказма.

Конечно, мы можем исправить некоторые проблемы, описанные выше, с помощью Object. freeze или Object.defineProperties для большего или меньшего эффекта. Но зачем имитировать форму без функции, игнорируя инструменты, которые JavaScript предоставляет нам изначально, которые мы не можем найти в таких языках, как Java? Вы бы использовали молоток с надписью «отвертка», чтобы закрутить винт, когда в вашем ящике с инструментами была настоящая отвертка, сидящая рядом с ним?

В поисках хороших сторон

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

Существуют разумные аргументы в пользу того, какие части JavaScript подходят, но я надеюсь, что убедил вас, что класс не является одним из них. В противном случае, надеюсь, вы понимаете, что наследование в JavaScript может быть запутанным беспорядком и что класс не исправляет это и не избавляет вас от необходимости разбираться в прототипах. Дополнительный кредит, если вы уловили намеки на то, что объектно-ориентированные шаблоны проектирования прекрасно работают без классов или наследования ES6.

Я не говорю вам избегать класс целиком. Иногда вам нужно наследование, и class предоставляет более чистый синтаксис для этого. В частности, класс X расширяет Y , что намного лучше, чем подход старого прототипа. Кроме того, многие популярные интерфейсные фреймворки поощряют его использование, и вам, вероятно, следует избегать написания странного нестандартного кода только из принципа. Мне просто не нравится, куда это идет.

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

В конце концов мы все сдаемся и начинаем заново изобретать велосипеды на Rust, Go, Haskell или кто знает что еще, а затем компилировать в Wasm для Интернета, а новые веб-фреймворки и библиотеки множатся до бесконечности.

Это действительно не дает мне спать по ночам.

Понимание основ

  • Что такое ECMAScript 6?

    ES6 — это последняя стабильная реализация ECMAScript, открытого стандарта, на котором основан JavaScript. Он добавляет в язык ряд новых функций, включая официальную систему модулей, переменные и константы с блочной областью действия, стрелочные функции и множество других новых ключевых слов, синтаксисов и встроенных объектов.

  • Является ли ES6 последней версией ECMAScript?

    ES6 (ES2015) — это самый последний стандарт, который является стабильным и полностью реализованным (за исключением правильных хвостовых вызовов и некоторых нюансов) в последних версиях основных браузеров и других средах JS. ES7 (ES2016) и ES8 (ES2017) также являются стабильными спецификациями, но их реализация весьма неоднозначна.

  • Является ли ES6 объектно-ориентированным?

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

  • Что такое классы ES6?

    В ES6 ключевое слово «класс» и связанные с ним функции представляют собой новый подход к созданию конструкторов прототипов. Они не являются настоящими классами в том смысле, в каком они знакомы пользователям большинства других объектно-ориентированных языков.

  • Какие ключевые слова можно использовать для реализации наследования в ES6?

    В JavaScript ES6 можно реализовать наследование с помощью ключевых слов «класс» и «расширяет». Другой подход заключается в использовании идиомы функции «конструктор» плюс назначение функций и статических свойств прототипу конструктора.

  • Что такое наследование прототипов в JavaScript?

    В прототипном наследовании прототипы — это экземпляры объектов, которым дочерние экземпляры делегируют неопределенные свойства. Напротив, классы в классическом наследовании являются определениями типов, от которых дочерние классы наследуют методы и свойства во время создания экземпляра.

Reddit — Погрузитесь во что угодно

Просто любопытно, я часто вижу, как люди говорят, что классы в JS — это «просто синтаксический сахар, ничего похожего на Java или C#», задаваясь вопросом, что это значит?

Я знаю, что class в JS на самом деле не синтаксический сахар (как в CoffeeScript, если вы помните это :), движок V8 включает в себя некоторые специальные оптимизации, есть небольшие отличия от использования прототипов JS. Но дело не в этом.

Какая концептуальная или практическая разница между классом в TS и классом С# или Java?

Я знаю, как работают прототипы в JS, я использовал его до появления синтаксиса классов. Вопрос в том, чем отличается использование класса с синтаксисом класса между TS (JS) и C# или Java.

Я не знаю C# или Java, и кажется, что люди, которые приходят из них, несколько удивлены тем, как работают классы в JS.

Всем спасибо за ответы!

СУММА КОММЕНТАРИЙ:

C# может сериализовать JSON в класс, потому что C# достаточно умен, чтобы использовать типы во время выполнения. Чтобы узнать больше об этом, выполните поиск «отражение типов», «типы времени выполнения». Проще говоря, C # может проверять JSON во время выполнения на основе типов из типа компиляции. TS не может этого сделать, а не цель, вместо этого мы должны использовать библиотеки, такие как zod, для синтаксического анализа, сначала определить «типы» во время выполнения, а затем сделать вывод об их «компилировании». типы «время». Это своего рода хак.

Java, C# и, вероятно, большинство строго типизированных языков используют «номинальную» типизацию, а TS использует «структурную» типизацию. Как я слышал из какой-то статьи, «структурная» типизация — редкая жемчужина, она реализована в Typescript, Crystal и Scala 3 и больше нигде. Для классов это означает, что если классы A и B имеют одинаковые свойства и методы, они равны для TS, но в C#/Java это два разных класса.

Если вам грустно из-за того, что класс A может быть равен классу B, не беспокойтесь, в TS можно сделать «непрозрачный» тип, проверьте это здесь, я не пробовал, но я считаю, что TS достаточно гибок, чтобы применить этот непрозрачный к класс.

Вы можете изменить класс в JS. Такое можно правильно напечатать в TS, требует некоторых махинаций с типом. И невозможно в С# или Java. И особо не нужен.

Если кто-то говорит, что JS «симулирует» классы, поделитесь этой цитатой из MDN:

> Сама прототипная модель наследования на самом деле более мощная, чем классическая модель. Например, довольно просто построить классическую модель поверх модели-прототипа.

Согласны ли вы с тем, является ли классический ООП Java или Smalltalk — решать вам, MDN здесь на стороне Java.

Частный и общедоступный: JS настолько открыт для людей, что по умолчанию он общедоступен, у нас есть ключевое слово `private` TS и # вход в JS для частного. В Java также есть публичный и приватный, приватный по умолчанию. C# имеет «защищенный», что означает защищенный от подклассов (если я правильно понимаю), и «внутренний», что *вероятно* означает закрытый.

В JS есть наследование прототипов. Я думаю, что это довольно важный факт, может быть, важный, если вы смотрите с точки зрения JS, и это неожиданно, когда вы переходите с C#/Java. Похоже на то.

Модификатор доступа `private` и другие языковые ограничения не могут защитить программу, если вредоносное ПО может проникнуть внутрь ее среды выполнения, будь то браузер, node.js или JVM.