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:
然後將其加入網頁:
<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 的即時示範。而這個示範包含等待區塊,是用於其他非同步行為 (例如語音或音訊、使用者輸入) 的絕佳範例。