«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", "baeldung");

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

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

Выполнение этого фрагмента кода дает: «Здравствуйте, baeldung baeldung baeldung».

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

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

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

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

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

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

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

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. Locations

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

Используя эту функцию, мы можем получить объект, совместимый с ожиданиями библиотек 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 JavaScript. В приведенных здесь примерах используются строковые литералы сценариев, но для реальных сценариев вы, скорее всего, захотите хранить свои сценарии в отдельных файлах и загружать их с помощью класса Reader.

Как всегда, весь код в этой статье доступен на GitHub.