{{notification.text}}

MirGames

Многие разработчики уверены в том, что JavaScript — это язык, который совершенно не подходит для разработки больших проектов; что это медленный язык, опасный и в некоторой степени даже непредсказуемый.

Основными доводами являются:
1) Отсутствие статической типизации.
2) Отсутствие полноценного ООП (про то, что оно есть в JS часто никто не знает).
3) Отсутствие возможности проверки кода в compile-time.
4) Высокая сложность командной разработки, которая проистекает из уже озвученных доводов.

Доводы, как вы понимаете, приводили апологеты C# и Java. PHP разработчики уже многие годы вполне уживаются с этими «проблемами» в языке PHP и успешно создают крупные популярные сервисы.

Я разрабатывал приложение, объем JS кода в котором без учета сторонних библиотек составлял около мегабайта, и могу сказать, что это было не так сложно, но по историческим причинам и в силу часто невысокой квалификации разработчиков, которые используют этот язык, существует множество стереотипов, касающихся JavaScript.

Я не буду развенчивать и свергать, не буду рассказывать об ООП в JavaScript, о возможностях в качестве языка функционального программирования, о валидаторах и статических анализаторах, типа JSLint. Мысли о том, что, следуя принципам SOLID, мы в итоге приходим к JavaScript, я тоже оставлю для следующей статьи.

Вместо этого, я попробую рассказать о языке TypeScript — надмножестве языка JavaScript, предложенного и предоставленного Microsoft широкой общественности 28 сентября 2012 года, практически ровно через год после презентации языка Dart от Google, который также решает задачу «фатальных недостатков» JS. Кстати, не может не радовать тот факт, что в работе над TypeScript принимал участие Андерс Хейлсберг, возглавлявший ранее группы проектировщиков таких языков, как Delphi и C#, и я должен признать, что результаты его работы впечатляют.

Итак, что же нам предлагает Microsoft и Хейлсберг? Они предоставляют:
  1. Сам язык TypeScript и его спецификацию.
  2. Компилятор кода на TypeScript, написанный на нём же и выложенный в открытый доступ на CodePlex, с возможностью использования на любых платформах, где может быть запущен JavaScript.
  3. Инструментарий для работы с TS: плагины для Visual Studio, vim, sublime и emacs.
  4. Песочницу, где можно поиграться с языком, и посмотреть в режиме реального времени на результат преобразования кода в JS.

Во многом в Microsoft не стали изобретать велосипедов, и по сути TypeScript — это даже не столько расширение JavaScript, сколько расширение ещё не вышедшего ECMAScript 6.

Сам язык TypeScript упрощает жизнь разработчиков:
  1. Возможностью типизации. Разумеется по желанию. С выведением типов, и утиной типизацией
  2. Поддержкой лямбда-выражений.
  3. ООП в классическом понимании — с интерфейсами, классами, конструкторами, областями видимости и т.п.
  4. Поддержкой модулей — как в стиле CommonJS, так и AMD.

Кроме того, TypeScript полностью совместим с JS, т.е. любой JS код без проблем обрабатывается компилятором TypeScript.

Типизация
В TypeScript разработчик может добавить аннотации типов, которые будут учитываться при выведении типов и при проверке во время компиляции. Например,

function sum(a: number, b: number): number {
  return a + b;
}
var e = sum(1, 2); // тип переменной c будет выведен из типа результата функции
var d: number = sum(1, 2); // но если есть желание, то можно и явно задать тип переменной

// компилятор не даст скомпилировать этот код, так как функция alert
// ожидает на вход объекты string
alert(e);
e = "Hello world"; // и не даст присвоить переменной значение другого типа

// как и не позволит регистрировать переменные в глобальной области видимости,
// так что можно не опасаться того, что var будет пропущен по ошибке,
// как это было во время запуска проекта Tactoom
c = 1;

Также поддерживаются типизированные массивы

