簡體   English   中英

是否有一種安全的方法來調用`call`來調用JavaScript中的函數?

[英]Is there a safe way to call `call` to call a function in JavaScript?

我想用自定義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)

這就是鴨子打字的力量(和風險): if typeof func.call === 'function' ,那么你應該把它看作是一個正常的,可調用的函數。 func提供者來確保他們的call屬性與公共簽名匹配。 我實際上在一些地方使用它,因為JS沒有提供重載()運算符並提供經典仿函數的方法。

如果你真的需要避免使用func.call ,我會使用func()並要求functhisArg作為第一個參數。 因為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);
}

在某些時候,你必須相信窗口上可用的內容。 它要么意味着緩存您計划使用的功能,要么嘗試對代碼進行沙箱處理。

呼叫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);

safeCallsandboxCallsandboxCall受到對Function.prototype.call 未來更改的影響,但正如您所看到的,它們依賴於某些現有的全局函數來在運行時工作。 如果惡意腳本此代碼之前執行,則您的代碼仍然容易受到攻擊。

如果您信任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模塊安全地運行任意腳本。 在Java下你有像Rhino和Nashorn這樣的項目; 我確定.Net有自己的實現(甚至可以像JScript一樣運行它!)並且可能在php中實現了大量破解的javascript VM。

如果你能做到這一點 ,為什么不使用像Runnable這樣的服務來動態創建javascript沙箱,甚至可以設置你自己的服務器端環境。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM