Nashorn: JavaScript е направен страхотно в Java 8

Nashorn, произнасян „nass-horn“, е немски за „носорог“ и е едно от имената на животните за германски миноносец, използван през Втората световна война. Това е и името на заместващия - въведен с Java 8 - за стария, бавен Rhino JavaScript двигател. И Rhino, и Nashorn са изпълнения на езика JavaScript, написан за работа на Java виртуалната машина или JVM.

Задължително рантоване: JavaScript може да има Java като част от името си, но двата езика са много различни по дух и дизайн, както и по своите имплементации. Независимо от това, един от начините за внедряване на интерпретатор на JavaScript е да се компилира JavaScript в байтови кодове на Java, за което са проектирани Rhino и Nashorn.

Вероятно мислите за JavaScript от гледна точка на скриптове на уеб браузъри и бихте били прави в по-голямата си част. Използва се и за сървъри. Например Node.js се използва за изграждане на бързи, леки сървъри, базирани на V8 JavaScript двигателя от Google Chrome. Двигателите на JavaScript в уеб браузърите имат достъп до обектния модел на документ HTML (DOM) и могат да манипулират HTML елементи чрез DOM. Като се има предвид, че различните уеб браузъри имат различни DOM и JavaScript двигатели, рамки като jQuery се опитват да скрият подробностите за изпълнението от програмиста.

Nashorn и Rhino преди него изрично не поддържат DOM на браузъра. Внедрени в JVM, те обикновено се извикват за скриптове на крайни потребители в Java приложения. Nashorn и Rhino могат да бъдат вградени в Java програми и да се използват като черупки на командния ред. Разбира се, допълнителната магия, необходима, когато скриптирате Java от JavaScript, е свързване на данните и несъответствия на типа между двата езика.

Проблеми с Rhino

Разработката на Rhino стартира в Netscape през 1997 г. за злополучен проект "Javagator" и беше пусната на Mozilla.org през 1998 г. След това беше лицензирана за Sun и други. Честно казано, 1998 г. също може да бъде Юрският период, с развитието на Интернет - 16 години по-късно, Rhino ясно показа възрастта си. Според Джим Ласки от Oracle, главният разработчик на Nashorn:

Сигурен съм, че всичко е вярно, но като объркан разработчик и мениджър на разработката намирам последното изречение за много забавно. В крайна сметка големите пренаписвания никога не са забавни. Започването от нулата винаги е забавно.

Голове на Нашорн

Ласки описа целите си за Нашорн по следния начин:

  • Nashorn ще се основава на езиковата спецификация ECMAScript-262 Edition 5.1 и трябва да премине тестовете за съответствие на ECMAScript-262.
  • Nashorn ще поддържа javax.scriptAPI (JSR 223).
  • Ще бъде осигурена поддръжка за извикване на Java код от JavaScript и за Java за извикване на JavaScript код. Това включва директно картографиране към JavaBeans.
  • Nashorn ще дефинира нов инструмент за команден ред jjs, за оценка на JavaScript кода в скриптове "shebang", тук документи и редактиране на низове.
  • Производителността и използването на паметта на приложенията Nashorn трябва да бъдат значително по-добри от Rhino.
  • Nashorn няма да излага никакви допълнителни рискове за сигурността.
  • Доставените библиотеки трябва да функционират правилно при локализация.
  • Съобщенията за грешки и документацията ще бъдат интернационализирани.

Laskey също изрично ограничи обхвата на проекта с някои "нецели":

  • Nashorn ще поддържа само ECMAScript-262 Edition 5.1. Той няма да поддържа никакви функции на Edition 6 или някакви нестандартни функции, предоставени от други реализации на JavaScript.
  • Nashorn няма да включва API за приставки за браузър.
  • Nashorn няма да включва поддръжка за DOM / CSS или други свързани библиотеки (като jQuery, Prototype или Dojo).
  • Nashorn няма да включва поддръжка за директно отстраняване на грешки.

И така, какво означава да се основава на ECMAScript-262 Edition 5.1? Диференциаторът тук е, че Rhino се основава на по-старото, по-малко способно издание 3. javax.scriptAPI (JSR 223) е за обратно извикване в JavaScript от Java.

Липсата на поддръжка за отстраняване на грешки в Nashorn е стъпка назад от Rhino, който има свой собствен JavaScript дебъгер. Въпреки това ще намерите решения за този умишлен пропуск в поне две популярни IDE.

Инструменти на командния ред на Nashorn: Инсталиране на jjs и jrunscript

След като прочетох за инструмента за команден ред на Nashorn jjs, бях нетърпелив да изпробвам черупката на моя iMac, но след инсталирането на Java 8 тя не беше достъпна за черупката на bash. Оказва се, че документацията и внедряването не са били напълно синхронизирани.

Знаех, че инсталацията е била успешна:

 >java -version java version "1.8.0" Java(TM) SE Runtime Environment (build 1.8.0-b132) Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode) 

но бягането се jjsвърна -bash: jjs: command not found. Малко блъскане ме доведе до /usr/bin/директорията:

 >which java /usr/bin/java 

Там намерих нещо наречено jrunscript, което се оказа вариант на jjsстартиращ допълнителен стартиращ скрипт. Това трябваше да ме удовлетвори, но бях озадачен защо документираният jjsинструмент не е инсталиран /usr/bin/с останалата част от времето за изпълнение на Java 8. Малко проучване ме накара да разгледам JavaVirtualMachinesинсталацията за Java 8. На Mac потърсете jjsв /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/bin/или /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/.

