TypeScript как будущее энтерпрайзного JavaScript. Часть 1
Не стану пересказывать тут историю появления JS, она прекрасно всем известна, и описывается одним словом — спешка. JavaScript задумывался и создавался в очень сжатые сроки. По словам создателя JS Brendan Eich, у них было лишь 10 дней на все. Microsoft наступал на пятки и, если бы они проиграли, то сейчас эта статья была бы частично посвящена VBScript (Visual Basic Script).
Статья разделена на две части. Первая часть описывает язык TypeScript — я попытаюсь разъяснить, каким образом множество новых концепций TS проецируются на JavaScript. Вторая часть будет посвящена процессу разработки и миграции существующего кода на TypeScript, а также планам развития языка.
Битва с ветряными мельницами
Браузерный мир выбрал JavaScript со всеми его недостатками, оговорками и поверхностной простотой. С тех пор утекло много воды, сам язык изменился, оброс дополнениями и удобствами, но основные проблемы остались, и избавиться от них почти невозможно, не сломав весь интернет.
Первые веб-сайты были очень просты и статичны. JavaScript использовался лишь в редких случаях для анимации или валидации форм перед отправкой. Проблема заключалась в том, что далеко не все браузеры поддерживали JavaScript. Когда же он стал стандартом для всех популярных браузеров, началась разработка приложений, которые делали в рамках веб-страницы нечто большее, чем анимация бегущей строки или проверка введенных значений на форме обратной связи. Приложения становились больше, и начали давать о себе знать проблемы JavaScript, связанные с типами данных, с отсутствием единого способа наследования объектов, с моделью памяти JS, а также многое другое.
Сам язык способствует написанию плохого кода. И прощает, по крайне мере вначале, написание явной лапши вместо кода. Динамическая природа языка просто подталкивает к написанию универсальных функций, которые могут принимать десятки вариантов аргументов (как по типу данных, так и по их количеству).
Рискую быть закиданным помидорами, так как покушаюсь на святое, но самый популярный пример — jQuery. Стоит хотя бы вспомнить возможные варианты главной функции-объекта jQuery. «Функция-объект» — чувствуете, как это звучит? Вы можете вызвать jQuery как функцию девятью (девятью, Карл!) различными способами — все зависит от аргументов. А еще, сама функция является объектом, в котором может быть неконтролируемое число методов и/или свойств.
Да, jQuery — это действительно швейцарский нож с огромным количеством возможностей по упрощению жизни рядовому веб-разработчику. Но часто на базе системы jQuery-плагинов создают целые приложения интернет-магазина с десятками форм и диалогов. И вот там уже начинается натуральный ад и «лапша-код». Это самый популярный пример. jQuery разрабатывался как средство удобного доступа к DOM, но в итоге получился комбайн и практически отдельный язык программирования, что и порождает целую отдельную вселенную безумия, где на каждый вопрос один ответ — плагин для jQuery.
Да, написано огромное количество фреймворков уровня приложения, которые помогают удержать всю мощь динамичной лапши в узких рамках предложенной/рекомендуемой/требуемой структуры файлов и папок. Только благодаря таким фреймворкам мы с вами еще не сошли с ума окончательно.
В корпорациях вроде Google технические специалисты очень быстро столкнулись с тем, что с ростом размеров JavaScript приложения практически с геометрической или даже экспоненциальной скоростью растут затраты на поддержку и исправление ошибок. В ответ на эту проблему Google выпустил GWT — Google Web Toolkit. Это компилятор, набор инструментов и базовых классов для разработки веб-приложений на Java. Стало возможным с минимальными оговорками писать веб-приложения на строго типизированном языке программирования с использованием большинства его плюшек. В качестве результата вы получаете приложение, написанное на, фактически, машинном JavaScript. Этот код невозможно отлаживать отдельно от GWT, это просто лишено смысла. В декабре прошлого года, после более чем года молчания, проект выпустил бету новой версии (2.8.0).
Стоит также заметить, что GWT чаще рассматривают как единственную возможность для джавистов, не владеющих JS, писать развесистый front-end без отрыва от любимого языка для back-end’а.
Других же не устраивал только синтаксис JavaScript, и они разрабатывали свои варианты языка программирования, которые с разной степенью прозрачности транслируются в JavaScript. Тут список примеров возглавляет, конечно же, CoffeScript. Подборка всего, что так или иначе транслируется в JavaScript, опубликована на github в wiki проекта CoffeScript.
Общий смысл сводится к тому, что вы можете писать front-end приложение почти на любом языке программирования, который вам нравится, важно лишь, чтобы была возможность сгенерировать JavaScript код на базе вашего исходного кода.
ES6
ES6 приходит к нам как набор дополнений к уже привычному стандарту ES5. Набор этих дополнений неточен, постоянно дополняется и переделывается в той или иной степени.
Конечно, ввиду того, что браузеров больше, чем один, и интерпретаторов JS, как минимум, столько же (даже несмотря на общие корни и/или основу у многих из них), каждый из них гнет свою линию и экспериментирует с синтаксисом, с трактованием предложений сообщества, или предлагает свои идеи.
Это своего рода продолжение войны браузеров, но уже в более вялой форме. Вот только она по-прежнему приводит к условиям проверки браузера или даже его версии в нашем коде, всевозможным «полифилам» (polifill), которые мы вынуждены подключать к нашим проектам, если хотим использовать какую-то «вкусность» из ES6, например, Promise или setImmediate. Но это история про браузерный API, а не про сам язык JavaScript.
Если же мы хотим использовать чуть более классические (в сравнении с другими языками) классы, генераторы, не хотим думать о контексте для колбека, то тут уже начинаются проблемы: одно дело — желания современного разработчика, который хочет писать современный код, и, совсем другое дело — реальность брузеров клиентов. Вот тут на помощь и приходят транспайлеры и компиляторы.
Проект Babel представляет из себя транспайлер из современного представления о правильном переводе ES6 или даже ES2015 в код, совместимый с ES5. То есть разработчик получает возможность писать на самой современной версии JavaScript и, в большинстве случаев, не беспокоиться о совместимости с браузерами, которые еще не включают поддержку ES6 в свои JS-движки (или не включают по умолчанию, так как все, что скрыто за специальными экспериментальными настройками, лучше считать выключенным и недоступным). Про проект и все его возможности вы можете прочитать на официальном сайте, а мы пойдем дальше.
Бардак и порядок
Хорошо, мы можем прозрачно использовать все вкусности современного JavaScript в своем коде и не беспокоиться о совместимости с браузером.
Когда команда разработки состоит из более чем одного человека, то возникает необходимость в соглашениях — по архитектуре, выбору внешних библиотек, фреймворка или списка фреймворков (мало ли?). Большинство из этих вопросов решается устно за пару митингов и уточняется устно (конечно же документируется, верно?) на последующих митингах.
С началом разработки появляются новые вопросы и трудности — иерархии классов нужно поддерживать здоровыми и четкими. Минимализм интерфейсов крупных компонентов нужно строго документировать и постоянно устраивать внимательное ревью кода, чтобы не пропустить момент, когда все пойдет вкривь и вкось. А это обязательно случится. В какой-то команде/проекте раньше, в какой-то позже, но это случится. И это будет похоже на притчу про лягушку в кастрюле с холодной водой на медленном огне. В чем может быть причина такого развития событий?
Тут я выскажу исключительно свое мнение, которое вы не должны воспринимать как чистую правду или аксиому. Но дело вот в чем: динамическая природа JS подталкивает к излишней гибкости, и это расхлябывает интерфейсы объектов и функций. Со временем ваши функции и конструкторы объектов начинают принимать все больше разновидностей аргументов, объектов разной степени целостности и такая гибкость в итоге приводит к росту числа фантомных багов, которые решаются подпорками то тут то там, что в свою очередь, еще сильнее повышает энтропию в вашем коде.
Единственное спасение от такого сценария — постоянное жестокое ревью каждого коммита/пул-реквеста, где каждое расширение интерфейса ставится под сомнение и отвергается, если задачу возможно решить уже существующими средствами. Чаще всего такой случай означает, что разработчик хочет передать данные в необычном формате, данные просто не подготовлены и разработчик хочет переложить ответственность за преобразование данных на класс получателя, а это уже архитектурное решение. Ну и постоянный рефакторинг и борьба с техническим долгом. Да, это противоречит большинству идей, что «Сначала доставить клиенту продукт, а потом наведем порядок» и, любимое многими, «Ну, ведь работает же?». Это все понятно, но крайности, — это всегда плохо, нужно искать баланс, когда вы сможете и продукт доставить вовремя, и не превратите процесс разработки и поддержки в ад для себя или других ваших товарищей. Ну или хотя бы отсрочите коллапс этого лапша-кода на максимальное время.
Согласно второму закону термодинамики, энтропия всегда возрастает, но в наших силах хотя бы замедлить этот процесс.
TypeScript — надстройка вокруг ES6, ES2015
Пришло время перейти к главной теме. TypeScript — это язык программирования, который является супер-сетом вокруг ES6, ES2015. Разработан в Microsoft и теперь развивается в содружестве с коммьюнити. Даже Google приложил руку в виде AtScript, был поглощен одной из прошлых версий TypeScript. Спросите, если хотите, подробности у Google.
Что из себя представляет супер-сет? Это надстройка вокруг основного языка. Таким образом, любой работающий JavaScript-код автоматически является валидным TypeScript-кодом.
Что нового привносит TypeScript:
— Статическая типизация и выведение типов;
— Необязательные аргументы для функций и значения по умолчанию;
— public, private, protect для свойств и методов классов;
— Геттеры и сеттеры для свойств «без головной боли»;
— Декораторы для всего*;
— Интерфейсы и абстрактные классы;
— Generics;
— Компилирование в ES5 или даже в ES3 (с оговорками).
И в том числе плюшки ES6:
— Arrow Functions;
— Классы с единым стилем наследования;
— async/await из ES7;
— Итераторы и Генераторы*;
— Многострочные строки с шаблонизацией и тоже «без головной боли» с плюсами и кавычками;
— Управление зависимостями, в том числе и их динамическая загрузка.
* - (цель компиляции — не ниже ES5) экспериментальная поддержка, имплементация может измениться.
Главное, что нужно усвоить при начале использования TypeScript — никакой магии компилятор не делает, и что бронежилет он на вас тоже не надевает. Вы по-прежнему можете писать низкокачественную лапшу, и компилятор это переварит, хоть и завалит вас предупреждениями.
Подавляющее большинство работы по защите вас от вас же компилятор производит именно в момент анализа типов данных и их взаимодействия. Вообще вся история про TS так или иначе сводится к сверке типов данных и интерфейсов объектов. Только после этого начинают работать преобразователи синтаксического сахара. Они чем-то напоминают простые макросы, которые по шаблону переделывают строки (конечно, это грубое сравнение, но оно близко к реальности).
Компилятор не будет против сложения числа со строкой или даже с объектом, он лишь выдаст предупреждение о том, что вы, объявляя тип, имели ввиду, скорее всего, что-то другое, и вам стоит обратить внимание на конкретно эту строку и проверить, все ли вы тут делаете осознанно.
Предупреждения — главное оружие компилятора. Через предупреждения, которые он выдает в консоли, он сообщает вам о своих «сомнениях» относительно качества кода — «Вы указали, что эта переменная имеет тип Строка, но вот тут вы используете её как Число, вы уверены?» (это очень вольный пересказ сообщения компилятора). Если вы уверены, и ваш выбор конкретно такой логики осознан, то вы можете такую строчную переменную дополнить приведением типа к числу (или воспользоваться, например, parseInt()
), явно сообщая компилятору, что ситуация под контролем.
Весь генерируемый JS-код в результате работы компилятора может быть легко читаемым нормальным JS-кодом, сохранившим имена переменных, объектов и свойств, ваши комментарии в нужных местах — все то, что даст вам возможность легко сопоставить TypeScript-код с JavaScript-кодом для лучшего понимания языка.
Это на самом деле удобно и практично, особенно в самом начале осваивания TS, его внедрения в существующий проект — вы можете писать TypeScript-код и тут же видеть получаемый JavaScript-код.
Предупреждения и ошибки компиляции
В большинстве случаев компилятор может скомпилировать код, даже с предупреждениями. Возможных ошибок, останавливающих компиляцию, довольно мало — например, некоторые синтаксические конструкции вроде декораторов невозможно скомпилировать в код, совместимый со стандартом ES3. Но это не означает, что вы не можете настроить жесточайшие правила для своего проекта.
Например, пакет компиляции TypeScript для grunt-ts вы можете настроить так, что он будет обрывать сборку даже при одном предупреждении от компилятора.
Такое толерантное отношение к предупреждениям у компилятора связано в первую очередь с тем, что очень часто TypeScript-код работает вместе с обычным JavaScript-кодом, и TypeScript-часть кода может не в полной мере описывать свою связь с JS-частью. Потому компилятор работает в условиях неполной картины мира и в предупреждениях сообщает как раз о таких острых для него краях этого мира.
Лишь в ближайшее время компилятор TypeScript научится использовать в своей работе JavaScript-код (то есть код в файлах .js) — это позволит расширить сферу его возможностей к анализу на jsDoc-комментарии, где часто можно встретить в том числе типы данных для переменных, аргументов и непосредственному выведению типов на основе кода.
Любой JavaScript — это валидный TypeScript. Так как это супер-сет вокруг JavaScript, это означает, что вы можете использовать все, что есть в JavaScript всех версий, и то, что привносит непосредственно TypeScript.
Статическая типизация и выведение типов
У вас есть базовый набор типов JavaScript с явными декларациями, плюс парочка дополнительных, вроде enum(множество) и tuple (тьюплы, схожие по концепции с таковыми в python).
Все типы данных, явно декларируемые для переменных и свойств классов, конечно же, нужны только для проверок на этапе компиляции, никакой проверки типов на этапе выполнения не вносится. Все декларирование типов удаляется.
Компилятор достаточно умен, чтобы не требовать повсеместного указания типов, значительную часть типов он способен вывести, исходя из вашего кода. Местами это удобно и позволяет писать лаконичный компактный код. Но иногда все же следует себя заставлять и явно описывать типы, повышая читаемость кода для других программистов, да и для вас из «завтра».
Например:
function sum(a, b: number){ return a + b; }
В примере выше нет необходимости описывать возвращаемый функцией результат. Компилятору будет понятно, что сложение двух чисел всегда предполагает в результате число.
Но если волей программной логики вы вынуждены реализовать функцию, чей результат по типу может быть очень разным (строка, логическое, объект, уж не знаю, как вам вообще такая идея пришла в голову, но да ладно), или размеры функции не позволяют, окинув её взглядом, понять, какой тип данных она вернет, то его лучше указать явно, даже если компилятор вас об этом не просит (он смог вывести тип результата).
Также TypeScript, как говорилось ранее, оставляет вам возможность использовать все богатства динамического языка и позволяет вводить особый тип данных any. Этот тип данных говорит компилятору буквально следующее: «В этой переменной может быть все что угодно».
Необязательные аргументы для функций и значения по умолчанию
Без предисловий, пример, который расскажет сразу все:
function F1( a, b: number, c: {name: string}, d: boolean, ...otherParams: string[]): void { .. } function F2( a, b?: number, c?: {name: string} | number | string): void { .. } function F3( a, b?: number, C = 10): number { .. }
Кроме объявления типа или структуры (что по сути является типом, но без имени) аргумента, в F1 используется конструкция для функции с неограниченным количеством аргументов (при вызове). Фокус состоит в том, что в переменную otherParams будут помещены все прочие (после четвертого) аргументы, с которыми будет вызвана функция. Конечно, для этого будет сгенерированы несколько строк JS-кода, которые любезно отделят эти аргументы из arguments в массив.
F2 описывает случай, когда в аргументах функции есть необязательные элементы — <имяПеременной>?. Это означает, что для этих параметров при вызове функции компилятор не будет напоминать в предупреждениях о несоответствии заголовка функции её вызову. Переменная c — мало того, что необязательная, так еще и описывает несколько вариантов своего типа. То есть эта переменная может быть универсальной по типу — компилятор проследит, чтобы только эти типы данных использовались при вызове функции с этим аргументом.
F3 показывает пример с аргументом, который имеет значение по умолчанию. Синтаксически здесь все просто, с точки зрения генерируемого JS-кода тоже — создается условие, проверяющее аргумент на существование, если аргумент не определен, ему присваивают это значение.
Как видите, все то, что раньше нам приходилось снова и снова повторять в своем JS коде, простейшие конструкции, в которые иногда все же закрадывались досадные опечатки, теперь можно не писать. Синтаксический сахар TS помогает сделать код понятней и наглядней.
Те из вас, кто знаком с языком Python, могут спросить, почему авторы не пошли дальше и не добавили передачу аргументов по имени, примерно вот так:
let x = F3(a = 1, c = 3);
Я, конечно же, выскажу только свое мнение, я даже не знаю, обсуждалась ли такая идея среди разработчиков языка. Но у этой идеи есть явная проблемная сторона — реализовать такой синтаксис возможно через оборачивание всех аргументов в объект. Но тогда такие функции будут потеряны для внешнего кода написанного на JS. Хотя, конечно, можно представить заглушку и на этот случай, но тогда генерируемый код станет существенно сложнее, да и будет сложнее сохранить прозрачность трансляции и увеличится риск коллизий в именах аргументов и полей в передаваемых структурах. Что если внешний JS код вызывает такую функцию и в первом аргументе передает объект в котором есть поля, чьи имена совпадают с именами аргументов функции? Вводить запрет на «первый сложный аргумент функции»? Это выглядит, как минимум, странно.
Public, private, protect для свойств и методов классов
Об этом пункте можно было бы рассказать в разделе «Без магии, или Контроль над ситуацией».
Конечно же, в объектной модели JavaScript в прототипах объектов не существует понятия доступности поля из потомка или потребителя. И TypeScript не добавляет его через хитрейшие сокрытия переменных в областях видимости. Все проще. Если конкретное поле или метод класса вы описали как private, но чуть позже пытаетесь обратится к нему извне, то компилятор скажет вам об этом. Но опять же, несмотря на предупреждение в консоли при компиляции, компилятор послушно сгенерирует обращение к этому полю объекта в JS-коде.
Геттеры и сеттеры для свойств «без головной боли»
Тут все просто — вы используете один синтаксис get name(){}, а TypeScript генерирует для вас способ определения таких свойств через стандартный Object.defineProperty. При этом вам доступен и вариант с вычисляемыми именами таких свойств.
Декораторы для всего (ES7)
Это экспериментальный функционал, доступный при включении опции —experimentalDecorators и при компиляции в JS версии не ниже ES5
Декораторы — это синтаксический сахар и паттерн одновременно.
В ранних версиях TS предлагалось использовать аннотации, но от этого отказались в пользу декораторов.
Аннотации — как это было (или могло быть)
Аннотации — это пометки на сущности. Выглядело это примерно вот так (в старых версиях TS и AtScript, откуда это и пришло):
function annotate(obj){...} // TS @annotate class A {...}
А теперь JavaScript:
// JS function A(){}; A.__annotations = [annotate(A)]; // или function A(){}; A.annotations = [annotate(A)];
То есть к объекту прикреплялось дополнительное свойство __annotations или annotations, которые можно было использовать по своему усмотрению. Вы заметили это «__annotations или annotations»? В этом и скрывалась проблема, различные имплементации допускали разные варианты, что вводило путаницу. Проблема усугублялась еще больше, если существовал внешний код, который мог использовать эти свойства, но не знал, какой именно вариант нужно искать в объекте, если вообще знал, что нужно что-то искать, в итоге применение такого сгенерированного кода в модуле написанном на JS, обрастало условиями проверок вида аннотирования, что не способствовало качеству кода.
Декораторы
Декораторы в свою очередь «декорируют» объект, оборачивают собой сущность и, если нужно, подменяют её.
Суть проще продемонстрировать на простом примере. Представим класс, который предоставляет доступ к некой админке и кратко выглядит вот так:
class AdminPage { isAuth: boolean = false; isRoot: boolean = false; @guest( { ifauth_redirect_to: 'home' }) public login(){..} @auth public fetch(order: string[]){..} @auth @rootUser public destroyUniverse(){..} }
Декораторы в этом примере описывают уровни доступа текущего пользователя к конкретным методам класса:
— Метод login доступен гостю, иначе перенаправить на страницу home;
— Метод fetch доступен только авторизованному пользователю;
— Возможность вызвать конец света методом destroyUniverse дана только авторизованному пользователю с правами рут-пользователя.
Такой стиль класса и использования декораторов должен вызвать чувство дежавю как минимум у питонистов и джавистов. Они привыкли к такому подходу.
Долго не задерживаясь, давайте посмотрим на имплементацию декоратора guest:
function guest(options: { ifauth_redirect_to: string | boolean } ={ ifauth_redirect_to: false }) { // place for any logic with options before return function generator return function(target: any, methodName: string, desc: PropertyDescriptor) { // 1 let origin = target[methodName]; target[methodName] = function() { if (this.isAuth && options.ifauth_redirect_to) { this.redirect(options.ifauth_redirect_to); } else { origin.apply(this, arguments); } } return target; } // 1 }
Итак, функция декоратора должна вернуть функцию, которая будет вызвана для объекта декорирования при создании объекта.
В случае декорирования метода класса функция-результат работы генератора декоратора получит прототип объекта, строчное имя метода и описание метода, если оно существует.
Как видно из кода, мы сохраняем оригинальный метод и заменяем его своим, в котором проверяем нужные поля экземпляра объекта и вызываем оригинальную функцию, если нужно.
Возможность ответить на вопрос «Как применять декораторы в реальной практике и зачем они вам нужны?» я оставлю вам. Эта концепция имеет очень мощную основу и может существенно повлиять на архитектуру вашего приложения.
Повторюсь, этот функционал носит в компиляторе экспериментальный характер, хоть и есть подозрение, что он останется именно в том виде, как доступен сейчас, хотя бы потому, что в таком же виде он уже много лет существует в python.
Интерфейсы и абстрактные классы
Ни интерфейсов, ни абстрактных классов в JavaScript нет, как и нет разумных аналогов их представления. Потому здесь речь опять про синтаксический сахар.
Приведу сразу объемный пример с комментариями, где я постарался использовать почти все:
interface C { abc: boolean; } interface D { dd: number[]; } abstract class B { // компилятор "не позволит" создать экземпляр этого класса constructor(public name: string) { // аргумент name в конструкторе описан так, // чтобы автоматически было создано свойство name // с публичным уровнем доступа } test(a, b) { return a === b; // для этой функции типы не нужны } abstract length(a, b: string): number // класс потомок обязан // имплементировать этот метод // или объявить его снова абстрактным (как и сам класс) } class A extends B implements C, D { static x: number = 5 private abc = true public dd = [10, 11] constructor() { super(“A”); // компилятор будет настаивать, чтобы в первой строке // конструктора вызвали конструктор предка } length(a, b: string): number { // обещанная имплементация return (a + b).length; } }
Использование интерфейсов в TypeScript откровенно привносит порядок в безумный мир динамического программирования на JavaScript. Конечно же, при компиляции от интерфейсов не остается и следа. Но пока ведется разработка, они помогают контролировать ситуацию, избегая дублирования свойств и методов, поддерживая соглашения между различными частями системы, предоставляя возможность IDE строить адекватные предположения и подсказки.
Еще пример (взятый из официальной документации):
interface IShape { color: string; } interface ISquare extends IShape { // интерфейсы наследуются простым объединением sideLength: number; } // создание переменной с пустым объектом, но с указанием его типа или типа структуры let square = <ISquare>; { }; square.color = "blue"; square.sideLength = 10; // попытка добавить новое свойство square.a = 1; // вызовет предупреждение -- этого свойства нет ни в одном // из интерфейсов.
В этом примере показан случай создания типа данных структуры без использования классов. Они не обязательны для таких случаев. Выгода от такого подхода в том, что вы явно просите компилятор помочь вам не ошибиться при работе с переменной. Конечно, на базе этих же описаний IDE сможет предложить вам достойный набор автодополнений и даже, возможно, рефакторинг.
А теперь, интерфейс для функции (взятый из официальной документации):
interface SearchFunc { (source: string, subString: string): boolean; } let mySearch: SearchFunc; mySearch = function(source: string, subString: string) { let result = source.search(subString); return result != -1; }
Зачем это нужно? Ну, например, вы можете описать интерфейс требуемого колбека. Если вам в функцию попытаются передать функцию с другим интерфейсом, компилятор предупредит об этом.
В этом разделе я привел лишь малую часть возможностей и особенностей интерфейсов, настоятельно рекомендую прочесть взятый из официальной документации, там еще много полезного.
Generics
Непростой функционал, хоть в базовом случае и работает очень просто. Пример из документации:
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>(); myGenericNumber.zeroValue = 0; myGenericNumber.add = function(x, y) { return x + y; };
Как и в случае с интерфейсами, лучше прочесть главу в документации для полного понимания. Если очень кратко: компилятор подменяет T в декларации класса на number и перезапускает анализ, будто у класса там везде number. Вот и все. Опять же, до JavaScript-кода вся эта магия (как же я старался избегать этого слова) не доходит, все проверяется/сверяется/выводится до трансляции в JS-код.
Компилирование в ES5 или даже в ES3 (с оговорками)
Тут все просто, компилятор имеет внутри себя заготовки различных конструкций, вроде наследования, в виде шаблонов или кусков синтаксического дерева (это не важно), для различных версий стандарта EcmaScript (из списка поддерживаемых).
Оговорки касаются экспериментальных конструкций вроде декораторов и того, что просто невозможно описать в конкретной версии EcmaScript. Например, ни декораторы, ни set/get для полей класса нельзя описать в стандарте ES3 — просто в этом стандарте нет нужных вызовов в API примитива Object.
Выбор цели компиляции, гарантии, что будут доступны в качестве цели и будущие версии EcmaScript, — это делает TypeScript чуть ли не серебряной пулей. Вы уже можете использовать все то, что придумано нового в синтаксисе JavaScript, использовать проверку типов, декларации интерфейсов, абстрактные классы, декораторы и т.д. И в момент, когда «бизнес решит», что браузеры клиентов уже готовы для ES6 или ES7, вы просто переключите компилятор на другую цель, и генерируемый код обретет новые конструкции, избавится от каких-то подпорок для обратной совместимости. Код потенциально даже может стать быстрее, так как вместо явных обходных путей будет использовать нативное API движка JS.
ES6 в TypeScript
Как было сказано в самом начале, TypeScript является супер-сетом вокруг JavaScript. Это значит, что он включает в себя все синтаксические конструкции, которые добавлены в JS в ES6. Я не вижу смысла освещать их здесь, это заметка все же про TypeScript.
Выводы
Синтаксический сахар и проверки множества параметров при компиляции — вот два ингредиента, которые существенно улучшают процесс разработки и страхуют разработчика от неожиданных звонков посреди ночи.
Внесите порядок и структуру в ваш код и ... (тут допишите свой вариант рекламного лозунга).
Конечно, компилятор не может защитить вас от архитектурных ошибок. Но может предупредить о возможных несоответствиях между задуманным (интерфейсом) и реализацией (классом, функцией, структурой). Типизация переменных и аргументов поможет быть уверенным, что вы правильно прочитали интерфейс и применяете сущности правильно, а не руководствуетесь лишь своим внутренним чутьем. Декораторы могут существенно повлиять на сам подход к оформлению классов и архитектуры в целом. Шаблоны классов (generics), абстрактные классы, помогут не допустить потерю контроля над архитектурой приложения. Ну и весь прочий синтаксический сахар облегчает жизнь и делает код более выразительным.
Во второй части расскажу о тонкостях процесса разработки на TS и миграции уже существующего кода, а также о планах развития TypeScript.