生成和运行 JavaScript

Blockly 应用通常会生成 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 已添加到预留字词列表中,因此,如果用户的代码包含该名称的变量,系统会自动重命名该变量,而不是发生冲突。所有局部变量都应以这种方式预留。

突出显示的代码块

在代码运行时突出显示当前正在执行的代码块有助于用户了解其程序的行为。您可以先设置 STATEMENT_PREFIX,然后再生成 JavaScript 代码,以便逐个语句地突出显示问题:

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-Interpreter 项目。此项目与 Blockly 是分开的,但专为 Blockly 编写。

  • 以任何速度执行代码。
  • 暂停/恢复/单步执行。
  • 在代码块执行时突出显示相应代码块。
  • 与浏览器的 JavaScript 完全隔离。

运行解释器

首先,从 GitHub 下载 JS 解释器:

下载 ZIP 文件 下载 TAR 文件包 在 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 解释器是一个与浏览器完全隔离的沙盒。任何与外界执行操作的代码块都需要向解释器添加 API。如需了解完整说明,请参阅 JS 解释器文档。不过,首先,下面是支持提醒和提示块所需的 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 解释器中运行时,应在用户逐步执行程序时立即在沙盒外执行 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。此演示包含一个等待代码块,是一个适合用于其他异步行为(例如语音或音频、用户输入)的好示例。