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
. Затем мы передаем пару инструкций и получаем результат, который, как и ожидалось, оказывается строкой
« hello world
».
4. Передача данных в скрипт
Данные можно передать в движок, определив объект Bindings
и передав его в качестве второго параметра функции eval
:
Bindings bindings = engine.createBindings();
bindings.put("count", 3);
bindings.put("name", "foreach");
String script = "var greeting='Hello ';" +
"for(var i=count;i>0;i--) { " +
"greeting+=name + ' '" +
"}" +
"greeting";
Object bindingsResult = engine.eval(script, bindings);
Запуск этого фрагмента выдает: « Hello foreach foreach foreach
».
5. Вызов функций JavaScript
Конечно, можно вызывать функции JavaScript из кода Java:
engine.eval("function composeGreeting(name) {" +
"return 'Hello ' + name" +
"}");
Invocable invocable = (Invocable) engine;
Object funcResult = invocable.invokeFunction("composeGreeting", "foreach");
Это вернет « Hello foreach
».
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
ориентируется на 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!");
}
Это напечатает « Строка брошена: БУМ
».
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 (все числа).
Строковые и логические
значения были неявно преобразованы в их логические целые аналоги.
7.5. Установка прототипа объекта с помощью Object.setPrototypeOf
Nashorn
определяет расширение API, которое позволяет нам изменять прототип объекта:
Object.setPrototypeOf(obj, newProto)
Эта функция обычно считается лучшей альтернативой Object.prototype.__proto__
, поэтому она должна быть предпочтительным способом установки прототипа объекта во всем новом коде.
7.6. Волшебные __noSuchProperty__
и __noSuchMethod__
Можно определить методы для объекта, которые будут вызываться всякий раз, когда осуществляется доступ к неопределенному
свойству или вызывается неопределенный метод:
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
, которые, что неудивительно, возвращают копию строки
с удаленным пробелом:
print(" hello world".trimLeft());
print("hello world ".trimRight());
Дважды напечатает «hello world» без начальных и конечных пробелов.
7.10. Java.asJSONСовместимая
функция
Используя эту функцию, мы можем получить объект, совместимый с ожиданиями библиотек Java JSON.
А именно, если он сам или любой объект, транзитивно доступный через него, является массивом JavaScript, то такие объекты будут представлены как JSObject
, который также реализует интерфейс List
для предоставления элементов массива.
Object obj = engine.eval("Java.asJSONCompatible(
{ number: 42, greet: 'hello', primes: [2,3,5,7,11,13] })");
Map<String, Object> map = (Map<String, Object>)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
, который имеет единственную функцию, называемую приращением.
Используя эту парадигму, мы можем даже эмулировать базовую модульность!
8. Заключение
В этой статье были рассмотрены некоторые особенности движка Nashorn J
avaScript. В продемонстрированных здесь примерах используются строковые литералы сценариев, но для реальных сценариев вы, скорее всего, захотите хранить свои сценарии в отдельных файлах и загружать их с помощью класса Reader .
Как всегда, весь код в этой статье доступен на GitHub .