运行动态Javascript代码

Running dynamic Javascript code

本文关键字:代码 Javascript 动态 运行      更新时间:2023-09-26

我正在制作一款小游戏,我想要一种非常简单的自定义编程语言作为其中的一部分。如果用户输入代码,例如variable "helloWorld" = 5,"解释器"会将变量更改为var,并去掉引号以显示正常的JavaScript。

我应该如何运行代码?我读过eval(),但我也读过它很慢,不应该使用。我已经研究了使用词法分析器、解析器和标记器创建编程语言,但我不打算创建如此深入的东西。

任何指导的帮助将是伟大的。

我认为您不需要"如何编写代码"的帮助,而是需要如何执行用户脚本。

关于eval:

  1. eval慢吗?是的。多慢才算慢?如果一个脚本在编译后的10毫秒内运行,而在编译后的20毫秒内运行,这对您和您的应用程序来说是个问题吗?
  2. 用户会把eval搞砸吗?是的!他们可以重新分配函数、全局变量等。他们可能会不小心破坏页面。
  3. 危险吗?是的!您可能容易受到XSS攻击。你有什么敏感数据吗?您的应用程序有服务器端吗?如果没有,我认为eval是可以的。

以下是来自不同SO问题的更多信息:

  • 使用eval而不受XSS威胁
  • JS 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    

关于eval速度的更多信息。一个真实的例子:

#!/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'稍微慢一点。您可能会执行大eval,一次运行用户脚本的所有行。'evil-eval'要慢得多,因为js引擎要运行eval 10,000,000次!:)

这完全取决于你真正想用你的语言做什么。我猜你真的不希望用户在你的应用所在的全局空间中运行任意的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是托管在一个不同的域,它是从你自己的网页和你自己的web应用服务器隔离。这基本上就是jsFiddle的工作原理,它允许用户运行任意javascript,同时保护自己的应用程序免受许多攻击。


或者,按照将window.postMesage()用于独立iframe的方式,您甚至可以在另一个iframe中使用eval()。如果用户JS的目的是与你的游戏互动,那么你就必须弄清楚如何让两个iframe相互交流,但要做到安全。它可以用IE8或更新版本支持的window.postMessage()来完成。

孤立iframe的美妙之处在于,它有自己孤立的全局变量集,除了通过你通过window.postMessage()暴露的任何接口,它根本不能真正扰乱你的游戏。

您的语法可能会得到一些改进,但思想是这样的…

首先,创建函数:

function variable(name, value){
window[name] = value;
}

下一步是解析用户输入(这就是语法改进可以发挥作用的地方)。在您的示例中,您可以完全去掉引号,然后先用'='除字符串,然后用空格留下部分。你得到3个元素,分别表示:1)函数名称变量名3)变量值

然后,像这样调用函数:

window[functionName](variableName, variableValie);

如果您需要JavaScript的完整表现力,那么坚持使用原始JavaScript,因为它已经为更多人所熟悉。使用原始JavaScript(没有限制解析器)有两种可能性:

  1. 如果你想让用户放入你的应用程序的JavaScript能够与你的网站有完全的交互权限,以一种类似于允许特权附加组件到你的网站的方式(记住巨大的风险:用户可能不知道他们在做什么,或者更糟的是,可能从一些恶意来源粘贴,完全访问将包括允许访问cookie(您可能使用cookie存储会话信息或密码),然后继续执行eval()

  2. 如果您想要JavaScript的完整表现力,但不希望用户代码干扰您的应用程序(并且可以自由地将代码托管在单独的站点上,而无需访问站点的cookie),那么您可以使用类似于基于postMessageeval()沙箱的东西:http://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/#safely-sandboxing-eval

如果你不需要JavaScript的全部表达能力,但是,只是希望用户调用自己的自定义API或其他东西,我强烈建议考虑使用Blockly(演示/教程),因为它是一种图形编码机制,应该更友好,更少出错比必须手动创建一些自定义语法。