產生及執行 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 已加入保留字清單,因此如果使用者程式碼含有該名稱的變數,系統會自動重新命名,而不會發生衝突。任何本機變數都應以這種方式保留。

精選內容區塊

在程式碼執行時醒目顯示目前執行的區塊,有助於使用者瞭解程式的行為。您可以在產生 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 解譯器專案。這個專案與 Blockly 分開,但專門為 Blockly 編寫。

  • 以任何速度執行程式碼。
  • 暫停/繼續/逐步執行。
  • 在執行時醒目顯示區塊。
  • 與瀏覽器的 JavaScript 完全隔離。

執行解譯器

首先,請從 GitHub 下載 JS-Interpreter:

下載 ZIP 檔案 下載 TAR Ball 前往 GitHub 查看

然後將其加入網頁:

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

呼叫此 API 最簡單的方法是產生 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-Interpreter 是與瀏覽器完全隔離的沙箱。凡是與外部世界執行動作的區塊,都需要在轉譯器中新增 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 的即時示範。而這個示範包含等待區塊,是用於其他非同步行為 (例如語音或音訊、使用者輸入) 的絕佳範例。