Рубрика:
Разработка /
Инструменты
|
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|
КИРИЛЛ СУХОВ, веб-программист в дистрибьюторской компании MICS. Занимается проектированием и разработкой различных интернет-сервисов. Круг интересов: веб-технологии, RIA, Framework-среды, sukhov-kirill@yandex.ru
Альтернативы JavaScript от Microsoft и Google
Надоели «сюрпризы» JavaScript? Не понимаете, как можно жить без классов? Не приемлете идею прототипированного наследования, и вас просто трясет от отсутствия строгой типизации? А с другой стороны, без JavaScript сейчас ведь просто не обойтись... Беда. Но выход есть! Даже несколько!
Общеизвестный факт – чистый JavaScript вызывает претензии у многих разработчиков. Особенно много нареканий исходит от программистов, привыкших к языкам с классической объектной моделью, но, кроме непривычного ООП, есть и другие замечания, часть из которых, как это ни грустно, вполне обоснована. Попытки улучшить JavaScript или придумать что-нибудь ему на замену предпринимались довольно давно. Первым успехом такого рода стал CoffeScript – технология, о которой наш журнал уже писал. Гиганты информационной индустрии Microsoft и Google предлагают свои варианты выхода из ситуации. И это мы сегодня и рассмотрим.
TypeScript – типа JavaScript от Microsoft
TypeScript – язык, позиционируемый как средство разработки веб-приложений, расширяющее возможности JavaScript. Он был представлен корпорацией Microsoft в 2012 году. Примечательна личность разработчика – это Андерс Хейлсберг (Anders Hejlsberg), принимавший деятельное участие в создании Turbo Pascal, Delphi и C#. В отличие от CoffeeSript, TypeScript является обратносовместимым сJavaScript. Он компилируется в JavaScript, после чего программу на TypeScript можно выполнять в любом современном браузере. Как и CoffeeSript, его вполне можно использовать совместно сплатформой 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)); //6
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), arroy-синтаксисом из EcmaScript 6, типом данных Generic. Очень динамично развивающаяся технология, поддерживаемая технологическим гигантом, – даже если вы сторонник чистого JavaScript, не стоит обделять вниманием этот проект.
Dart – дротик в спину JavaScript от Google
Естественно, «корпорация добра», разработчик движка V8 и одного из самых популярных браузеров Google Chrome, не могла пройти мимо тенденции «улучшения» JavaScript, и 12 сентября 2011 года наконференции разработчиков Goto была проведена официальная презентация языка Google Dart.
В отличие от аналогов Dart прямо позиционируется в качестве замены JavaScript, «страдающего от фундаментальных изъянов, которые невозможно исправить путем эволюционного развития».
Разработчики языка поставили перед собой следующие задачи: создать структурированный и в то же время гибкий язык для веб-программирования; сделать язык похожим на существующие дляупрощения обучения; обеспечить высокую производительность получаемых программ как в браузерах, так и в иных окружениях начиная от смартфонов и заканчивая серверами.
В настоящее время Dart-программы исполняются двумя способами: с использованием виртуальной машины (в браузере Google Chrome) и с промежуточной трансляцией (компиляцией) в JavaScript.
Самое интересное, что Dart претендует на то, чтобы стать прямым конкурентом платформы Node.js – виртуальная машина Dart (VM Dart) является неразрывной частью языка. С помощью нее можно запускать Dart-программы в консольном режиме, и средства для работы на стороне сервера в Dart имеются в достаточном количестве.
Экосистема Dart
Давайте сначала рассмотрим, что собой представляет этот язык. Для этого имеет смысл познакомиться со специализированными инструментами, созданными Google для сопровождения технологии.
На странице проекта (см. рис. 1) мы сразу можем видеть призыв – Downlod Dart+Editor. Воспользуемся этим предложением и получим, скачав и распаковав архив, много интересных вещей.
Рисунок 1. Dart – официальный портал проекта
Прежде всего это виртуальная машина Dart (Dart VM) – серверная платформа, реализующая событийно-ориентированную, асинхронную модель исполнения, сходную с так хорошо уже знакомым нам принципом работы Node.js. Далее Dart Editor – полноценная интегрированная среда разработки (IDE), основанная на Eclipse. Dart Editor дополнен веб-браузером – специальной модификацией обозревателя Chromium со встроенным Dart (Dart VM) движком.
Наконец, нам теперь доступен набор Dart SDK, куда входят сам интерпретатор dart, утилита для трансляции dart-приложения в JavaScript-файл (dart2js), утилита для генерации документации (docgen) ипрочие полезные инструменты.
Давайте запустим редактор, создадим новый проект (иконка в левом верхнем углу основного окна IDE, следует выбрать тип comand-line application) и напишем нашу первую dart-программу. Ничего оригинального:
main() {
print("Hello Dart");
}
Теперь запустим ее на выполнение, кликнув по стрелке на верхней панели инструментов. Судя по появившейся строчке «Hello Dart» в консоли редактора (см. рис. 2), Dart работает (об особенностях самого языка – чуть ниже).
Рисунок 2. Работа в Dart Еditor
Того же результата мы можем достичь, сохранив вышеприведенный код в файле hello.dart и запустив его с помощью интерпретатора из командной строки:
$ dart hello.dart
Hello Dart
Теперь можно попробовать транслировать нашу программу в JavaScript:
$ dart2js hello/bin/hello.dart
Dart file (hello\bin\hello.dart) compiled to JavaScript: out.js
Сделано! Скрипт получился рабочим, но слабочитаемым, поэтому сгенерированные 14 Кб я тут приводить не буду.
Теперь стоит вспомнить, что Dart задуман в первую очередь как язык, исполняемый в браузере. Если мы создадим новый проект, при этом выбрав тип Wep Application, в качестве заготовки дляприложения мы получим следующий dart-код:
import 'dart:html';
void main() {
querySelector("#sample_text_id")
..text = "Click me!"
..onClick.listen(reverseText);
}
void reverseText(MouseEvent event) {
var text = querySelector("#sample_text_id").text;
var buffer = new StringBuffer();
for (int i = text.length - 1; i >= 0; i--) {
buffer.write(text[i]);
}
querySelector("#sample_text_id").text = buffer.toString();
}
Не будем его трогать, а обратим внимание, что IDE, кроме файла сценария, создала еще и html-шаблон. И его стоит рассмотреть:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Helloweb</title>
<script async type="application/dart" src="helloweb.dart"></script>
<script async src="packages/browser/dart.js"></script>
<link rel="stylesheet" href="helloweb.css">
</head>
<body>
<h1>Helloweb</h1>
<p>Hello world from Dart!</p>
<div id="sample_container_id">
<p id="sample_text_id">Click me!</p>
</div>
</body>
</html>
Тут сразу бросается в глаза наличие нового Internet Media Type – application/dart. Сам dart-сценарий подгружается и выполняется посредством тега <script> специфицированным данным типом. После запуска приложения в IDE откроется браузер, и мы сможем увидеть нашу программу в работе (см. рис. 3).
Рисунок 3. Dart веб-приложение
Теперь обратим внимание на строчку
<script async src="packages/browser/dart.js"></script>
Это обычный JavaScript-сценарий, который проверяет наличие в DOM браузера поддержки функции navigator.webkitStartDart() и в случае отсутствия таковой заменяет dart-сценарий JavaScript-файлом, сгенерированным dark2js. Так что получившаяся страница отработает и в Firefox (правда, не очень быстро).
Как работает Dart, мы разобрались, теперь давайте посмотрим, что собой представляет сам язык.
Знакомство с Dart
Еще раз подчеркнем, Dart – это не диалект JavaScript и не надстройка над ним, это совершенно самостоятельный язык, имеющий c расширением EcmaScript 262 только одно пересечение – сферу применения. Создавали его люди, похоже, знающие и любящие технологии C++ , Java и C# и не пожелавшие отказываться от своих привычек. И это здорово, но немного непривычно для традиционного клиент-сайт-веб-программирования. Вот полюбуйтесь на Hello World по Dart`ски:
main() {
var d = "Dart";
String w = "World";
print("Hello ${d} ${w}");
}
Прежде всего бросается в глаза использование функции main(). Она в Dart делает именно то, что и в C/C++, – служит точкой входа в исполняемую программу, с которой начинается ее работа. Когда сценарий исполняется на странице, она начинает исполняться немедленно после загрузки DOM-модели документа. JavaSript – язык с динамической типизацией, что здорово. В С++ или Java типизация строгая, статическая, что вообще замечательно. Но лучше всего с этим дела обстоят у Dart – типизация у него факультативная. Это обозначает, что при объявлении переменных можно обозначать типы, аможно этого не делать, все зависит только от задач:
var foo = "Hi"; // объявление без аннотации типа
String foo = "Hi"; // объявление с аннотацией типа String
final String foo = "Hi"; // объявление с аннотацией типа String и финализацией
Ключевое слово final используется в том случае, когда значение, с которым она инициализирована, не должно далее быть изменено. Аннотации типов могут встречаться также в объявлениях параметров функций и возвращаемых значений, а еще в определениях классов.
ООП – чтим традиции!
Создатели Dart не стали экспериментировать с ОПП. В Dart классы и объекты используются вполне традиционно, в рамках классической модели наследования:
class Rectangle {
var _id;
var name;
final num height,
width;
//конструктор (Короткий синтаксис)
Rectangle(num this.height, num this.width);
// метод
num area() {
return height * width;
}
// метод (Короткий синтаксис)
num perimeter() => 2*height + 2*width;
// акцессоры
get id => _id;
set id(val) => _id = val;
}
class Square extends Rectangle {
Square(num size) : super(size, size);
}
Пример простого класса и одного наследника. Тут есть, на что обратить внимание.
Dark поддерживает короткий синтаксис обновления функций и методов.
Закрытые члены обозначаются добавлением знака подчеркивания _ в начало имени (в Dart нет ключевых слов public, protected и private. Если имя начинается с подчеркивания (_), то это приватное свойство). Ключевое слово this применяется строго традиционным образом, ссылаясь на конкретный экземпляр класса.
Наследование поддерживается одному классу и нескольким интерфейсам.
Использование классов тоже вполне традиционно:
var myRect = new Rectangle(3,4); //создает новый экземпляр класса Rectangle
myRect.name = "Nice Rectangle"; // Присваиваем значение полю
print(myRect.perimeter());
Интересной особенностью классов в Dart является то, что каждый из них неявно подразумевает интерфейс, который можно имплементировать. То есть переписать его методы с собственной реализацией:
class Shape {
num length;
num perimeter() => length * 4;
}
class Square implements Shape {
num length;
num perimeter() => length * 4;
}
class Triangle implements Shape {
num length;
num perimeter() => length * 3;
}
class Rectangle implements Shape {
num length;
num width;
num perimeter() => length * 2+width*2;
}
num printPerimetr(Shape shape) {
print(shape.perimeter());
}
main() {
var square = new Square();
square.length = 4;
printPerimetr(square); // 16
var triangle = new Triangle();
triangle.length = 4;
printPerimetr(triangle); // 12
var rectangle = new Rectangle();
rectangle.length = 4;
rectangle.width = 6;
printPerimetr(rectangle); // 22
}
Тут мы имплементируем интерфейс класса тремя различными способами, но функция printPerimetr(), ожидающая типизированный параметр (Shape), отрабатывает успешно.
Есть в Dart и абстрактные классы, с помощью которых можно реализовывать фабричный конструктор объектов:
abstract class iHello {
String sayHello(String realise, String name);
factory iHello(realise) {
return new realise();
}
}
class RussianHello implements iHello {
sayHello(name) {
return "Привет $name";
}
}
void main() {
iHello myHello = new iHello(); //
var message = myHello.sayHello("Dart");
print(message); // Привет Dart
}
Область видимости и библиотеки
Функции и классы можно организовать в библиотеки – совокупность исходных файлов, сгруппированных исходя из логических соображений. Каждая библиотека может содержать несколько классов ифункций верхнего уровня. Библиотеки могут импортировать другие, необходимые для работы файлы и библиотеки. Пример простой библиотеки приведен ниже:
library animals;
part "paint.dart";
class Dog {
noise() => paint('BARK!');
}
И ее использование:
import 'animals.dart';
var fido = new Dog();
import 'animals.dart' as pets;
var fido = new pets.Dog();
Ключевое слово library служит для определения библиотеки. Импортируется библиотека с помощью ключевого слова import, а part используется для ссылки на другие исходные файлы. Именно на уровне библиотеки имеют значения закрытые члены класса. Клиенты библиотеки не могут обращаться непосредственно к ним, вместо этого используются аксессоры. Наверное, не будет сюрпризом тот факт, чтобиблиотек для Dart уже написано великое множество, и основные из них поставляются вместе с языком как часть Dart SDK. Их мы можем наблюдать (и, естественно, использовать) в IDE Dart Editor (см. рис. 4). Одними из значимых являются dart:html – библиотека для работы с DOM-моделью браузера и dart:io, содержащая классы и функции для доступа к файловой системе, сетевым сокетам, для работы с протоколом HTTP и веб-сокетами.
Рисунок 4. Библиотеки Dart
Изоляторы
Любое Dart-приложение работает в однопоточном режиме. Но современные браузеры, даже на мобильных платформах, работают на многоядерных процессорах. Для использования всех ядер разработчики применяют работающие одновременно потоки с разделяемой памятью.
В Dart единицей работы является изолятор (isolate), а не процесс или поток. У каждого изолятора имеется собственная область памяти (этим изолятор в основном и отличается от потока), недоступная любому другому изолятору. Изоляторы могут работать параллельно, могут обмениваться сообщениями (сигналами и данными).
Механизм изоляторов доступен и на веб-странице – каждый скрипт, содержащий функцию main(), исполняется в отдельном изоляторе. При трансляции в JavaScript такие конструкции будут реализованы посредством технологий HTML5 (WebWorkers).
Программа на Dart может создать новый изолятор (по аналогии с fork()). Для создания нового изолятора с указанной точкой входа необходимо импортировать библиотеку dart:isolate и вызвать функцию spawnFunction(), передав ей имя точки входа:
import "dart:isolate";
void main() {
...
analyzeFileList(runSript);
}
Изоляторы хорошо использовать как для распределения логики между процессорами, так и для динамической загрузки внешнего кода. Код, не являющийся частью приложения, можно загружать иисполнять в изолированной области.
Я даже вкратце не перечислил всех особенностей новой технологии Google, но, я думаю, вы можете оценить масштаб инноваций. Dart явно претендует на место универсального и всеобъемлющего языка веб-программирования. Получится ли у него его занять? Вопрос сложный. Но старт технологии очень впечатляет.
А в общем-то...
Честно потратив время на исследование различных разработок около JavaScript и изложив результаты на этих страницах, не могу удержаться и не заметить, что сама идея заменить JavaScript не кажется мне обоснованной. Достаточно простой и вместе с тем мощный язык доказал свое право на существование, а некоторые претензии к нему действительно кажутся надуманными.
Facebook
Мой мир
Вконтакте
Одноклассники
Google+
|