Введение в Нашорн

Введение в Нашорн

1. Вступление

Эта статья посвященаNashorn –, новому механизму JavaScript по умолчанию для JVM, начиная с Java 8.

Было использовано множество сложных методов, чтобы сделатьNashorn на порядки более производительным, чем его предшественник, названныйRhino,, так что это стоящее изменение.

Давайте посмотрим, как его можно использовать.

2. Командная строка

JDK 1.8 включает интерпретатор командной строки под названиемjjs, который можно использовать для запуска файлов JavaScript или, если запущен без аргументов, как REPL (интерактивная оболочка):

$ $JAVA_HOME/bin/jjs hello.js
Hello World

Здесь файлhello.js содержит единственную инструкцию:print(“Hello World”);

Тот же код может быть запущен в интерактивном режиме:

$ $JAVA_HOME/bin/jjs
jjs> print("Hello World")
Hello World

Вы также можете указать среде выполнения * nix использоватьjjs для запуска целевого скрипта, добавив#!$JAVA_HOME/bin/jjs в качестве первой строки:

#!$JAVA_HOME/bin/jjs
var greeting = "Hello World";
print(greeting);

И тогда файл можно запустить как обычно:

$ ./hello.js
Hello World

3. Встроенный движок сценариев

Второй и, вероятно, более распространенный способ запуска JavaScript из JVM - этоScriptEngine. JSR-223, который определяет набор API-интерфейсов сценариев, что позволяет создавать подключаемую архитектуру механизма сценариев, которая может использоваться для любого динамического языка (при условии, что конечно, есть реализация JVM).

Давайте создадим движок JavaScript:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");

Object result = engine.eval(
   "var greeting='hello world';" +
   "print(greeting);" +
   "greeting");

Здесь мы создаем новыйScriptEngineManager и сразу же просим его дать намScriptEngine с именемnashorn. Затем мы передаем пару инструкций и получаем результат, который предсказуемо оказываетсяString «hello world».

4. Передача данных в скрипт

Данные можно передать в движок, определив объектBindings и передав его в качестве второго параметра функцииeval:

Bindings bindings = engine.createBindings();
bindings.put("count", 3);
bindings.put("name", "example");

String script = "var greeting='Hello ';" +
  "for(var i=count;i>0;i--) { " +
  "greeting+=name + ' '" +
  "}" +
  "greeting";

Object bindingsResult = engine.eval(script, bindings);

Выполнение этого фрагмента дает: «Hello example example example».

5. Вызов функций JavaScript

Конечно, можно вызывать функции JavaScript из кода Java:

engine.eval("function composeGreeting(name) {" +
  "return 'Hello ' + name" +
  "}");
Invocable invocable = (Invocable) engine;

Object funcResult = invocable.invokeFunction("composeGreeting", "example");

Это вернет «Hello example».

6. Использование объектов Java

Поскольку мы работаем в JVM, можно использовать нативные объекты Java из кода JavaScript.

Это достигается с помощью объектаJava:

Object map = engine.eval("var HashMap = Java.type('java.util.HashMap');" +
  "var map = new HashMap();" +
  "map.put('hello', 'world');" +
  "map");

7. Языковые расширения

Nashorn is targeting ECMAScript 5.1, но он предоставляет расширения, которые делают использование JavaScript немного приятнее.

7.1. Итерация коллекций с помощью For-Each

For-each - удобное расширение, упрощающее итерацию по различным коллекциям:

String script = "var list = [1, 2, 3, 4, 5];" +
  "var result = '';" +
  "for each (var i in list) {" +
  "result+=i+'-';" +
  "};" +
  "print(result);";

engine.eval(script);

Здесь мы объединяем элементы массива с помощью итерационной конструкцииfor-each.

Результатом будет1-2-3-4-5-.

7.2. Функциональные литералы

В простых объявлениях функций вы можете опустить фигурные скобки:

function increment(in) ++in

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

7.3. Положения об условном вылове

Можно добавить защищенные предложения catch, которые выполняются, только если указанное условие истинно:

try {
    throw "BOOM";
} catch(e if typeof e === 'string') {
    print("String thrown: " + e);
} catch(e) {
    print("this shouldn't happen!");
}

Будет напечатано «String thrown: BOOM».

