5

我想用来new Function(...)从非常简化的代码中生成一个函数。我想这样做

  • 避免自己解析表达式
  • 尽可能灵活。

eval()我尽可能避免。但我不确定它是否足够安全,new Function(...)也被称为容易出现安全漏洞。

背景

我想管理菜单按钮的状态。所以,在定义按钮时,我想写一些类似的东西

 {
 ..., // More button definition
 state: "isInEditmode && (isWidgetSelected || isCursorInWidget),
 ...
 }

在几个事件中处理 statechange 时,我将检查(总结)当​​前整体状态对象的状态与states属性中的状态。

因此,我将在渲染时生成一个函数并将其附加为 DOM 对象属性,而不是这样的 DOM 属性

 ...
 $el.stateFn = new Function("stateObj", "with (stateObj) {return " + item.state + ";}");
 ...

测试状态:

 visible = $el.stateFn.call(currentStates, currentStates);

with语句帮助我将当前state对象的属性作为变量提供,这样上面的表达式就不需要像obj.isInEditmode.

安全问题

在我看来,这不会引入安全漏洞,因为附加到 DOM 对象的函数是在渲染期间生成并从源代码读取的。还是我错了?我应该避免这种情况吗?

性能提示表示赞赏(评论)(我认为只要我Function在渲染期间评估一次新的,这是可以接受的)。

编辑 1

  • 我正在使用 Backbone.js。使用另一个框架是没有问题的。
  • 有些菜单项需要绑定到不同甚至多个模型。
  • 委托(或外观/代理?)模型是相当可观的。
4

4 回答 4

6

如果允许用户输入在代码中中断,那么从安全性方面来说,两者都一样糟糕。但是,在维护方面,当本地评估与您的范围混淆并导致动态范围界定时,您不必担心隐藏的错误。

在性能方面,由 生成的函数new Function与任何其他函数完全相同。生成速度较慢,但eval​​不会导致包含范围不可优化。

事实上,new Function可用于在以下情况下提高性能:

//Will behave like function a( obj ) { return obj.something }
function makePropReader( propName ) {
    return new Function( "obj", "return obj." + propName );
}

构造的函数将比这里返回的函数执行得更好

function makePropReader( propName ) {
     return function( obj ) {
         return obj[propName];
     }
}

由于必须propName从闭包上下文中动态读取并在每次调用对象时对其进行动态读取。

于 2013-08-05T15:31:57.583 回答
4

无论如何,请永远不要使用 eval 。有一个更好的选择。代替eval,使用Function构造函数。eval是邪恶的,这是毫无疑问的,但是大多数人跳过了最邪恶的方面eval:它使您可以访问本地范围内的变量。回到 90 年代,在 JIST 编译概念出现之前,这eval听起来像是一个好主意(而且确实如此):只需将一些额外的行动态地插入到您已经逐行执行的代码中。这也意味着evals 并没有真正减慢一切。但是,现在使用 JIST 编译eval语句对 JIST 编译器来说是非常繁重的,它在内部完全删除了变量名的概念。对于 JIST 编译器,为了评估 eval 语句,它必须确定其所有变量的存储位置,并将它们与在 evaled 语句中找到的未知全局变量进行匹配。如果你真正掌握技术,问题会更深。

但是,使用Function,JIST 编译器不必进行任何昂贵的变量名查找:整个代码块是自包含的并且在全局范围内。例如,采用以下非常低效的eval代码段。请注意,这只是为了作为示例。在生产代码中,您甚至不应该使用 eval 或Function从内容已知的字符串生成函数。

var a = {
    prop: -1
};
var k = eval('(function(b){return a.prop + b;})');
alert( k(3) ); // will alert 2

现在,让我们来看看更好的Function选择。

var a = {
    prop: -1
};
var k = (Function('a', 'b', 'return a.prop + b')).bind(undefined, a);
alert( k(3) ); // will alert 2

注意到区别了吗?有一个主要的:eval在本地范围内Function执行,而在全局范围内执行。

现在,进入下一个问题:安全性。有很多关于安全性有多困难的讨论,是的,使用 eval 几乎是不可能的(例如,如果您将整个代码包装在沙盒函数中,那么您所要做的就是过早地结束该函数并开始一个新的一种在当前范围内自由执行代码)。但是,使用Function,您可以轻松(但不是最有效地)沙箱化任何东西。看下面的代码。

var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
    return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
    "use-strict";
    blacklist.push.apply(blacklist, arguments);
    blacklist[blacklist.length-1] = 
        '"use-strict";' + arguments[arguments.length-1];
    var newFunc = Function.apply(
        Function,
        blacklist
    );
    blacklist.length = listlen;
    return newFunc.bind.apply(newFunc, blanklist);
}