function sum(numbers: number[]) {
  var result = 0;
  for (var i = 0; i < numbers.length; i++) {
    result += numbers[i]; // numbers[i] здесь будет иметь тип number
  }
  return result;
}

// несмотря на то, что мы не указывали явный тип результата функции sum,
// он будет выведен автоматически, и переменная a будет иметь тип number
var a = sum([1, 2, 3]); 
var b = sum(["1", "2", "3"]); // ошибка

Конечно же, можно аннотировать переменное количество аргументов. Замечу, что помимо аннотации типов в TS также активно используется синтаксис из ECMAScript 6.

function sum(...numbers: number[]) {
  var result = 0;
  for (var i = 0; i < numbers.length; i++) {
    result += numbers[i];
  }
  return result;
}

alert(sum(1, 2, 3).toString()); 

Функции в JavaScript являются первоклассными сущностями и могут быть переданы в том числе и в качестве параметра, и таким образом можно задавать аннотации параметров-функций:

function fold(numbers: number[], callback: (item: number, item2: number) => number) {
  var result = 0;
  for (var i = 0; i < numbers.length; i++) {
    result = callback(result, numbers[i]);
  }
  return result;
}

alert(fold([1, 2, 3], function(item, item2) {
  return item + item2;
}).toString());

Тип переменных item и item2 также будет выведен автоматически, исходя из сигнатуры метода.

Часто используемой концепцией в JS являются также опциональные параметры, для их использования нужно лишь добавить знак вопроса к имени:

function doWork(successCallback: () => void, errorCallback?: (err) => void) {
 // некоторый код, вызывающий колбэки
}

doWork(() => console.log('Success!'))

Более того, TypeScript поддерживает возможность указать, что в качестве параметра мы ожидаем объект с определенными членами, и в аннотации можно указать обязательность присутствия этих членов.

function init(options: {
  debug?: bool;
  filePath: string;
  version?: number;
}) {
// ..
}

init({ filePath: "D:/temp" });

// никто не запрещает передавать посторонние свойства
init({ filePath: "D:/temp", another: "" });

// но все неопциональные параметры должны быть указаны
// на этой строчке компилятор выдаст ошибку
init({ another: "" });

Так можно использовать «утиную» типизацию в TypeScript. Вместо не очень красивой аннотации объекта непосредственно в аргументах, можно использовать интерфейс, о чем я расскажу далее.

Время от времени возникает ситуация, когда наш метод может принимать аргументы разных типов. В таких случаях TypeScript позволяет указать, что объект является функцией, имеющей перегрузки.

var complex: { (text: string): void; (number: number): number; };
complex = function(anyVar): any {
  // проверяем тип anyVar и выполняем какие-то действия
};

var a = complex(1); // тип переменной a - number
var b = complex("1"); // ошибка, так как void не может быть присвоен
complex({}); // ошибка, так как complex не может принимать на вход объект

Лямба-выражения
Каждый раз мои попытки писать в функциональном стиле заканчивались ужасом в глазах от многословности полученного кода. В самом деле, даже такой простой код

users
  .where(function(item) { return item.registrationDate >= someDate })
  .select(function(item) { return item.login })
  .forEach(function(item) { console.log(item); })

выглядит пугающе.

TypeScript предлагает упрощенный синтаксис для задания анонимных функций — лямбда-выражения, которые сейчас можно найти во многих языках программирования, даже в C++. В TypeScript же синтаксис лямбда-выражений очень похож на синтаксис, используемый в C#: <переменные> => <возвращаемое значение>, либо <переменные> => <тело функции>. Перепишем примеры в духе лямбда-выражений:

// определение функции fold
alert(fold([1, 2, 3], (item, item2) => item + item2).toString());

users
  .where(item => item.registrationDate >= someDate )
  .select(item => item.login )
  .forEach(item => console.log(item));

Код стал читаться проще.