Можете да определите псевдоним за jjsв последната директория и да го добавите към конфигурацията на вашата черупка, ако имате нужда от нея за скриптове на Mac или Linux. На компютър можете да добавите правилната jre/bin/директория към вашия PATH. Във видеото си от старта на Java 8 Джим Ласки предлага да копирате jjsв /usr/bin/директорията, но когато го направих, установих, че jjsне мога да намеря правилно JRE по време на изпълнение.

Изпълнение на JavaScript скриптове

Защо двата инструмента от командния ред за стартиране на JavaScript скриптове? Не съм напълно наясно какво е мислил екипът за разработки, но jjsима възможности, които jrunscriptне го правят, и jrunscriptима файл за инициализация. По-долу са дадени няколко прости примера за jjsи jrunscriptизползване.

 $ jrunscript nashorn> alert("hello, "); script error: ReferenceError: "alert" is not defined in  at line number 1 

Това не работи, защото alert()е функция на браузър / DOM. Да! Можех обаче да се закълна, че това е работило в Rhino.

 nashorn> print("Hello, "); Hello,  

Това работи, защото print () е основна функция на JavaScript.

 nashorn> var a = 1; nashorn> var b = "1"; nashorn> print (a+b); 11 nashorn> print(a+a); 2 nashorn> quit(); $ 

С други думи, тук имаме основна среда REPL (read-execute-print-loop command-line) за JavaScript. Ако сте изненадани от отговора a+b, помислете за това:

 nashorn> print (typeof(a+b)); string 

Това е очарователен страничен ефект от свободното писане и претоварването на оператора "+" в JavaScript. Това е правилно поведение според спецификацията на JavaScript, а не грешка.

Nashorn supports the "#" character as a leading line comment marker, so jjs and jrunscript can be used in executable "shebang" scripts written in JavaScript. On a Mac or Linux, you'll have to mark the JavaScript file as executable with the chmod utility to make it runnable.

You'll find a scripting mode in jjs that jrunscript seems to lack. In scripting mode, expressions inside back-ticks are passed to the outer shell for evaluation:

 $ jjs -scripting jjs> print ('ls'); Applications Applications (Parallels) Creative Cloud Files Desktop ... work jjs>

Scripting mode also enables an extension for "heredocs," which are basically multiline strings in a format familiar to Perl and Ruby programmers.

By the way, the arrow keys on the Mac keyboard don't work properly for line editing in the jjs shell. But there is a hack for that: You can brew install rlwrap and use that as part of your alias for jjs in your .bashrc or .zshrc file.

Calling JavaScript from Java

To call Nashorn JavaScript from a Java 8 program, you basically need to make a new ScriptEngineManager instance and use that ScriptEngineManager to load the Nashorn script engine by name. (See this Stack Overflow question for a pithy summary of loading and debugging Nashorn.)

Finally, you can pass the Nashorn engine a file or a string to evaluate:

 import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; ... try { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("nashorn"); engine.eval("load(\"" + "src" + "/" + "javascript_sample" + "/" + "test1.js" + "\");"); } catch (Exception ex) { //... } ... try { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("nashorn"); engine.eval("function hi(){\nvar a = 'PROSPER'.toLowerCase(); \nmiddle(); \nprint('Live long and' + a)}\n function middle(){\n var b = 1; for(var i=0, max = 5; i
    

Note that scripts can always generate ScriptException errors, so you need to catch them.

Calling Java from JavaScript

Calling Java from Nashorn is about as easy as it can be, since the Java 8 class libraries are built into Nashorn:

 print(java.lang.System.currentTimeMillis()); var file = new java.io.File("sample.js"); print(file.getAbsolutePath()); print(file.absolutePath); 

Note that Nashorn does not import the java package by default, because references to String or Object conflict with the corresponding types in JavaScript. Hence, a Java string is java.lang.String, not String.

Nashorn and JavaFX

If you invoke jjs with the -fx switch, it will allow you to use visual JavaFX classes in your Nashorn applications. For instance, the following example from the Oracle documentation displays a JavaFX button:

 var Button = javafx.scene.control.Button; var StackPane = javafx.scene.layout.StackPane; var Scene = javafx.scene.Scene; function start(primaryStage) { primaryStage.title = "Hello World!"; var button = new Button(); button.text = "Say 'Hello World'"; button.onAction = function() print("Hello World!"); var root = new StackPane(); root.children.add(button); primaryStage.scene = new Scene(root, 300, 250); primaryStage.show(); } 

Debugging Nashorn

I mentioned earlier that Nashorn doesn't include a debugger of its own. Fortunately, both NetBeans 8 and IntelliJ IDEA 13.1 support debugging Nashorn JavaScript. The Stack Overflow question I mentioned earlier includes a useful NetBeans 8 project that you can use as a sample. You'll find that simply using the debug item from the pop-up menu on JavaScript files will allow you to debug the Nashorn code.

In IntelliJ IDEA 13, you can set breakpoints in the Java and Nashorn JavaScript files using the same shortcut key (Com/Ctrl-F8). When you hit a JavaScript breakpoint, you get all the usual debugging information.

Nashorn was designed to be a better, faster replacement for the old Rhino engine, and by most measures it succeeds. It has some minor warts that I hope will be corrected in future updates, but for now there are reasonable hacks to let you use Nashorn effectively in your projects.