然后,摆弄白名单,按照你想要的方式得到它,然后你就可以sandboxed_functionFunction. 例如:

var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
    return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
    "use-strict";
    blacklist.push.apply(blacklist, arguments);
    blacklist[blacklist.length-1] = 
        '"use-strict";' + arguments[arguments.length-1];
    var newFunc = Function.apply(
        Function,
        blacklist
    );
    blacklist.length = listlen;
    return newFunc.bind.apply(newFunc, blanklist);
}
var myfunc = sandboxed_function('return "window = " + window + "\\ndocument = " + document + "\\nBoolean = " + Boolean');
output.textContent = myfunc();
<pre id="output"></pre>

至于编写要在这个严格的沙箱下运行的代码,您可能会问,如果 window 未定义,我如何测试方法是否存在。有两种解决方案。#1 只是简单地像这样使用 typeof 。

output.textContent = 'typeof foobar = ' + typeof foobar;
<div id="output"></div>

正如您在上面的代码中看到的,使用 typeof 不会引发错误,而只会返回 undefined。第二种检查全局的主要方法是使用 try/catch 方法。

try {
    if (foobar)
        output.textContent = 'foobar.constructor = ' + foobar.constructor;
    else
        output.textContent = 'foobar.constructor = undefined';
} catch(e) {
    output.textContent = 'foobar = undefined';
}
<div id="output"></div>

所以,总而言之,我希望我的代码片段能让您对 eval 更好、更好、更简洁的替代方案有所了解。我希望我已经渴望你达到一个更大的目标:冷落 eval。至于浏览器兼容性,虽然它sandboxed_function会在 IE9 中运行,但为了让它真正沙箱化任何东西,需要 IE10+。这是因为该"use-strict"声明对于消除许多像下面这样的偷偷摸摸的沙盒破坏方式非常重要。

var whitelist = ['Math', 'Number', 'Object', 'Boolean', 'Array'];
var blacklist = Object.getOwnPropertyNames(window).filter(function(x){
    return whitelist.indexOf(x) === -1 && !/^[^a-zA-Z]|\W/.test(x)
});
var listlen = blacklist.length;
var blanklist = (new Array(listlen+1)).fill(undefined);
function sandboxed_function(){
    blacklist.push.apply(blacklist, arguments);
    blacklist[blacklist.length-1] = 
        /*'"use-strict";' +*/ arguments[arguments.length-1];
    var newFunc = Function.apply(
        Function,
        blacklist
    );
    blacklist.length = listlen;
    return newFunc.bind.apply(newFunc, blanklist);
}
var myfunc = sandboxed_function(`return (function(){
    var snatched_window = this; // won't work in strict mode where the this
                                // variable doesn't need to be an object
    return snatched_window;
}).call(undefined)`);
output.textContent = "Successful broke out: " + (myfunc() === window);
<pre id="output"></pre>
最后一个评论是,如果您要允许事件 API 进入您的沙盒环境,那么您必须小心:该view属性可以是一个窗口对象,因此您也必须删除它。还有其他几件事,但我建议彻底研究并探索 Chrome 控制台中的对象。

最后,注意这Function是一个非常独特的构造函数,它返回一个函数而不是一个对象实例,所以不需要使用new.

于 2017-09-22T23:18:15.663 回答
1

正如您所说,您只会在您自己编写的代码上执行此操作 - 我会说这很好。new Function()绝对比eval()在任何情况下使用都要好。您不会弄乱任何局部变量,而是通过使用fn.call.

在我看来,如果您使用的是支持 2 路数据绑定的 MVC 或 MVVM 框架,那么您尝试解决的问题将是相当直接的。即更改 UI 会更新支持模型,更新模型会自动为您刷新 UI。

例如,knockout.js。在这种情况下,可见的数据绑定将是合适的。

于 2013-08-05T14:52:30.360 回答
1

这些天来,旧线程的答案被认为是危险的。 new Function()仍然允许访问全局变量。因此,当对手有机会影响函数字符串时——这通常是考虑的原因new Function并且很难保证它不会被恶意执行——可以读取和修改任何全局变量。从那时起祝你好运:-)

这就是为什么从这里提到的 CSP(内容安全策略)的角度来看new Function属于同一类别的原因。eval

例子:

a = 10
> 10

b = new Function('a = 20; return 42')
> function(...)

a
> 10

b()
> 42

a
> 20
于 2020-09-03T07:32:22.663 回答