Introduction to Nashorn – 纳什恩简介

最后修改: 2017年 1月 7日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

This article is focused on Nashorn – the new default JavaScript engine for the JVM as of Java 8.

本文主要讨论Nashorn – 从Java 8开始,JVM的新默认JavaScript引擎。

Many sophisticated techniques have been used to make Nashorn orders of magnitude more performant than its predecessor called Rhino, so it is a worthwhile change.

许多复杂的技术被用来使Nashorn的性能比其前身Rhino高出几个数量级,所以这是一个值得的改变。

Let’s have a look at some of the ways in which it can be used.

让我们来看看它的一些使用方法。

2. Command Line

2.命令行

JDK 1.8 includes a command line interpreter called jjs which can be used to run JavaScript files or, if started with no arguments, as a REPL (interactive shell):

JDK 1.8包括一个名为jjs的命令行解释器,它可以用来运行JavaScript文件,或者,如果在没有参数的情况下启动,可以作为一个REPL(交互式Shell)。

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

Here the file hello.js contains a single instruction: print(“Hello World”);

这里的文件hello.js包含一条指令。print(“Hello World”);

The same code can be run in the interactive manner:

同样的代码可以以互动方式运行。

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

You can also instruct the *nix runtime to use jjs for running a target script by adding a #!$JAVA_HOME/bin/jjs as the first line:

你也可以通过添加#!$JAVA_HOME/bin/jjs 作为第一行来指示*nix运行时使用jjs来运行目标脚本。

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

And then the file can be run as normal:

然后就可以像平常一样运行该文件。

$ ./hello.js
Hello World

3. Embedded Script Engine

3.嵌入式脚本引擎

The second and probably more common way to run JavaScript from within the JVM is via the ScriptEngine. JSR-223 defines a set of scripting APIs, allowing for a pluggable script engine architecture that can be used for any dynamic language (provided it has a JVM implementation, of course).

第二种,也可能是更常见的从JVM中运行JavaScript的方式是通过ScriptEngine. JSR-223定义了一组脚本API,允许一个可插拔的脚本引擎架构,可用于任何动态语言(当然,只要它有一个JVM实现)。

Let’s create a JavaScript engine:

让我们来创建一个JavaScript引擎。

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

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

Here we create a new ScriptEngineManager and immediately ask it to give us a ScriptEngine named nashorn. Then, we pass a couple instructions and obtain the result which predictably, turns out to be a Stringhello world“.

在这里,我们创建了一个新的ScriptEngineManager,并立即要求它给我们一个ScriptEngine,命名为nashorn。然后,我们传递几个指令并获得结果,可以预见的是,结果是一个字符串hello world“。

4. Passing Data to the Script

4.将数据传递给脚本

Data can be passed into the engine by defining a Bindings object and passing it as a second parameter to the eval function:

数据可以通过定义一个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);

Running this snippet produces: “Hello baeldung baeldung baeldung“.

运行这个片段会产生。”Hello baeldung baeldung baeldung“。

5. Invoking JavaScript Functions

5.调用JavaScript函数

It’s, of course, possible to call JavaScript functions from your Java code:

当然,也可以从你的Java代码中调用JavaScript函数。

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

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

This will return “Hello baeldung“.

这将返回”Hello baeldung“。

6. Using Java Objects

6.使用Java对象

Since we are running in the JVM it is possible to use native Java objects from within JavaScript code.

由于我们是在JVM中运行,所以可以在JavaScript代码中使用本地的Java对象。

This is accomplished by using a Java object:

这是通过使用一个Java对象来实现的。

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

7. Language Extensions

7.语言扩展

Nashorn is targeting ECMAScript 5.1 but it does provide extensions to make JavaScript usage a tad nicer.

Nashorn的目标是ECMAScript 5.1,但它确实提供了一些扩展,使JavaScript的使用变得更加美好。

7.1. Iterating Collections With For-Each

7.1.用For-Each迭代集合

For-each is a convenient extension to make iteration over various collections easier:

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

Here, we join elements of an array by using for-each iteration construct.

这里,我们通过使用for-each迭代结构来连接数组中的元素。

The resulting output will be 1-2-3-4-5-.

