1. 序章

この記事は、 Nashorn – Java8以降のJVM用の新しいデフォルトのJavaScriptエンジンに焦点を当てています。

Nashorn を、その前身である Rhino、よりも桁違いに高性能にするために、多くの高度な技術が使用されてきたため、変更する価値があります。

それを使用できるいくつかの方法を見てみましょう。

2. コマンドライン

JDK 1.8には、 jjs と呼ばれるコマンドラインインタープリターが含まれています。これを使用してJavaScriptファイルを実行したり、引数なしで開始した場合はREPL(対話型シェル)として使用したりできます。

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

ここで、ファイル hello.js には、次の1つの命令が含まれています。 print( “Hello World”);

同じコードをインタラクティブな方法で実行できます。

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

#!$ JAVA_HOME / bin / jjs を最初の行として追加することにより、ターゲットスクリプトの実行にjjsを使用するように*nixランタイムに指示することもできます。

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

そして、ファイルは通常どおりに実行できます。

$ ./hello.js
Hello World

3. 組み込みスクリプトエンジン

JVM内からJavaScriptを実行する2番目の、おそらくより一般的な方法は、ScriptEngineを使用することです。JSR-223は、スクリプトAPIのセットを定義し、任意の動的言語に使用できるプラグ可能なスクリプトエンジンアーキテクチャを可能にします。 (もちろん、JVM実装がある場合)。

JavaScriptエンジンを作成しましょう:

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

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

ここでは、新しい ScriptEngineManager を作成し、すぐにnashornという名前のScriptEngineを提供するように依頼します。 次に、いくつかの命令を渡して、予想どおり、 String hello world“である結果を取得します。

4. スクリプトへのデータの受け渡し

Bindings オブジェクトを定義し、それを2番目のパラメーターとして 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);

このスニペットを実行すると、「 Hello baeldung baeldungbaeldung」が生成されます。

5. JavaScript関数の呼び出し

もちろん、JavaコードからJavaスクリプト関数を呼び出すこともできます。

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

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

これにより、「Hellobaeldung」が返されます。

6. Javaオブジェクトの使用

JVMで実行しているため、JavaScriptコード内からネイティブJavaオブジェクトを使用できます。

これは、Javaオブジェクトを使用して実現されます。

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

7. 言語拡張

NashornはECMAScript5.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の拡張

NashornStringプロトタイプで提供する2つの単純ですが非常に便利な拡張機能があります。 これらはtrimRightおよびtrimLeft関数であり、当然のことながら、空白が削除されたStringのコピーを返します。

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

「helloworld」を先頭または末尾のスペースなしで2回印刷します。

7.10. Java.asJSONCompatible関数

この関数を使用すると、JavaJSONライブラリの期待値と互換性のあるオブジェクトを取得できます。

つまり、それ自体、またはそれを介して一時的に到達可能なオブジェクトがJavaScript配列である場合、そのようなオブジェクトは、配列要素を公開するためのListインターフェイスも実装するJSObjectとして公開されます。 。

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. スクリプトの読み込み

ScriptEngine内から別のJavaScriptファイルをロードすることも可能です。

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

ここでは、という名前のオブジェクトを定義しています算数それはと呼ばれる単一の関数を持っていますインクリメント。 このパラダイムを使用して、基本的なモジュール性をエミュレートすることもできます。

8. 結論

この記事では、 Nashorn JavaScriptエンジンのいくつかの機能について説明しました。 ここで紹介する例では、文字列リテラルスクリプトを使用しましたが、実際のシナリオでは、スクリプトを別々のファイルに保持し、Readerクラスを使用してロードすることをお勧めします。

いつものように、この記事のコードはすべてGitHub利用できます。