生成并运行 JavaScript

块应用通常会生成 JavaScript 作为其输出语言,通常在网页(可能相同或嵌入式 WebView)中运行。与所有生成器一样,第一步是添加 JavaScript 生成器。

import {javascriptGenerator} from 'blockly/javascript';

要从工作区生成 JavaScript,请调用:

javascriptGenerator.addReservedWords('code');
var code = javascriptGenerator.workspaceToCode(workspace);

生成的代码可以直接在目标网页中执行:

try {
  eval(code);
} catch (e) {
  alert(e);
}

基本上,上面的代码段仅生成和评估代码。不过,它进行了一些优化。一种优化措施是,eval 封装在 try/catch 中,以便所有运行时错误都可见,而不是静默失败。另一个优化是,将 code 添加到了保留字列表中。这样一来,如果用户代码包含具有该名称的变量,它便会自动重命名,而不是发生冲突。所有局部变量都应以这种方式预留。

高亮方块

在代码运行时突出显示当前正在执行的块有助于用户了解其程序的行为。在生成 JavaScript 代码之前,可以通过设置 STATEMENT_PREFIX 逐个语句级别突出显示代码:

javascriptGenerator.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
javascriptGenerator.addReservedWords('highlightBlock');

定义 highlightBlock 以标记工作区上的代码块。

function highlightBlock(id) {
  workspace.highlightBlock(id);
}

这会导致在每个语句之前添加语句 highlightBlock('123');,其中 123 是要突出显示的块的序列号。

无限循环

虽然生成的代码一定会始终在语法上正确无误,但可能包含无限循环。由于解决暂停问题超出了 Blockly 的讨论范围 (!),因此处理这些情况的最佳方法是维护计数器,并在每次执行迭代时减少计数器。为此,只需将 javascriptGenerator.INFINITE_LOOP_TRAP 设置为一个代码段,该代码段就会插入每个循环和每个函数中。示例如下:

window.LoopTrap = 1000;
javascriptGenerator.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = javascriptGenerator.workspaceToCode(workspace);

示例

下方是有关如何生成和执行 JavaScript 的实时演示

JS 解释器

如果您确实想正确运行用户的块,那么 JS-Explainer 项目是理想选择。此项目与 Blockly 相互独立,但专为 Blockly 而编写。

  • 以任意速度执行代码。
  • 暂停/继续/逐步执行。
  • 在代码块执行时突出显示。
  • 与浏览器的 JavaScript 完全隔离。

运行解释器

首先,从 GitHub 下载 JS-Explainer:

下载 ZIP 文件 下载 TAR Ball 在 GitHub 上查看

然后将其添加到您的网页:

<script src="acorn_interpreter.js"></script>

进行调用的最简单方法是生成 JavaScript,创建解释器,然后运行代码:

var code = javascriptGenerator.workspaceToCode(workspace);
var myInterpreter = new Interpreter(code);
myInterpreter.run();

逐步说明翻译

为了减慢代码执行速度或以更受控的方式执行代码,请将对 run 的调用替换为执行此步骤的循环(在本例中,每 10 毫秒执行一步):

function nextStep() {
  if (myInterpreter.step()) {
    setTimeout(nextStep, 10);
  }
}
nextStep();

请注意,每个步骤都不是一行或一个块,而是 JavaScript 中的一个语义单元,这可能非常精细。

添加 API

JS-Explainer 是一个与浏览器完全隔离的沙盒。任何对外界执行操作的块都需要向解释器添加 API。如需了解完整说明,请参阅 JS-Explainer 文档。但首先,以下是支持提醒和提示块所需的 API:

function initApi(interpreter, globalObject) {
  // Add an API function for the alert() block.
  var wrapper = function(text) {
    return alert(arguments.length ? text : '');
  };
  interpreter.setProperty(globalObject, 'alert',
      interpreter.createNativeFunction(wrapper));

  // Add an API function for the prompt() block.
  wrapper = function(text) {
    return prompt(text);
  };
  interpreter.setProperty(globalObject, 'prompt',
      interpreter.createNativeFunction(wrapper));
}

然后修改解释器初始化以传入 initApi 函数:

var myInterpreter = new Interpreter(code, initApi);

提醒块和提示块是默认块集中仅有的两个块需要解释器的自定义 API。

正在关联 highlightBlock()

在 JS-Explainer 中运行时,应在用户逐步执行程序时立即在沙盒外执行 highlightBlock()。为此,请创建一个封装容器函数 highlightBlock() 来捕获函数参数,并将其注册为原生函数。

function initApi(interpreter, globalObject) {
  // Add an API function for highlighting blocks.
  var wrapper = function(id) {
    return workspace.highlightBlock(id);
  };
  interpreter.setProperty(globalObject, 'highlightBlock',
      interpreter.createNativeFunction(wrapper));
}

更复杂的应用可能希望不暂停地重复执行步骤,直到找到突出显示命令,然后暂停。此策略会模拟逐行执行。以下示例使用了此方法。

JS 解释器示例

请点击此处,查看逐步解释 JavaScript 的演示。此演示包括一个等待块,这是一个很好的例子,用于其他异步行为(例如语音或音频、用户输入)。