Перейти к содержанию

Классы

Причина, по которой важно иметь классы в JavaScript:

  1. Классы предлагают полезную структурную абстракцию
  2. Предоставляют разработчикам согласованный единый способ использования классов вместо разных версий, предлагаемых фреймворками (emberjs, reactjs).
  3. Разработчики с опытом ООП уже понимают классы

На текущий момент 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.).

Static

Классы в 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

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);
    }
}