Javascript класс языка программирования

Класс: базовый синтаксис

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

На практике нам часто надо создавать много объектов одного вида, например пользователей, товары или что-то ещё.

Как мы уже знаем из главы Конструктор, оператор «new», с этим может помочь new function .

Но в современном JavaScript есть и более продвинутая конструкция «class», которая предоставляет новые возможности, полезные для объектно-ориентированного программирования.

Синтаксис «class»

Базовый синтаксис выглядит так:

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

Затем используйте вызов new MyClass() для создания нового объекта со всеми перечисленными методами.

При этом автоматически вызывается метод constructor() , в нём мы можем инициализировать объект.

class User < constructor(name) < this.name = name; >sayHi() < alert(this.name); >> // Использование: let user = new User("Иван"); user.sayHi();

Когда вызывается new User(«Иван») :

  1. Создаётся новый объект.
  2. constructor запускается с заданным аргументом и сохраняет его в this.name .

…Затем можно вызывать на объекте методы, такие как user.sayHi() .

Частая ошибка начинающих разработчиков – ставить запятую между методами класса, что приводит к синтаксической ошибке.

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

Что такое класс?

Итак, что же такое class ? Это не полностью новая языковая сущность, как может показаться на первый взгляд.

Давайте развеем всю магию и посмотрим, что такое класс на самом деле. Это поможет в понимании многих сложных аспектов.

В JavaScript класс – это разновидность функции.

class User < constructor(name) < this.name = name; >sayHi() < alert(this.name); >> // доказательство: User - это функция alert(typeof User); // function

Вот что на самом деле делает конструкция class User <. >:

  1. Создаёт функцию с именем User , которая становится результатом объявления класса. Код функции берётся из метода constructor (она будет пустой, если такого метода нет).
  2. Сохраняет все методы, такие как sayHi , в User.prototype .

При вызове метода объекта new User он будет взят из прототипа, как описано в главе F.prototype. Таким образом, объекты new User имеют доступ к методам класса.

На картинке показан результат объявления class User :

Можно проверить вышесказанное и при помощи кода:

class User < constructor(name) < this.name = name; >sayHi() < alert(this.name); >> // класс - это функция alert(typeof User); // function // . или, если точнее, это метод constructor alert(User === User.prototype.constructor); // true // Методы находятся в User.prototype, например: alert(User.prototype.sayHi); // sayHi() < alert(this.name); >// в прототипе ровно 2 метода alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

Не просто синтаксический сахар

Иногда говорят, что class – это просто «синтаксический сахар» в JavaScript (синтаксис для улучшения читаемости кода, но не делающий ничего принципиально нового), потому что мы можем сделать всё то же самое без конструкции class :

// перепишем класс User на чистых функциях // 1. Создаём функцию constructor function User(name) < this.name = name; >// каждый прототип функции имеет свойство constructor по умолчанию, // поэтому нам нет необходимости его создавать // 2. Добавляем метод в прототип User.prototype.sayHi = function() < alert(this.name); >; // Использование: let user = new User("Иван"); user.sayHi();

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

Однако есть важные отличия:

    Во-первых, функция, созданная с помощью class , помечена специальным внутренним свойством [[IsClassConstructor]]: true . Поэтому это не совсем то же самое, что создавать её вручную. В отличие от обычных функций, конструктор класса не может быть вызван без new :

class User < constructor() <>> alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new'

Кроме того, строковое представление конструктора класса в большинстве движков JavaScript начинается с «class …»

class User < constructor() <>> alert(User); // class User

Также в дополнение к основной, описанной выше, функциональности, синтаксис class даёт ряд других интересных возможностей, с которыми мы познакомимся чуть позже.

Class Expression

Как и функции, классы можно определять внутри другого выражения, передавать, возвращать, присваивать и т.д.

Пример Class Expression (по аналогии с Function Expression):

Аналогично Named Function Expression, Class Expression может иметь имя.

Если у Class Expression есть имя, то оно видно только внутри класса:

// "Named Class Expression" // (в спецификации нет такого термина, но происходящее похоже на Named Function Expression) let User = class MyClass < sayHi() < alert(MyClass); // имя MyClass видно только внутри класса >>; new User().sayHi(); // работает, выводит определение MyClass alert(MyClass); // ошибка, имя MyClass не видно за пределами класса

Мы даже можем динамически создавать классы «по запросу»:

function makeClass(phrase) < // объявляем класс и возвращаем его return class < sayHi() < alert(phrase); >; >; > // Создаём новый класс let User = makeClass("Привет"); new User().sayHi(); // Привет

