Надоели «сюрпризы» JavaScript? Не понимаете, как можно жить без классов? Не приемлете идею прототипированного наследования, и вас просто трясет от отсутствия строгой типизации? А с другой стороны, без JavaScript сейчас ведь просто не обойтись… Беда. Но выход есть! Даже несколько!
Общеизвестный факт – чистый JavaScript вызывает претензии у многих разработчиков. Особенно много нареканий исходит от программистов, привыкших к языкам с классической объектной моделью, но, кроме непривычного ООП, есть и другие замечания, часть из которых, как это ни грустно, вполне обоснована. Попытки улучшить JavaScript или придумать что-нибудь ему на замену предпринимались довольно давно. Гиганты информационной индустрии Microsoft и Google предлагают свои варианты выхода из ситуации. Сегодня мы рассмотрим язык программирования TypeScript.
TypeScript – JavaScript от Microsoft
TypeScript – язык, позиционируемый как средство разработки веб-приложений, расширяющее возможности JavaScript. Он был представлен корпорацией Microsoft в 2012 году. Примечательна личность разработчика – Андерса Хейлсберга (Anders Hejlsberg), принимавшего деятельное участие в создании Turbo Pascal, Delphi и C#. В отличие от CoffeeSript, TypeScript является обратносовместимым с JavaScript. Он компилируется в JavaScript, после чего программу на TypeScript можно выполнять в любом современном браузере. Как и CoffeeSсript, его вполне можно использовать совместно с платформой Node.js.
Отличия TypeScript от JavaScript:
- возможность явного определения типов (статическая типизация);
- поддержка использования полноценных классов (как и традиционная объектно-ориентированная модель);
- поддержка подключения модулей.
Подобные нововведения призваны повысить скорость разработки, читаемость, рефакторинг и повторное использование кода, дать возможность осуществлять поиск ошибок на этапе разработки и компиляции, а также увеличить скорость выполнения программ. Хотя, если честно, последний абзац смотрится и вовсе как рекламный слоган. Давайте посмотрим, что там есть на самом деле. Хорошие новости – действительно, каждая программа JavaScript является корректной программой TypeScript. Более того, компилятор TypeScript выполняет только локальное преобразование файлов, не делает никаких переопределений переменных и не меняет их названия. Это позволяет писать код, максимально близкий к оригинальному JavaScript.
Node.js как TypeScript-компилятор
Ну, теперь за дело. TypeScript действительно нуждается в компиляции, и путей для нее ровно два – использовать Microsoft Visual Studio (от 2012) или соответствующий модуль платформы Node.js. Последним мы и воспользуемся для наших примеров. Сначала установим сам модуль:
npm install -g typescript
Пользоваться им просто. Создаем простой файл с TypeScript-кодом (hello.ts):
function greeter(person: string) {
return "Hello, " + person;
}
console.log(greeter("TypeScript"));
Теперь компиляция. Набираем в консоли:
$ tsc hello.ts
Результатом будет файл hello.js:
function greeter(person) {
return "Hello, " + person;
}
console.log(greeter("TypeScript"));
Не много различий, правда? Ну, теперь, когда у нас все работает, давайте разберемся с самим языком.
Аннотации типов
Что нам может предложить TypeScript? Прежде всего статическую типизацию, мы ее наблюдали в первом же примере:
function greeter(person: string){...}
Тип данных тут указан после имени аргумента. Если мы передадим функции неверный тип данных, при компиляции будет выдано предупреждение:
greeter(1);
Результат:
$ tsc hello.ts
gr.ts(4,13): error TS2082: Supplied parameters do not
match any signature of call target:
Could not apply type 'string' to argument 1 which is of type 'number'.
gr.ts(4,13): error TS2087: Could not select overload f
or 'call' expression.
Можно указать тип возвращаемого значения:
function process(x: number, y: number): number {
var sum = x + y;
return sum;
}
А можно вообще ничего не указывать – строгая типизация не является в TypeScript обязательной. Зато функции теперь можно задавать параметры по умолчанию и указывать необязательные аргументы:
function process(x =5, y?: number): number {
var sum;
if(!y){
sum = x;
} else{
sum = x + y;
}
return sum;
}
console.log(process(2,6)); //8
console.log(process(2)); //2
console.log(process()); //5
Результаты компиляции:
function process(x, y) {
if (typeof x === "undefined") { x = 5; }
var sum;
if (!y) {
sum = x;
} else {
sum = x + y;
}
return sum;
}
console.log(process(2, 6)); //8
console.log(process(2)); //2
console.log(process()); //5
Больше всего тут приятно, что результирующий код на JavaScript получается простым и понятным. Правда, это только на простых конструкциях, а нам пора переходить к более сложным.
Классы! Настоящие классы!
Готовы поспорить, ради того все и затевалось. Да, в TypeScript существуют «нормальные» классы и «нормальное» наследование. Пример TypeScript-класса:
class Person {
name: string;
surname: string;
private id: number;
static title = "Example";
constructor (name: string, surname: string) {
this.name = name;
this.surname = surname;
}
setID (id) { this.id = id; }
getFullName () { return this.name+" "+this.surname; }
}
console.log(Person.title+":"); // Example:
var User = new Person("Kirill", "Sukhov");
console.log(User.name); // Kirill
console.log(User.getFullName()); // Kirill Sukhov
Тут почти все, о чем мечтали сторонники «традиционного» ООП: поля, методы, конструктор. Имеются и модификаторы доступа – попытка получить значение User.id или установить его значение непосредственно, а не с помощью специальных методов класса Person, потерпит неудачу (модификатор public тоже есть, но его почти всегда можно опустить).
Статические поля и свойства также подчиняются привычным законам – то есть доступны без создания экземпляра класса.
При компиляции этого кода мы получим следующую, не очень сложную JavaScript-конструкцию:
var Person = (function () {
function Person(name, surname) {
this.name = name;
this.surname = surname;
}
Person.prototype.setID = function (id) {
this.id = id;
};
Person.prototype.getFullName = function () {
return this.name + " " + this.surname;
};
Person.title = "Example";
return Person;
})();
console.log(Person.title + ":"); // Example:
var User = new Person("Kirill", "Sukhov");
console.log(User.name);
console.log(User.getFullName());
Теперь наследование. Напишем еще один класс, расширяющий предыдущий:
class Member extends Person {
band: string;
constructor(name: string, surname: string, band: string){
super(name, surname);
this.band = band;
}
getBand () { return this.band; }
}
var User = new Member("John", "Lennon", "The Beatles");
console.log(User.getFullName()); // John Lennon
console.log(User.getBand()); // The Beatles
Мы добавили немного – название группы. В конструкторе мы методом super()
вызываем родительский конструктор.
Можно переопределить родительский метод:
class Member extends Person {
band: string;
constructor(name: string, surname: string, band: string){
super(name, surname);
this.band = band;
}
getBand () { return this.band; }
getFullName() {
return super.getFullName()+" From "+this.band;
}
}
var User = new Member("John", "Lennon", "The Beatles");
console.log(User.getFullName()); // John Lennon from The Beatles
Интерфейсы
Да, кроме классов, в TypeScript существуют и эти полезные языковые конструкции. Ниже пример простого интерфейса и использующей его функции:
interface Band {
name: string;
state?: string;
members: any;
}
function ShowBand(band: Band) {
console.log(band.name);
if(band.state){
console.log(band.state);
}
band.members.forEach( function(member){
console.log(member);
});
}
var obj = { name: "Focus",
state: "nl",
members: ["Thijs", "Jan", "Martin", "Hans"]
}
ShowBand(obj);
Тут интерфейс контролирует параметры объекта, передаваемого функции как аргумент. Знак вопроса после параметра id
указывает на его необязательность. При компиляции кода в JavaScript интерфейс исчезает, он свое дело сделал:
function ShowBand(band) {
console.log(band.name);
if (band.state) {
console.log(band.state);
}
band.members.forEach(function (member) {
console.log(member);
});
}
var obj = {
name: "Focus",
state: "nl",
members: ["Thijs", "Jan", "Martin", "Hans"]
};
ShowBand(obj);
Модули
Модули в TypeScript организованы по стандартам CommonJS и EcmaScript6. Их задача – инкапсуляция бизнес-логики в отдельной конструкции со своим пространством имен… Хотя, что объяснять, что такое модули в конце статьи про Node.js? Лучше покажу на практике, как они реализованы в TypeScript.
module Say {
export function Hello(text: string) {
return "Hello " + text;
}
}
console.log(Say.Hello("Module"));
Ничего не напоминает? Хотя что, собственно, напоминать, это не сходство, это тот же самый механизм, который мы используем, например, в Node.js. При компиляции получаем следующее:
var Say;
(function (Say) {
function Hello(text) {
return "Hello " + text;
}
Say.Hello = Hello;
})(Say || (Say = {}));
console.log(Say.Hello("Module"));
Что еще?
Кроме разобранных нами языковых конструкций, TypeScript еще много чем может порадовать разработчика. Например, реализацией примесей (mixins), array-синтаксисом из EcmaScript 6, типом данных Generic. Очень динамично развивающаяся технология, поддерживаемая технологическим гигантом – даже если вы сторонник чистого JavaScript, не стоит обделять вниманием этот проект.
В следующей статье расскажем вам об альтернативе JavaScript от Google — языке программирования Dart.
Материал взят из выпуска №146-147 журнала «Системный Администратор»