[英]Running dynamic Javascript code
我正在制作一个小游戏,其中一部分我想要一个非常简单的自定义编程语言。 如果用户输入代码,例如variable "helloWorld" = 5
,则“解释器”会将变量更改为var并将引号删除为普通JavaScript。
我该如何运行该代码? 我读过有关eval()
,但我也读过它很慢,不应该使用它。 我已经研究了使用词法分析器,解析器和标记器创建编程语言,但我并不打算创建一些深入的东西。
对方向的任何帮助都会很棒。
我假设您不需要“如何编写该代码?”的帮助,但是如何执行用户脚本。
eval
: eval
还可以。 将脚本包装在IIFE中! 像这样包装脚本:
(function(){
// user script goes here. This will cause it to be in it's own scope!
})();
Javascript具有函数范围,因此这将保护全局空间不被用户变量和函数填充。 用户仍然可能恶意影响全局变量,如下所示:
(function(){Array.isArray = function() { return 2;};})()
Array.isArray([]);
// returns 2
#!/bin/env node
// Be careful running this. You don't want to melt your cpu. Try 100,000 first.
console.time("no-eval");
for (var i = 0; i < 10000000; i++) { Math.sqrt(i); }
console.timeEnd("no-eval");
console.time("big-eval");
eval("for (var i = 0; i < 10000000; i++) { Math.sqrt(i); }");
console.timeEnd("big-eval");
console.time("evil-eval");
for (var i = 0; i < 10000000; i++) { eval("Math.sqrt(i);"); }
console.timeEnd("evil-eval");
输出:
no-eval: 272ms
big-eval: 294ms
evil-eval: 1945ms
正如你所看到的,'big-eval'有点慢。 您可能会执行big-eval,一次运行用户脚本的所有行。 'evil-eval'慢得多,因为js引擎运行了10,000,000次eval! :)
这一切都取决于你真正想用你的语言做什么。 我猜你真的不希望用户运行在应用程序所在的同一全局空间中运行的任意javascript。
因此,根据您真正想要做的事情,您不一定需要使用eval()
。 例如,让我们接受你的陈述:
variable "helloWorld" = 5
让我们假设您解析它并将其解析为一个包含数组中四个项的语句对象:
var statement = ["variable", "helloWorld", "=", 5];
好的,您可以从数组中的第一项看到用户正在声明变量。 现在,您可能不希望用户的变量进入全局命名空间(另一个原因是不使用eval()
。因此,您为用户的变量创建了一个对象:
var userVars = {};
现在,当您处理上述语句时,您将看到它是对您刚刚转换为的用户变量的赋值:
userVars[statement[1]] = statement[3];
现在,您在名为userVars
的变量中有一个对象,其名称为"helloWorld"
,其值为5
。 用户的变量都不在全局命名空间中,或者可能影响您自己的程序操作。
现在,显然如果你想进入详细的表达式和逻辑流程,那么事情变得比这更难,但我希望你看到你可能不仅仅想要将eval()
用于全局命名空间。
执行任意用户javascript的另一种“安全”方式是将其放在由主网页不同的域托管的iframe中。 您可以将JS发送到您的服务器并将其传递给另一个服务器(可能在同一个盒子上)然后提供iframe,或者您可以使用window.postMessage()
将JS文本传递给另一个合作的iframe和在那里执行。
由于iframe托管在不同的域上,因此它与您自己的网页和您自己的网络应用服务器隔离。 这基本上就是jsFiddle的工作方式,并允许用户运行任意javascript,同时保持自己的应用程序免受许多攻击。
或者,在将window.postMesage()
用于孤立的iframe的过程中,您甚至可以在其他iframe中使用eval()
。 如果用户JS的要点是与您的游戏进行交互,那么您必须弄清楚如何允许两个iframe彼此交谈,但要安全地这样做。 可以使用IE8或更高版本支持的window.postMessage()
来完成。
孤立的iframe的美妙之处在于它拥有自己独立的全局变量集,并且除了通过window.postMessage()
公开的whateve接口之外,根本不会弄乱你的游戏。
你的语法可能会得到一些改进,但这个想法是这样的......
首先,你创建功能:
function variable(name, value){
window[name] = value;
}
下一步是解析用户输入(这是语法改进可能发挥作用的地方)。 在你的例子中,你可以完全剥离引号,然后先将字符串除以'=',然后将空格分开。 你得到3个代表(分别)的元素:1)函数名2)变量名3)变量值
然后,你调用这样的函数:
window[functionName](variableName, variableValie);
如果你需要JavaScript的完整表现力,那么坚持使用原始JavaScript,因为它已经为更多人所熟悉。 使用原始JavaScript有两种可能性(没有限制解析器):
如果您希望允许用户放入您的应用程序的JavaScript能够与您的网站拥有完全的交互式权限,那么类似于允许您网站的特权加载项(请注意以下风险:用户可能不会知道他们正在做什么,或者更糟糕的是,可能已经粘贴了一些恶意来源,完全访问权限将包括允许访问您可能用于存储会话信息或密码的cookie,然后继续使用自己的eval()
。
如果你想要JavaScript的完整表现力但不希望用户代码干扰你的应用程序(并且可以在不访问你网站的cookie的情况下在一个单独的站点上托管代码的自由),那么你可以使用类似这样的东西用于postMessage
-基于eval()
沙盒: http : //www.html5rocks.com/en/tutorials/security/sandboxed-iframes/#safely-sandboxing-eval
但是,如果您不需要JavaScript的全部表现力,并希望用户调用您自己的自定义API或其他东西,我强烈建议您考虑使用Blockly ( 演示/教程 ),因为它是一种图形编码机制,应该比手动创建一些自定义语法更加用户友好且不易出错。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.