Геттеры/сеттеры, другие сокращения

Как и в литеральных объектах, в классах можно объявлять вычисляемые свойства, геттеры/сеттеры и т.д.

Вот пример user.name , реализованного с использованием get/set :

class User < constructor(name) < // вызывает сеттер this.name = name; >get name() < return this._name; >set name(value) < if (value.length < 4) < alert("Имя слишком короткое."); return; >this._name = value; > > let user = new User("Иван"); alert(user.name); // Иван user = new User(""); // Имя слишком короткое.

При объявлении класса геттеры/сеттеры создаются на User.prototype , вот так:

Object.defineProperties(User.prototype, < name: < get() < return this._name >, set(name) < // . >> >);

Пример с вычисляемым свойством в скобках [. ] :

Источник

Нужны ли в JavaScript классы?

JavaScript принято считать прототип-ориентированным языком программирования. Но, как ни странно, этим подходом практически никто не пользуется: большинство популярных JS-фреймворков явно или неявно оперируют классами.
В этой статье я хочу рассказать об альтернативном способе программирования на JavaScript, без использования классов и конструкторов — чистым прототипным ООП и особенностях его реализации на ECMA Script 5.

ООП можно разделить на две группы: класс-ориентированное (классическое) и прототип-ориентированное. Классический подход отражает взгляд Аристотеля на мир, в котором всё описывается идеальными понятиями. Прототипное ООП ближе к философии Людвига Витгенштейна, которая не полагается на строгую категоризацию и классификацию всего и вся, а пытается представить понятия предметной области материальными и интуитивно понятными (насколько это возможно). Типичным аргументом в пользу прототипирования является то, что обычно намного проще сначала разобраться в конкретных примерах, а только потом, изучая и обобщая их, выделить некоторые абстрактные принципы и впоследствии их применять.

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

Классы

В JavaScript нет классов, скажете вы. Я бы не стал так утверждать.
Под классами в JS я подразумеваю функции-конструкторы: функции, вызываемой при создании экземпляра (выполнении оператора new ), со ссылкой на прототип — объект, содержащий свойства (данные) и методы (функции) класса.

Как известно, в ЕСМА Script 6 возможно таки введут ключевое слово class :

 class Duck< constructor(name)< this.name = name; >, quack() < return this.name +" Duck: Quack-quack!"; >> /// Наследование class TalkingDuck extends Duck< constructor(name)< super(name); >, quack() < return super.quack() + " My name is " + this.name; >> /// Инстанцирование var donald = new TalkingDuck("Donald"); 

Но по сути, ничего существенного (например модификаторов public , private ) данное нововведение не принесет. Это не что иное, как синтаксический сахар для подобной конструкции:

 var Duck = function(name)< this.name = name; >; Duck.prototype.quack = function()< return this.name +" Duck: Quack-quack!"; >; /// Наследование var TalkingDuck = function(name) < Duck.call(this, name); >TalkingDuck.prototype = Object.create(Duck.prototype); TalkingDuck.prototype.constructor = TalkingDuck; TalkingDuck.prototype.quack = function()< return Duck.prototype.quack.call(this) + " My name is " + this.name; >; /// Инстанцирование var donald = new TalkingDuck("Donald"); 

Следовательно, классы в текущей версии JS уже есть, только нет удобной синтаксической конструкции для их создания.
В конце-концов, давайте определимся, что же такое класс. Вот определение (из википедии):

Класс — разновидность абстрактного типа данных в ООП, характеризуемый способом своего построения. Суть отличия классов от других абстрактных типов данных состоит в том, что при задании типа данных класс определяет одновременно и интерфейс, и реализацию для всех своих экземпляров, а вызов метода-конструктора обязателен.

Следуя этому определению, функция-конструктор является классом:
Функция-конструктор это абстрактный тип данных? — Да.
Функция-конструктор (вместе с свойствами из прототипа) определяет одновременно и интерфейс, и реализацию? — Да.
Вызов конструктора при создании экземпляра обязателен? — Да.

Прототипы

  1. Это уже готовый к использованию объект, не нуждающийся в инстанцировании. Он может иметь собственное состояние (state). Можно сказать что прототип является классом и экземпляром объединенными в одну сущность, грубо говоря, Singleton’ом.
  2. Вызов конструктора при создании объекта (клонировании прототипа) не обязателен.

Наследование

Итак, суть прототипного (делегирующего) наследования состоит в том, что один объект может ссылаться на другой, что делает его прототипом. Если при обращении к свойству/методу оно не будет найдено в самом объекте, поиск продолжится в прототипе, а далее в прототипе прототипа и т.д.

 var duck$ = >; var donald = < __proto__: duck$, name: "Donald" >; var daffy = < __proto__: duck$, name: "Daffy" >; console.log( donald.quack() ); // Donald Duck: Quack-quack! console.log( daffy.quack() ); // Daffy Duck: Quack-quack! console.log( duck$.isPrototypeOf(donald) ); // true 

daffy и donald используют один общий метод quack() , который предоставляет им прототип duck$. С прототипной точки зрения donald и daffy являются клонами объекта duck$ , а с класс-ориентированной — “экземплярами класса” duck$ .
Eсли же добавить/изменить некоторые свойства непосредственно в объекте donald (или daffy ), тогда его можно будет считать еще и “наследником класса” duck$ . V8 так и делает, создавая скрытые классы при каждом добавлении свойства.

Не забываем, что свойство __proto__ eще не стандартизовано. Официально манипулировать свойством __proto__ возможно ECMAScript 5 методами Object.create и Object.getPrototypeOf :

 var donald = Object.create(duck$, < name: >); console.log( Object.getPrototypeOf(donald) === duck$ ); // true 

Инициализация

В отличии от класс-ориентированного подхода, наличие конструктора и его вызов при создании объекта на базе прототипа (клонировании) не обязателен.
Как же тогда инициализировать свойства объекта?
Простые, не калькулируемые значения по умолчанию для свойств можно сразу присвоить прототипу:

А если нужно использовать калькулируемые значения, то вместе с ECMA Script 5 нам на помощь приходит:

Ленивая (отложенная) инициализация

Ленивая инициализация это техника, позволяющая инициализировать свойство при первом к нему обращении:

var obj = < name: "БезИмени", get lazy()< console.log("Инициализация свойства lazy. "); // Вычисляем значение: var value = "Лениво инициализированное свойство " + this.name; // Переопределяем свойство, для того чтобы при следующем // обращении к нему, оно не вычислялось заново: Object.defineProperty(this, 'lazy', < value: value, writable: true, enumerable: true >); console.log("Инициализация окончена."); return value; >, // оставляем возможность инициализировать свойство // самостоятельно, в обход функции-инициализатора // (если это не будет влиять на согласованность объекта): set lazy(value)< console.log("Установка свойства lazy. "); Object.defineProperty(this, 'lazy', < value: value, writable: true, enumerable: true >); > >; console.log( obj.lazy ); // Инициализация свойства lazy. // Лениво инициализированное свойство БезИмени console.log( obj.lazy );// Инициализатор не запускается снова // Лениво инициализированное свойство БезИмени obj.lazy = "Переопределено";// Сеттер не запускается, т.к. свойство уже инициализировано console.log( obj.lazy ); // Переопределено 
  • Разбиение конструктора на более мелкие методы-аксессоры “автоматически”, как профилактика появления длинных конструкторов (см. длинный метод).
  • Прирост в производительности, т.к. не используемые свойства инициализироваться не будут.

Сравнительная таблица

var duck$ = < name: "Unnamed", get firstWords()< var value = this.quack(); Object.defineProperty( this, 'firstWords', ); return value; >, quack: function() < return this.name +" Duck: Quack-quack!"; >>; 
var Duck = function(name)< this.name = name||"Unnamed"; this.firstWords = this.quack(); >; Duck.prototype.quack = function()< return this.name +" Duck: Quack-quack!"; >; 
var talkingDuck$ = Object.create(duck$, < quack: > >); 
var TalkingDuck = function(name) < Duck.call(this, name); >TalkingDuck.prototype = Object.create(Duck.prototype); TalkingDuck.prototype.constructor = TalkingDuck; TalkingDuck.prototype.quack = function()< return Duck.prototype.quack.call(this) + " My name is " + this.name; >; 
class TalkingDuck extends Duck< constructor(name)< super(name); >, quack() < return super.quack() + " My name is " + this.name; >> 
var donald = Object.create(talkingDuck$); donald.name = "Donald"; 
var donald = new TalkingDuck("Donald"); 
var donald = new TalkingDuck("Donald"); 
  • javascript
  • prototype
  • prototype-based
  • OOP
  • class
  • constructor
  • lazy initialization
  • ECMA Script 5
  • ECMA Script 6
  • прототип
  • прототип-ориентированное
  • программирование
  • класс
  • конструктор
  • ленивая инициализация
  • отложенная инициализация
  • поздняя инициализация

Источник

Читайте также:  Чем характеризуется тип данных языка программирования
Оцените статью