我想使用自定义thisArg
调用函数。
这似乎微不足道,我只需要致电call
:
func.call(thisArg, arg1, arg2, arg3);
但是等等! func.call
可能不是Function.prototype.call
。
所以我考虑过使用
Function.prototype.call.call(func, thisArg, arg1, arg2, arg3);
但是等等! Function.prototype.call.call
可能不是Function.prototype.call
。
因此,假设Function.prototype.call
是本地属性,但考虑到可能已添加任意非内部属性,ECMAScript是否提供了一种安全的方法来执行以下操作?
func.[[Call]](thisArg, argumentsList)
答案 0 :(得分:4)
这就是鸭子打字的力量(和风险):if typeof func.call === 'function'
,那么你应该把它看作是一个正常的,可调用的函数。由func
提供商来确保他们的call
属性与公共签名匹配。我实际上在一些地方使用它,因为JS没有提供重载()
运算符并提供经典仿函数的方法。
如果您确实需要避免使用func.call
,我会选择func()
并要求func
将thisArg
作为第一个参数。由于func()
未委托给call
(即,f(g, h)
不会贬低f.call(t, g, h)
),您可以使用parens左侧的变量,它会给出你可以预测的结果。
您可以在加载库时缓存对Function.prototype.call
的引用,以防以后更换它,并在以后使用它来调用函数。这是lodash / underscore用于获取本机数组方法的模式,但不提供任何实际保证,您将获得原始本机调用方法。它可以非常接近并且不是非常难看:
const call = Function.prototype.call;
export default function invokeFunctor(fn, thisArg, ...args) {
return call.call(fn, thisArg, ...args);
}
// Later...
function func(a, b) {
console.log(this, a, b);
}
invokeFunctor(func, {}, 1, 2);
这是任何具有多态性的语言中的基本问题。在某些时候,您必须信任对象或库根据其合同行事。与任何其他情况一样,信任但要验证:
if (typeof duck.call === 'function') {
func.call(thisArg, ...args);
}
通过类型检查,您也可以进行一些错误处理:
try {
func.call(thisArg, ...args);
} catch (e) {
if (e instanceof TypeError) {
// probably not actually a function
} else {
throw e;
}
}
如果你可以牺牲thisArg
(或强迫它成为一个实际的参数),那么你可以用parens进行类型检查和调用:
if (func instanceof Function) {
func(...args);
}
答案 1 :(得分:2)
在某些时候,您必须相信窗口上可用的内容。它要么意味着缓存您计划使用的功能,要么尝试对代码进行沙盒化。
"简单"调用call
的解决方案是临时设置属性:
var safeCall = (function (call, id) {
return function (fn, ctx) {
var ret,
args,
i;
args = [];
// The temptation is great to use Array.prototype.slice.call here
// but we can't rely on call being available
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
// set the call function on the call function so that it can be...called
call[id] = call;
// call call
ret = call[id](fn, ctx, args);
// unset the call function from the call function
delete call[id];
return ret;
};
}(Function.prototype.call, (''+Math.random()).slice(2)));
然后可以将其用作:
safeCall(fn, ctx, ...params);
请注意,传递给safeCall的参数将被集中到一个数组中。您需要apply
才能使其正确行事,而我只是想在此处简化相关性。
safeCall
的改进版本,为apply
添加依赖项:
var safeCall = (function (call, apply, id) {
return function (fn, ctx) {
var ret,
args,
i;
args = [];
for (i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
apply[id] = call;
ret = apply[id](fn, ctx, args);
delete apply[id];
return ret;
};
}(Function.prototype.call, Function.prototype.apply, (''+Math.random()).slice(2)));
这可以用作:
safeCall(fn, ctx, ...params);
安全地调用call的另一种解决方案是使用来自不同窗口上下文的函数。
这可以通过创建一个新的iframe
并从其窗口中获取函数来完成。您仍然需要对可用的DOM操作函数进行一定程度的依赖,但这是作为设置步骤发生的,因此任何未来的更改都不会影响现有脚本:
var sandboxCall = (function () {
var sandbox,
call;
// create a sandbox to play in
sandbox = document.createElement('iframe');
sandbox.src = 'about:blank';
document.body.appendChild(sandbox);
// grab the function you need from the sandbox
call = sandbox.contentWindow.Function.prototype.call;
// dump the sandbox
document.body.removeChild(sandbox);
return call;
}());
然后可以将其用作:
sandboxCall.call(fn, ctx, ...params);
safeCall
和sandboxCall
对 将来 对Function.prototype.call
的更改都是安全的,但正如您所看到的,他们依赖于某些现有的全局函数在运行时工作。如果恶意脚本在此代码之前执行,则您的代码仍然容易受到攻击。
答案 2 :(得分:1)
如果您信任Function.prototype.call
,您可以这样做:
func.superSecureCallISwear = Function.prototype.call;
func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */);
如果您信任Function..call
但不信任Function..call.call
,则可以这样做:
var evilCall = Function.prototype.call.call;
Function.prototype.call.call = Function.prototype.call;
Function.prototype.call.call(fun, thisArg, arg0, arg1 /*, ... */);
Function.prototype.call.call = evilCall;
甚至可能将其包裹在帮手中。
如果您的函数是纯函数且对象可序列化,则可以创建iframe并通过消息传递(window.postMessage
),将函数代码和参数传递给它,让它为您执行call
(因为它是一个没有任何第三方代码的新iframe,你是非常安全的),并且你是金色的,类似的东西(根本没有测试过,可能有很多错误):
// inside iframe
window.addEventListener('message', (e) => {
let { code: funcCode, thisArg, args } = e.data;
let res = Function(code).apply(thisArg, args);
e.source.postMessage(res, e.origin);
}, false);
Web Workers可以做同样的事情。
如果是这种情况,您可以更进一步将其发送到您的服务器。如果您正在运行节点,则可以通过vm module安全地运行任意脚本。在Java下你有像Rhino和Nashorn这样的项目;我敢肯定.Net有自己的实现(甚至可以像JScript一样运行它!)并且可能在php中实现了大量破解的javascript VM。
如果您可以 ,为什么不使用像Runnable这样的服务来动态创建javascript沙箱,甚至可以为此设置自己的服务器端环境。 / p>