Въведение в Nashorn

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здравей, свят “.

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 е удобно разширение за улесняване на итерациите в различни колекции:

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

Тук ние обединяваме елементи от масив, като използваме конструкция за всяка итерация.

Полученият резултат ще бъде 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!"); }

Това ще отпечата „ String хвърлен: 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 и boolean бяха имплицитно преобразувани в техните логически цели числа.

7.5. Задаване на прототип на обект с Object.setPrototypeOf

Nashorn defines an API extension that enables us to change the prototype of an object:

Object.setPrototypeOf(obj, newProto)

This function is generally considered a better alternative to Object.prototype.__proto__ so it should be the preferred way to set object's prototype in all new code.

7.6. Magical __noSuchProperty__ and __noSuchMethod__

It is possible to define methods on an object that will be invoked whenever an undefined property is accessed or an undefined method is invoked:

var demo = { __noSuchProperty__: function (propName) { print("Accessed non-existing property: " + propName); }, __noSuchMethod__: function (methodName) { print("Invoked non-existing method: " + methodName); } }; demo.doesNotExist; demo.callNonExistingMethod()

This will print:

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

7.7. Bind Object Properties With Object.bindProperties

Object.bindProperties can be used to bind properties from one object into another:

var first = { name: "Whiskey", age: 5 }; var second = { volume: 100 }; Object.bindProperties(first, second); print(first.volume); second.volume = 1000; print(first.volume);

Notice, that this creates is a “live” binding and any updates to the source object are also visible through the binding target.

7.8. Locations

Current file name, directory and a line can be obtained from global variables __FILE__, __DIR__, __LINE__:

print(__FILE__, __LINE__, __DIR__)

7.9. Extensions to String.prototype

There are two simple, but very useful extensions that Nashorn provides on the String prototype. These are trimRight and trimLeft functions which, unsurprisingly, return a copy of the String with the whitespace removed:

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

Will print “hello world” twice without leading or trailing spaces.

7.10. Java.asJSONCompatible Function

Using this function, we can obtain an object that is compatible with Java JSON libraries expectations.

Namely, that if it itself, or any object transitively reachable through it is a JavaScript array, then such objects will be exposed as JSObject that also implements the List interface for exposing the array elements.

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

This will print “hello” followed by [2, 3, 5, 7, 11, 13] followed by true.

8. Loading Scripts

It's also possible to load another JavaScript file from within the ScriptEngine:

load('classpath:script.js')

A script can also be loaded from a URL:

load('/script.js')

Keep in mind that JavaScript does not have a concept of namespaces so everything gets piled on into the global scope. This makes it possible for loaded scripts to create naming conflicts with your code or each other. This can be mitigated by using the loadWithNewGlobal function:

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

With the following math_module.js:

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

Here we are defining an object named math that has a single function called increment. Using this paradigm we can even emulate basic modularity!

8. Conclusion

Тази статия изследва някои функции на двигателя Nashorn J avaScript. Представените тук примери използват скриптове за литерални низове, но за реални сценарии най-вероятно искате да запазите скрипта си в отделни файлове и да ги заредите с клас Reader .

Както винаги, кодът в това описание е достъпен в GitHub.