Причина, по которой важно иметь классы в JavaScript:
- Классы предлагают полезную структурную абстракцию
- Предоставляют разработчикам согласованный единый способ использования классов вместо разных версий, предлагаемых фреймворками (emberjs, reactjs).
- Разработчики с опытом ООП уже понимают классы
На текущий момент JavaScript разработчики могут использовать class
. Далее реализация базового класса Point:
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
add(point: Point) {
return new Point(this.x + point.x, this.y + point.y);
}
}
var p1 = new Point(0, 10);
var p2 = new Point(10, 20);
var p3 = p1.add(p2); // {x:10,y:30}
Этот класс компилируется в следующий код для ES5:
var Point = (function () {
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.add = function (point) {
return new Point(this.x + point.x, this.y + point.y);
};
return Point;
})();
Это традиционная базовая модель класса в JavaScript.
Классы в TypeScript (как и в других языках) поддерживают одиночное наследование с помощью ключевого слова extends
:
class Point3D extends Point {
z: number;
constructor(x: number, y: number, z: number) {
super(x, y);
this.z = z;
}
add(point: Point3D) {
var point2D = super.add(point);
return new Point3D(point2D.x, point2D.y, this.z + point.z);
}
}
Если в вашем классе есть конструктор, то вы должны вызвать конструктор класса-родителя из конструктора класса-наследника (TypeScript укажет на это). Это гарантирует, что все члены класса, которые должны быть добавлены в this
, будут добавлены корректно. После вызова super
вы можете добавить любые дополнительные члены, которые необходимы в конструкторе (в примере мы добавляем новый член z
).
Обратите внимание, что вы легко переопределяете члены класса-родителя (в примере мы переопределяем add
) и все еще используете функциональность класса-родителя (использование super.
).
Классы в TypeScript поддерживают static
свойства, которые являются общими для всех экземпляров класса. Естественное место для их размещения (и доступа к ним) - это сам класс:
class Something {
static instances = 0;
constructor() {
Something.instances++;
}
}
var s1 = new Something();
var s2 = new Something();
console.log(Something.instances); // 2
Вы можете использовать как static
члены, так и static
методы.
TypeScript поддерживает модификаторы доступа public
,private
и protected
, которые определяют доступность членов класса:
доступ к | public |
protected |
private |
---|---|---|---|
класс | да | да | да |
дочерний класс | да | да | нет |
экземпляр класса | да | нет | нет |
Если модификатор доступа не опеределен, то он неявно интерпретируется как public
, поскольку это соответствует природе JavaScript 🌹.
Обратите внимание, что во время выполнения (в сгенерированном JS) это не имеет значения, но во время компиляции вы получите ошибки при некорректном использовании модификаторов доступа. Как показано в примере ниже для каждого модификатора:
class FooBase {
public x: number;
private y: number;
protected z: number;
}
// для экземпляра класса
var foo = new FooBase();
foo.x; // Ок
foo.y; // ошибка : private
foo.z; // ошибка : protected
// для класса-наследника
class FooChild extends FooBase {
constructor() {
super();
this.x; // Ок
this.y; // ошибка: private
this.z; // Ок
}
}
Как обычно, эти модификаторы работают и для члена класса, и для метода.
abstract
можно рассматривать как модификатор доступа. Мы рассматриваем его отдельно, потому что в отличие от предыдущих модификаторов он может использоваться как для class
, так и для любого члена класса. Наличие abstract
модификатора в первую очередь означает, что данная функциональность не может быть вызвана напрямую, класс-наследник должен обеспечивать функциональность.
- Экземпляр
abstract
класса не может быть создан напрямую. Вместо этого должен быть создан какой-либоclass
, который наследуется отabstract class
. abstract
члены класса недоступны напрямую и функциональность предоставляется только через класс-наследник.
Конструктор для класса не обязателен. Пример ниже прекрасно работает:
class Foo {}
var foo = new Foo();
Добавление членов класса и инициализация, как на примере ниже:
class Foo {
x: number;
constructor(x:number) {
this.x = x;
}
}
это настолько общий паттерн, что TypeScript предоставляет сокращенный вариант, в котором достаточно добавить модификатор доступа перед членом класса и он автоматически будет проинициализирован и скопирован из конструктора. Поэтому предыдущий пример может быть переписан как:
class Foo {
constructor(public x:number) {
}
}
Это прекрасная функция, поддерживаемая TypeScript (из ES7). Вы можете инициализировать любой член класса вне конструктора, обычно с дефолтным значением.
class Foo {
members = []; // инициализация
add(x) {
this.members.push(x);
}
}