Wertblöcke entsprechen Ausdrücken. Wenn Sie einen Wertblock als inneren Block verwenden, müssen Sie den generierten Ausdruck möglicherweise mehrmals im Code des Blocks verwenden. In einem Block, der das letzte Element einer Liste abrufen soll, wird beispielsweise der Ausdruck verwendet, der die Liste erstellt.
// 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];
}
Das führt zu Problemen, wenn der Code des inneren Blocks bei jeder Ausführung unterschiedliche Werte generiert oder der Code Nebeneffekte hat. Wenn der Code des inneren Blocks beispielsweise ein Funktionsaufruf ist, kann dieser Code zu einer Außerhalb-des-Bereichs-Bedingung führen:
randomList()[randomList().length - 1]
Um dieses Problem zu vermeiden, sollte der Code eines inneren Blocks genau einmal ausgeführt werden. Dafür gibt es zwei Möglichkeiten:
Temporäre Variablen: Das Ergebnis der Auswertung des Codes des inneren Blocks wird in einer temporären Variablen im Cache gespeichert und stattdessen verwendet. Sie können diese Methode nur verwenden, wenn es sich bei Ihrem Block um einen Anweisungsblock handelt.
Hilfsfunktionen: Erstellen Sie eine Funktion, die die gewünschte Arbeit ausführt, und übergeben Sie das Ergebnis der Auswertung des Codes des inneren Blocks als Argument an diese Funktion. Sie können diese Methode sowohl für Wert- als auch für Anweisungsblöcken verwenden.
Temporäre Variablen
In einer temporären Variablen wird der Wert des Codestrings eines inneren Blocks gespeichert, sodass der Code nur einmal bewertet wird und dann auf den Wert mehrmals verwiesen werden kann.
Temporäre Variablen können nicht in Wertblöcken verwendet werden, da Wertblöcke eine einzelne Codezeile zurückgeben müssen. Verwenden Sie stattdessen eine Dienstprogrammfunktion.
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;
}
Wenn der Code des inneren Blocks beispielsweise der Funktionsaufruf randomList()
ist, lautet der generierte Code:
var temp_list = randomList();
print(temp_list[temp_list.length - 1]);
Der getDistinctName
-Aufruf nimmt den gewünschten Variablennamen entgegen und gibt einen Namen zurück, der nicht mit benutzerdefinierten Variablen in Konflikt steht.
Redundanten Code reduzieren
Der Nachteil von temporären Variablen besteht darin, dass der Code des inneren Blocks ein Wert und keine Funktion oder ein Ausdruck ist. In diesem Fall wird redundanter Code generiert:
// Assigning to temp_list is unnecessary.
var temp_list = foo;
print(temp_list[temp_list.length - 1]);
Für einen saubereren Code können Sie prüfen, ob der Code des inneren Blocks ein Wert ist, und die temporäre Variable nur einfügen, wenn dies nicht der Fall ist.
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`;
}
Hilfsfunktionen
Eine Dienstprogrammfunktion ist eine vom Entwickler definierte Funktion, die Teil des generierten Codestrings ist. So lässt sich dafür sorgen, dass Blockcode nur einmal ausgewertet wird und der Wert dann mehrmals referenziert werden kann.
Dienstfunktionen können in Wertblöcken und Anweisungsblöcken verwendet werden. In Anweisungsblöcken sollten jedoch in der Regel vorübergehende Variablen verwendet werden, die in der Regel besser lesbar sind.
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];
}
Wenn der Code des inneren Blocks beispielsweise der Funktionsaufruf randomList()
ist, lautet der generierte Code:
// 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());
Funktion bereitstellen
Mit provideFunction_
können Sie Dienstfunktionen in Blockcodegeneratoren definieren. Sie nimmt den gewünschten Namen für die Dienstprogrammfunktion und ein Array von Codestrings entgegen, die die Definition der Funktion enthalten. Es gibt den resultierenden Namen Ihrer Dienstprogrammfunktion zurück, nachdem sie (möglicherweise) so geändert wurde, dass sie nicht mit benutzerdefinierten Funktionen in Konflikt steht.
provideFunction_
entfernt auch Duplikate von Dienstfunktionsdefinitionen, sodass jede Dienstfunktion nur einmal vorhanden ist, auch wenn der Blocktyp, der sie definiert, mehrmals vorhanden ist.
Prioritäten aktualisieren
Wenn Sie eine Dienstprogrammfunktion definieren, sollten Sie auch die Prioritäten im Blockcodegenerator aktualisieren, die festlegen, wie Klammern eingefügt werden.
Die Priorität basiert immer auf dem Codestring, der vom Blockcodegenerator zurückgegeben wird. Operatoren in Dienstprogrammfunktionen werden nicht berücksichtigt. Im vorherigen Beispiel wurde also der valueToCode
-Aufruf in Order.NONE
und das Rückgabetupel in Order.FUNCTION_CALL
geändert.