结果输出将是1-2-3-4-5-

7.2. Function Literals

7.2.函数字样

In simple function declarations you can omit curly braces:

在简单的函数声明中,你可以省略大括号。

function increment(in) ++in

Obviously, this can only be done for simple, one-liner functions.

很明显,这只能用于简单的、单行的函数。

7.3. Conditional Catch Clauses

7.3.条件性捕获条款

It is possible to add guarded catch clauses that only execute if the specified condition is true:

可以添加有防护的catch子句,只在指定条件为真时执行。

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

This will print “String thrown: BOOM“.

这将打印”抛出的字符串:BOOM“。

7.4. Typed Arrays and Type Conversions

7.4.类型化数组和类型转换

It is possible to use Java typed arrays and to convert to and from JavaScript arrays:

可以使用Java类型的数组,也可以与JavaScript数组进行转换。

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

Nashorn performs some type conversions here to make sure that all the values from the dynamically typed JavaScript array can fit into the integer-only Java arrays.

Nashorn在这里进行了一些类型转换,以确保所有来自动态类型的JavaScript数组的值都能放入只含整数的Java数组中。

The result of calling above function with argument [100, “1654”, true] results in the output of 100, 1654 and 1 (all numbers).

用参数[100, “1654”, true] 调用上述函数的结果是输出100、1654和1(都是数字)。

The String and boolean values were implicitly converted to their logical integer counterparts.

字符串和布尔值被隐含地转换为其逻辑整数对应物。

7.5. Setting Object’s Prototype With Object.setPrototypeOf

7.5.用Object.setPrototypeOf设置对象的原型

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

Nashorn 定义了一个API扩展,使我们能够改变一个对象的原型。

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.

这个函数通常被认为是替代Object.prototype.__proto__的更好方法,所以它应该是所有新代码中设置对象原型的首选方法。

7.6. Magical __noSuchProperty__ and __noSuchMethod__

7.6.神奇的__noSuchProperty____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:

可以在一个对象上定义方法,只要访问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()

This will print:

这将打印。

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

7.7. Bind Object Properties With Object.bindProperties

7.7.用Object.bindProperties绑定对象属性

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

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

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

7.8.地点

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

当前的文件名、目录和一行可以从全局变量__FILE__, __DIR__, __LINE__获得:

print(__FILE__, __LINE__, __DIR__)

7.9. Extensions to String.prototype

7.9.对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:

NashornString原型上提供了两个简单但非常有用的扩展。它们是trimRighttrimLeft函数,不出意料地,它们会返回一个去除空白的String的副本。

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

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

将打印 “hello world “两次,没有前导或尾部空格。

7.10. Java.asJSONCompatible Function

7.10.Java.asJSONCompatible 功能

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

使用这个函数,我们可以获得一个与Java JSON库预期兼容的对象。

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.

也就是说,如果它本身或任何可通过它到达的对象是一个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()));

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

这将打印”hello“,后面是[2, 3, 5, 7, 11, 13],然后是true.

8. Loading Scripts

8.加载脚本

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

也可以从ScriptEngine内加载另一个JavaScript文件。

load('classpath:script.js')

A script can also be loaded from a URL:

脚本也可以从一个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:

请记住,JavaScript没有命名空间的概念,所以所有的东西都被堆积到全局范围。这使得加载的脚本有可能与你的代码或彼此之间产生命名冲突。这可以通过使用loadWithNewGlobal函数来缓解。

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

With the following math_module.js:

用下面的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!

这里我们定义了一个名为math的对象,它有一个名为increment的单一函数。使用这种范式,我们甚至可以模仿基本的模块化

8. Conclusion

8.结论

This article explored some features of the Nashorn JavaScript engine. Examples showcased here used string literal scripts, but for real-life scenarios, you most likely want to keep your script in separate files and load them using a Reader class.

本文探讨了Nashorn JavaScript引擎的一些功能。这里展示的例子使用了字符串字面的脚本,但在现实生活中,你很可能希望将你的脚本放在独立的文件中,并使用Reader 类加载它们。

As always, the code in this write-up is all available over on GitHub.

一如既往,本篇文章中的代码都可以在GitHub上找到