快取內部值區塊程式碼

值區塊會對應至運算式。當您將值區塊用作內部區塊時,可能需要在區塊的程式碼中使用該區塊產生的運算式多次。舉例來說,取得清單最後一個元素的區塊會使用兩次建立清單的運算式。

// Incorrect block-code generator.
javascriptGenerator.forBlock['last_element'] = function(block, generator) {
  // Get the expression that creates the list.
  const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);

  // listCode is used twice.
  const code = `${listCode}[${listCode}.length - 1]`;

  return [code, Order.MEMBER];
}

如果內部區塊的程式碼每次執行時都會產生不同的值,或是程式碼有副作用,就會發生問題。舉例來說,如果內部區塊的程式碼實際上是函式呼叫,這段特定程式碼可能會導致超出範圍的情況:

randomList()[randomList().length - 1]

為避免這個問題,程式碼應執行內部區塊的程式碼一次。方法有以下兩種:

  • 臨時變數:在臨時變數中快取內部區塊程式碼的評估結果,並改用臨時變數。只有在區塊為陳述式區塊時,才能使用此方法。

  • 公用函式:建立函式來執行您需要執行的工作,並將評估內部區塊程式碼的結果,做為引數傳遞給這個函式。您可以對值和陳述式區塊使用此方法。

暫時變數

暫存變數會儲存內部區塊的程式碼字串值,因此程式碼只會評估一次,之後這個值就可以多次參照。

由於值區塊必須傳回單一行程式碼,因此無法在值區塊中使用暫時變數。請改用公用函式

import {javascriptGenerator, Order} from 'blockly/javascript';

// Correct block-code generator for a statement block that prints the last element of a list.
javascriptGenerator.forBlock['print_last_element'] = function(block, generator) {
  // Get the expression that creates the list.
  const listCode = generator.valueToCode(block, 'LIST', Order.MEMBER);
  // Get the name of a temporary variable.
  const listVar = generator.nameDB_.getDistinctName(
      'temp_list', Blockly.names.NameType.VARIABLE);

  // Evaluate listCode once and assign the result to the temporary variable.
  const code = `var ${listVar} = ${listCode};\n`;
  // Print the last element of the list.
  code += `print(${listVar}[${listVar}.length - 1]);\n`;
  return code;
}

舉例來說,如果內部區塊的程式碼是函式呼叫 randomList(),則產生的程式碼如下:

var temp_list = randomList();
print(temp_list[temp_list.length - 1]);

getDistinctName 呼叫會接收您想要的變數名稱,並傳回不會與任何使用者定義變數衝突的名稱。

減少多餘的程式碼

臨時變數的缺點是,如果內部區塊的程式碼是值,而非函式或運算式,您就會得到多餘的程式碼:

// Assigning to temp_list is unnecessary.
var temp_list = foo;
print(temp_list[temp_list.length - 1]);

如要產生更簡潔的程式碼,您可以檢查內部區塊的程式碼是否為值,並只在該區塊不是值時加入暫存變數。

if (listCode.match(/^\w+$/)) {
  const code = `print(${listCode}[${listCode}.length - 1]);\n`;
} else {
  const listVar = generator.nameDB_.getDistinctName(
      'temp_list', Blockly.names.NameType.VARIABLE);
  const code = `var ${listVar} = ${listCode};\n`;
  code += `print(${listVar}[${listVar}.length - 1]);\n`;
}

公用函式

公用函式是開發人員定義的函式,會納入產生的程式碼字串中。您可以使用這些值,確保內部區塊程式碼只會評估一次,然後該值可多次參照。

公用函式可用於值區塊和陳述式區塊。不過,陳述式區塊通常應使用暫存變數,因為這類變數通常更易讀。

import {javascriptGenerator, Order} from 'blockly/javascript';

// Correct block-code generator for a value block that gets the last element of a list.
javascriptGenerator.forBlock['last_element'] = function(block, generator) {
  // Get the expression that creates the list.
  const listCode = generator.valueToCode(block, 'LIST', Order.NONE);
  // Create a function that accepts a list and returns its last element. The
  // language generator adds this function to your code.
  const functionName = generator.provideFunction_(
      'list_lastElement',
      [
        `function ${generator.FUNCTION_NAME_PLACEHOLDER_}(list) {`,
        `  return list[list.length - 1];`,
        `}`
      ]
  );

  // Create an expression that calls the function with listCode as its argument.
  // This evaluates listCode once and passes the resulting list to the function.
  const code = `${functionName}(${listCode})`;
  return [code, Order.FUNCTION_CALL];
}

舉例來說,如果內部區塊的程式碼是函式呼叫 randomList(),則產生的程式碼如下:

// This code is added to the overall code returned by the code generator.
function list_lastElement(list) {
  return list[list.length - 1];
}

// This code is returned by your inner block.
list_lastElement(randomList());

提供函式

您可以使用 provideFunction_ 在區塊程式碼產生器中定義公用函式。它會擷取您要用於公用函式的名稱,以及包含函式定義的程式碼字串陣列。在 (可能) 修改公用函式以避免與使用者定義函式衝突後,它會傳回公用函式的結果名稱。

provideFunction_ 也會去除重複的公用函式定義,因此即使定義公用函式的區塊類型重複出現多次,每個公用函式也只會出現一次。

更新優先順序

定義公用函式時,您也應更新區塊程式碼產生器中包含的優先順序 (定義插入 括號的方式)。

優先順序一律以區塊程式碼產生器傳回的程式碼字串為準。它不會在意公用函式內的運算子。因此在前述範例中,valueToCode 呼叫已變更為 Order.NONE,而傳回的元組則變更為 Order.FUNCTION_CALL