classes.md

May 10, 2020 · View on GitHub

Классы

Причина, по которой важно иметь классы в 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, которые определяют доступность членов класса:

доступ кpublicprotectedprivate
классдадада
дочерний классдаданет
экземпляр классаданетнет

Если модификатор доступа не опеределен, то он неявно интерпретируется как 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);
    }
}