Альтернативы JavaScript. Часть 2

Ранее мы рассказывали о языке программирования TypeScript, созданном Microsoft в качестве замены JavaScript.

В этой статье расскажем вам об еще одной альтернативе JS — языке программирования Dart, созданного Google.

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 для сопровождения технологии.

На странице проекта мы сразу можем видеть призыв – Download Dart+Editor. Воспользуемся этим предложением и получим, скачав и распаковав архив, много интересных вещей.

Прежде всего это виртуальная машина 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» в консоли редактора, Dart работает (об особенностях самого языка – чуть ниже).

Того же результата мы можем достичь, сохранив вышеприведенный код в файле 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 откроется браузер, и мы сможем увидеть нашу программу в работе:

Теперь обратим внимание на строчку

<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);
}

Пример простого класса и одного наследника. Тут есть, на что обратить внимание.

Dart поддерживает короткий синтаксис обновления функций и методов.

Закрытые члены обозначаются добавлением знака подчеркивания _ в начало имени (в 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. Одними из значимых являются dart:html – библиотека для работы с DOM-моделью браузера и dart:io, содержащая классы и функции для доступа к файловой системе, сетевым сокетам, для работы с протоколом HTTP и веб-сокетами.

Изоляторы

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

В Dart единицей работы является изолятор (isolate), а не процесс или поток. У каждого изолятора имеется собственная область памяти (этим изолятор в основном и отличается от потока), недоступная любому другому изолятору. Изоляторы могут работать параллельно и обмениваться сообщениями (сигналами и данными).

Механизм изоляторов доступен и на веб-странице – каждый скрипт, содержащий функцию main(), исполняется в отдельном изоляторе. При трансляции в JavaScript такие конструкции будут реализованы посредством технологий HTML5 (WebWorkers).

Программа на Dart может создать новый изолятор (по аналогии с fork()). Для создания нового изолятора с указанной точкой входа необходимо импортировать библиотеку dart:isolate и вызвать функцию spawnFunction(), передав ей имя точки входа:

import "dart:isolate";
void main() {
    ...
    analyzeFileList(runSript);
}

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

Здесь даже вкратце не перечислены все особенности новой технологии Google, но, мы уверены, вы можете оценить масштаб инноваций. Dart явно претендует на место универсального и всеобъемлющего языка веб-программирования. Получится ли у него его занять? Вопрос сложный. Но старт технологии очень впечатляет.

А в общем-то…

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

Материал взят из выпуска №146-147 журнала «Системный Администратор»