7.4. Типизированные массивы и преобразования типов

Можно использовать типизированные Java-массивы и конвертировать в и из массивов JavaScript:

function arrays(arr) {
    var javaIntArray = Java.to(arr, "int[]");
    print(javaIntArray[0]);
    print(javaIntArray[1]);
    print(javaIntArray[2]);
}

Nashorn выполняет здесь некоторые преобразования типов, чтобы убедиться, что все значения из динамически типизированного массива JavaScript могут уместиться в целочисленные массивы Java.

Результат вызова вышеуказанной функции с аргументом[100, “1654”, true] дает результат 100, 1654 и 1 (все числа).

String и логические значения были неявно преобразованы в их логические целочисленные аналоги.

7.5. Установка прототипа объекта с помощьюObject.setPrototypeOf

Nashorn определяет расширение API, которое позволяет нам изменять прототип объекта:

Object.setPrototypeOf(obj, newProto)

Эта функция обычно считается лучшей альтернативойObject.prototype.proto, поэтому она должна быть предпочтительным способом установки прототипа объекта во всем новом коде.

7.6. МагическиеnoSuchProperty иnoSuchMethod

Можно определить методы для объекта, которые будут вызываться всякий раз, когда осуществляется доступ к свойствуundefined или вызывается методundefined:

var demo = {
    __noSuchProperty__: function (propName) {
        print("Accessed non-existing property: " + propName);
    },

    __noSuchMethod__: function (methodName) {
        print("Invoked non-existing method: " + methodName);
    }
};

demo.doesNotExist;
demo.callNonExistingMethod()

Это напечатает:

Accessed non-existing property: doesNotExist
Invoked non-existing method: callNonExistingMethod

7.7. Привязать свойства объекта кObject.bindProperties

Object.bindProperties можно использовать для привязки свойств одного объекта к другому:

var first = {
    name: "Whiskey",
    age: 5
};

var second = {
    volume: 100
};

Object.bindProperties(first, second);

print(first.volume);

second.volume = 1000;
print(first.volume);

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

7.8. Места

Текущее имя файла, каталог и строку можно получить из глобальных переменныхFILE, DIR, LINE:

print(__FILE__, __LINE__, __DIR__)

7.9. Расширения String.prototype

Есть два простых, но очень полезных расширения, которыеNashorn предоставляет для прототипаString. Это функцииtrimRight иtrimLeft, которые, что неудивительно, возвращают копиюString с удаленными пробелами:

print("   hello world".trimLeft());
print("hello world     ".trimRight());

Напечатает «привет мир» дважды без пробелов.

7.10. Java.asJSONCompatible Функция

Используя эту функцию, мы можем получить объект, который совместим с ожиданиями библиотек Java JSON.

А именно, что если он сам или любой объект, транзитивно достижимый через него, является массивом JavaScript, то такие объекты будут представлены какJSObject, который также реализует интерфейсList для отображения элементов массива.

Object obj = engine.eval("Java.asJSONCompatible(
  { number: 42, greet: 'hello', primes: [2,3,5,7,11,13] })");
Map map = (Map)obj;

System.out.println(map.get("greet"));
System.out.println(map.get("primes"));
System.out.println(List.class.isAssignableFrom(map.get("primes").getClass()));

Будет напечатано «hello», за которым следует[2, 3, 5, 7, 11, 13], за которым следуетtrue..

8. Загрузка скриптов

Также можно загрузить другой файл JavaScript изScriptEngine:

load('classpath:script.js')

Скрипт также может быть загружен с URL:

load('/script.js')

Имейте в виду, что в JavaScript нет концепции пространств имен, поэтому все становится частью глобальной области. Это позволяет загруженным скриптам создавать конфликты имен с вашим кодом или друг с другом. Это можно уменьшить, используя функциюloadWithNewGlobal:

var math = loadWithNewGlobal('classpath:math_module.js')
math.increment(5);

Со следующимиmath_module.js:

var math = {
    increment: function(num) {
        return ++num;
    }
};

math;bai

Здесь мы определяем объект с именемmath, который имеет единственную функцию с именемincrement.. Используя эту парадигму, мы можем даже эмулировать базовую модульность!

8. Заключение

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

Как всегда, весь код в этой записи доступенover on Github.