Классическое ООП
Фактически, TypeScript не предлагает здесь ничего нового, он лишь реализует часть спецификации ECMAScript 6, добавляя поддержку классов, интерфейсов, свойств и так далее. Программист, привыкший к ООП в том виде, который реализован в C# или Java, быстро освоится с этими нововведениями. Думаю, что ему будет достаточно лишь взгляда на то, как описывается класс в TypeScript:

interface ILogger {
	log(text: string): void;
}

class LoggerBase {
  private _options: any;
  
  get options() {
	  return this._options;
  }
	
  set options(value: any) {
	  return this._options = value;
  }
}

class Logger extends LoggerBase implements ILogger {	
  // конструктор может принимать аргументы с модификаторами
  // видимости. В этом случае, будут автоматически
  // созданы поля, и проинициализированы в конструкторе
  constructor (private filePath: string) {
	  super();
  }
	
  public log(text: string) {
    Logger.logLine(this.filePath, text);
  }
  
  private static logLine(filePath: string, text: string) {
    // ..
  }
}

var logger: ILogger = new Logger("filePath");
logger.log("test");

Этот код будет преобразован в следующий JS код (в зависимости от настроек компилятора, TypeScript умеет генерировать как ECMAScript 3 совместимый код, так и ECMAScript 5):

var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
}
var LoggerBase = (function () {
    function LoggerBase() { }
    Object.defineProperty(LoggerBase.prototype, "options", {
        get: function () {
            return this._options;
        },
        set: function (value) {
            return this._options = value;
        },
        enumerable: true,
        configurable: true
    });
    return LoggerBase;
})();
var Logger = (function (_super) {
    __extends(Logger, _super);
    function Logger(filePath) {
        _super.call(this);
        this.filePath = filePath;
    }
    Logger.prototype.log = function (text) {
        Logger.logLine(this.filePath, text);
    };
    Logger.logLine = function logLine(filePath, text) {
    }
    return Logger;
})(LoggerBase);
var logger = new Logger("filePath");
logger.log("test");

Модули
Сегодня в мире наиболее распространенными подходами к реализации модульной системы являются CommonJS и AMD. TypeScript предоставляет абстрактный синтаксис, который в зависимости от настроек компилятора преобразуется либо к стилю CommonJS (по умолчанию), либо к AMD.

Пример модуля:

// file MyModule.ts
module MyModule {
  export function doSomething() {
    console.log('Hello!');
  }

  export class SomeClass {
  }
}

// file App.ts
import myModule = module("MyModule");
myModule.doSomething(); // выведет Hello! в консоль

// к сожалению, следующим образом использовать модули нельзя:
// import someClass = module("MyModule").SomeClass;
// хотя это вполне валидное поведвение в CommonJS, где достаточно часто
// можно встретить что-то вроде var myClass = require("myModule").Class;

Что интересно, в TypeScript модули делятся на внешние, загружаемые через CommonJS/AMD, и внутренние — по сути просто namespace. Внутренние модули могут быть разделены на части и объявлены в нескольких файлах, но такие модули не будут загружены автоматически, и нужно будет позаботиться об их загрузке самостоятельно, либо использовать опцию компилятора -out, чтобы собрать результат в один js файл.

Интеграция с существующими библиотеками
Глупо было бы делать расширение языка JS, не имея возможности интегрироваться с многочисленными популярными библиотеками на нём написанными. Помимо того, что JS код без изменений может быть использован в TypeScript, существует возможность добавить к сторонними библиотекам так называемые файлы деклараций ".d.ts", в которых описываются контракты существующих библиотек. Подобные файлы уже существуют для jQuery, WinRT, NodeJS, knockoutJS, и некоторых других.

Вот так выглядит кусочек декларации типов для jQuery:

