admin管理员组文章数量:1332395
I want to call a function with a custom thisArg
.
That seems trivial, I just have to call call
:
func.call(thisArg, arg1, arg2, arg3);
But wait! func.call
might not be Function.prototype.call
.
So I thought about using
Function.prototype.call.call(func, thisArg, arg1, arg2, arg3);
But wait! Function.prototype.call.call
might not be Function.prototype.call
.
So, assuming Function.prototype.call
is the native one, but considering arbitrary non-internal properties might have been added to it, does ECMAScript provide a safe way in to do the following?
func.[[Call]](thisArg, argumentsList)
I want to call a function with a custom thisArg
.
That seems trivial, I just have to call call
:
func.call(thisArg, arg1, arg2, arg3);
But wait! func.call
might not be Function.prototype.call
.
So I thought about using
Function.prototype.call.call(func, thisArg, arg1, arg2, arg3);
But wait! Function.prototype.call.call
might not be Function.prototype.call
.
So, assuming Function.prototype.call
is the native one, but considering arbitrary non-internal properties might have been added to it, does ECMAScript provide a safe way in to do the following?
func.[[Call]](thisArg, argumentsList)
Share
Improve this question
asked Jun 26, 2015 at 15:18
OriolOriol
289k70 gold badges457 silver badges530 bronze badges
1
- 4 Took me 3 readings... but good question! – James Thorpe Commented Jun 26, 2015 at 15:20
3 Answers
Reset to default 4That's the power (and risk) of duck typing: if typeof func.call === 'function'
, then you ought to treat it as if it were a normal, callable function. It's up to the provider of func
to make sure their call
property matches the public signature. I actually use this in a few place, since JS doesn't provide a way to overload the ()
operator and provide a classic functor.
If you really need to avoid using func.call
, I would go with func()
and require func
to take thisArg
as the first argument. Since func()
doesn't delegate to call
(i.e., f(g, h)
doesn't desugar to f.call(t, g, h)
) and you can use variables on the left side of parens, it will give you predictable results.
You could also cache a reference to Function.prototype.call
when your library is loaded, in case it gets replaced later, and use that to invoke functions later. This is a pattern used by lodash/underscore to grab native array methods, but doesn't provide any actual guarantee you'll be getting the original native call method. It can get pretty close and isn't horribly ugly:
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);
This is a fundamental problem in any language with polymorphism. At some point, you have to trust the object or library to behave according to its contract. As with any other case, trust but verify:
if (typeof duck.call === 'function') {
func.call(thisArg, ...args);
}
With type checking, you can do some error handling as well:
try {
func.call(thisArg, ...args);
} catch (e) {
if (e instanceof TypeError) {
// probably not actually a function
} else {
throw e;
}
}
If you can sacrifice thisArg
(or force it to be an actual argument), then you can type-check and invoke with parens:
if (func instanceof Function) {
func(...args);
}
At some point you have to trust what's available on the window. It either means caching the functions you're planning on using, or attempting to sandbox your code.
The "simple" solution to calling call
is to temporarily set a property:
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)));
This can then be used as:
safeCall(fn, ctx, ...params);
Be aware that the parameters passed to safeCall will be lumped together into an array. You'd need apply
to get that to behave correctly, and I'm just trying to simplify dependencies here.
improved version of safeCall
adding a dependency to 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)));
This can be used as:
safeCall(fn, ctx, ...params);
An alternative solution to safely calling call is to use functions from a different window context.
This can be done simply by creating a new iframe
and grabbing functions from its window. You'll still need to assume some amount of dependency on DOM manipulation functions being available, but that happens as a setup step, so that any future changes won't affect the existing script:
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;
}());
This can then be used as:
sandboxCall.call(fn, ctx, ...params);
Both safeCall
and sandboxCall
are safe from future changes to Function.prototype.call
, but as you can see they rely on some existing global functions to work at runtime. If a malicious script executes before this code, your code will still be vulnerable.
If you trust Function.prototype.call
, you can do something like this:
func.superSecureCallISwear = Function.prototype.call;
func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */);
If you trust Function..call
but not Function..call.call
, you can do this:
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;
And maybe even wrap that in a helper.
If your functions are pure and your objects serializable, you can create an iframe and via message passing (window.postMessage
), pass it the function code and the arguments, let it do the call
for you (since it's a new iframe without any 3rd party code you're pretty safe), and you're golden, something like (not tested at all, probably riddled with errors):
// 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);
Same thing can be done with Web Workers.
If that's the case though, you can take it a step further and send it over to your server. If you're running node you can run arbitrary scripts rather safely via the vm module. Under Java you have projects like Rhino and Nashorn; I'm sure .Net has its own implementations (maybe even run it as JScript!) and there're probably a bazillion broken javascript VMs implemented in php.
If you can do that, why not use a service like Runnable to on-the-fly create javascript sandboxes, maybe even set your own server-side environment for that.
本文标签: Is there a safe way to call call to call a function in JavaScriptStack Overflow
版权声明:本文标题:Is there a safe way to call `call` to call a function in JavaScript? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742282240a2446305.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论