Javascript: Как динамически построить метод из строковых данных?
У меня есть документ XML, который определяет задачу, которая представляет собой список действий, которые необходимо выполнить с определенными данными. Мне нужно преобразовать этот "список задач" в метод Javascript, который может быть вызван позднее, который, в свою очередь, вызывает серию предопределенных методов с соответствующими данными. Как бы вы достигли этого?
Важное уточнение:
Я не беспокоюсь о разборе XML. Меня больше интересует, как на самом деле построить метод задачи, в том числе связать основные данные с заранее определенными методами действия. Это та часть, с которой я борюсь.
Изменить: я пересмотрел мой пример, чтобы сделать его немного интереснее, и, надеюсь, немного яснее.
XML:
<task id="enter-castle">
<if holding="castle-key">
<print message="You unlock the castle door and enter." />
<destroy item="castle-key" />
<goto location="castle" />
<else>
<print message="The castle door is locked." />
</else>
</if>
</task>
Javascript:
Game = {
print: function(message) {
// display message
},
destroy: function(item) {
// destroy the object
},
goto: function(location) {
// change player location
},
ifHolding: function(item) {
// return true if player has item
}
};
parseTask(taskNode) {
var taskId = taskNode.getAttribute('id');
// What goes here??
Game.tasks[taskId] = /* ??? */;
}
Когда я звоню parseTask()
на <task id="enter-castle">
узел, это должно создать функцию, которая, по сути, делает следующее при вызове:
Game.tasks.enterCastle = function() {
if (Game.ifHolding('castle-key')) {
Game.print("You unlock the castle door and enter.");
Game.destroy('castle-key');
Game.goto('castle');
} else {
Game.print("The castle door is locked.");
}
}
3 ответа
То, что вы хотите, это замыкания.
function createMethod(arguments) {
var task = doSomethingWithYour(arguments);
return function(xmlData) { // <- this is the fundamental part
// do the task with your data
// the "task" vars are still available
// even if the returned function is executed in a different context
}
}
Это позволяет вам создать собственный метод для каждой задачи. Не используйте конструктор Function или eval.
Это ситуация, когда JavaScript eval()
Функция сделает вашу жизнь намного проще. Вы можете легко создать исходную строку JavaScript, соответствующую вашей, и оценить ее, чтобы назначить функцию нужному свойству вашего объекта Game.
Конечно, есть недостатки в использовании "eval", которые я не буду рассматривать в этом ответе, поскольку вы можете найти бесчисленное множество причин, почему бы не использовать его в Интернете. Однако создание и оценка простой исходной строки JS в краткосрочной перспективе будет намного проще, чем, скажем, решение на основе замыкания, несмотря на любые потенциальные недостатки производительности и безопасности. Более того, решение на основе "eval" будет легко протестировать, поскольку вы можете просто проверить исходную строку перед ее оценкой.
Так что попробуйте что-то вроде этого:
function buildTaskFunction(taskXml) {
var source='', depth=0, node /*a visitor to each DOM node*/;
// foreach (node in traverseInOrder(taskXml)) {
switch (node.nodeName) {
case 'TASK':
source += 'Game.tasks.' + makeFunctionName(node.id) + '= function(){';
depth++;
break;
case 'IF':
source += 'if(' + getConditionalAttribute(node) + '){'
depth++;
break;
case 'ELSE':
source += '}else{';
break;
case 'DESTROY':
source += 'Game.destroy("' + node.getAttribute('item') + '");'
break;
case 'PRINT':
source += 'Game.print("' + node.getAttribute('message') + '");'
break;
// case etc...
default: throw new Error('unhandled node type "' + node.nodeName + '"');
}
// end "foreach node".
while (depth-- > 0) { // You'll need to account for nested "if"s somehow...
source += '}';
}
eval(source);
}
И снова, есть много потенциальных (не определенных) проблем с использованием "eval", поэтому, пожалуйста, прочитайте и постарайтесь понять их в контексте вашего решения. Принимая решение о том, стоят ли недостатки в вашей собственной программе, используйте свое собственное суждение - только то, что инструмент может быть опасным, не означает, что вы не должны его использовать.
Пример использования dojo:
dojo.require("dojox.xml.parser");
dojo.ready(function(){
// Parse text and generate an XML DOM
var xml = "<tnode><node>Some Text</node><node>Some Other Text</node></tnode>";
var dom = dojox.xml.parser.parse(xml);
var docNode = dom.documentElement();
// ...
}
Остальная часть функции нетривиальна, но в основном состоит из поиска атрибутов с использованием ['<attribute-name>']
итерация дочернего узла с использованием dojo.forEach(<node>.childNodes, function(childNode) { /* do stuff */ });