/*
   The jQuery instance members
*/
interface JQuery {
   /****
    AJAX
   *****/
   ajaxComplete(handler: any): JQuery;
   ajaxError(handler: (evt: any, xhr: any, opts: any) => any): JQuery;
   ajaxSend(handler: (evt: any, xhr: any, opts: any) => any): JQuery;
   ajaxStart(handler: () => any): JQuery;
   ajaxStop(handler: () => any): JQuery;
   ajaxSuccess(handler: (evt: any, xml: any, opts: any) => any): JQuery;

   load(url: string, data?: any, complete?: any): JQuery;

   serialize(): string;
   serializeArray(): any[];

   /**********
    ATTRIBUTES
   ***********/
   addClass(classNames: string): JQuery;
   addClass(func: (index: any, currentClass: any) => JQuery);

   attr(attributeName: string): string;
   attr(attributeName: string, value: any): JQuery;
   attr(map: { [key: any]: any; }): JQuery;
   attr(attributeName: string, func: (index: any, attr: any) => any): JQuery;
...

Будущее TypeScript
Глядя на появление поддержки NodeJS в Windows Azure, активные работы над JS движком IE, и на позиционирование HTML5, как средства для разработки приложений для Windows 8 и Windows Phone, я считаю, что TypeScript будет и в дальнейшем активно развиваться и поддерживаться. А даже если и нет, то исходники открыты, и любой желающий может усовершенствовать его.

Кстати, TypeScript всё ещё находится в статусе «preview», и к полноценному релизу они обещают реализовать ещё более интересные вещи. Например, известные программистам C# и Java, генерики.

Заключение
В целом, язык TypeScript мне показался весьма удобным, а некоторые особенности, такие как интерфейсы, и утиная типизация полезны при разработке клиент-серверных приложение на JS, где сервер запускается под NodeJS, а клиент — в браузере. Эти особенности позволяют гарантировать целостность контрактов сообщений — если кто-то изменил формат сообщений на сервере, я сразу же узнаю, что и где мне нужно изменить к клиенте. Эти и другие фичи, а также поддержка со стороны IDE, защищают от глупых ошибок, и позволяют вести разработку безопаснее и быстрее.

Полезные ссылки
www.typescriptlang.org/ — официальный сайт языка.
go.microsoft.com/fwlink/?LinkId=267238 — спецификация языка в PDF.
www.typescriptlang.org/Playground/ — песочница, где можно поиграть с языком TypeScript, и посмотреть, как код TS в реальном времени отображается в JS.
10.10.12 05:41

Комментарии

11.10.12 02:04
Отредактировано: 27.10.14 02:00

Сначала подумал о том, какой крутой язык, а потом понял, что статью надо было назвать: «Возможности ActionScript для разработчиков». Нет, серьёзно, Flash просто в очередной раз показал, как обогнал браузеро-разрабочиков, как медленно тащится индустрия веба и вообще.

Самое печальное, что этот язык пока что не поддерживается браузерами нативно — идёт трансляция в JavaScript, а это значит, что массивы, пусть даже с метаинформацией о типах, всё равно будут такими же тормозными, как и раньше.

Мало того, язык-таки баян, т.к. уже есть haxe

11.10.12 15:09

Вроде говорили что и сам код читабельней стал, по сравнению с js. Так или не так?

13.10.12 20:22

Читабельнее он мог стать, только благодаря лямбда-выражениям. В остальном синтаксис JS практически не изменился. Более того, повсеместное указание типов для переменных даже ухудшают читабельность.

14.10.12 08:44

ActionScript обогнал JS в плане синтаксиса, но, к сожалению, не по скорости. Насколько помню, имплементация AS безумно медленная, если сравнивать с текущими версиями V8.

14.10.12 15:42

Пока не запилят плагины для свободных IDE.

14.10.12 20:32

Сорри, вместо 13 написал 17 в коде :-) Поправил.

14.10.12 21:06

Vim и Emacs — в какой-то степени свободные IDE)

({{comment.CreationDate | date:'dd.MM.yy HH:mm'}})
Отредактировано: {{comment.UpdatedDate | date:'dd.MM.yy HH:mm'}}