是否可以以外部作用域不可见的方式声明/调用函数

Is it possible to declare/call a function in such a way that outer scopes are not visible?

本文关键字:声明 函数 方式 调用 外部 作用域 是否      更新时间:2023-09-26

假设您有以下代码:

var something = "here";
function test(){
   console.log(something)
}

显然,当您调用test()时,控制台日志将显示here,因为它将通过作用域链向上移动,直到找到something或到达链的末端。但是有没有一种方法可以清除作用域链,这样它就不会在本地作用域之外寻找变量。

我知道在不使用严格模式的情况下,可以使用with来添加到作用域链中,所以有没有相反的方法,从链中删除作用域,或者用其他方法使外部作用域不可访问。

我能想到的最接近实现这一点的方法是将函数体和任何参数一起传递给网络工作者,并在工作者中执行它,并将结果发回。当然,这是额外的开销,实现这样的目标有点过于复杂。

实际上并不需要这个,也不知道它的使用情况,但认为看看它是否可能会很有趣。

正如问题中所说,我想做这件事的唯一方法是与网络工作者合作。Jeremy J Starcher在评论中提到了使用iframe,直到提到这一点我才想到。因此,也提供了一个例子。

Web Worker示例

  • 所有需要的数据都需要作为参数传递
  • 浏览器需要支持
    • 网络工作者(当然)
    • 结构化克隆,在本例中使用
      • 参数必须是可克隆的
    • 可传输对象,本例中未使用
    • 如果以上两者都不受支持,则需要对字符串的参数进行(反)序列化
  • 需要使用回调,因为工作进程是异步的(是否使用promise?)
  • 可以修改以允许将函数绑定到某个上下文,但这需要上下文能够作为参数传递,还没有尝试过

主javascript

(function(window){
    'use strict';
    function respond(oEvent){
        this.cb&&this.cb(oEvent.data);
        this.worker.terminate();
    }
    window.sandbox = function(fn){
        return function(){
            var worker = new Worker("sandboxWorker.js");
            worker.onmessage = respond.bind({
                worker:worker,
                cb:[].pop.call(arguments)
            });
            worker.postMessage({
                "fn":fn.toString(),
                "args":[].slice.call(arguments,0)
            });
        };
    };
})(window);

sandboxWorker.js

onmessage = function (oEvent) {
    var fn = eval("("+oEvent.data.fn+")");
    var res = fn.apply(null,oEvent.data.args);
    postMessage(res);
};

测试

//Assume jQuery has been included
function maliciousAdd(a,b){ 
    jQuery = function(){ alert("No it doesn't fix everything"); } 
    return a+b;
}
var sandBoxed = window.sandbox(maliciousAdd);
sandBoxed(1,2,function(result){
    jQuery(document.body).css("background","#3F3");
    console.log(result);
});

JSFiddle演示

IFrame示例

  • 若来源相同,函数仍然可以访问父窗口等
  • 如果使用不同的来源,则必须使用postMessage
  • 在同源iframe的情况下,不需要(反)序列化/克隆参数
  • 同步,但也可以使其异步

Javascript:根据Felix Kling 的建议,修改为使用Function生成内部沙箱函数

window.onload = function(){
    var frame = document.createElement("iframe");
    frame.src = "";
    frame.style.display = "none";
    window.document.body.appendChild(frame);
    var win = (frame.contentWindow) ? frame.contentWindow : (frame.contentDocument.document) ? frame.contentDocument.document : frame.contentDocument;
    window.sandbox = function(fn){
        return function(){
            var args = [].slice.call(arguments);
            win.location.reload();
            win.sandbox = win.Function(
               'fn','args',
               'fn = eval("("+fn+")"); return fn.apply(this,args);'
            );
            win.document.head.appendChild(script);
            return win.sandbox(fn.toString(),args);
        };
    };
};

JSFiddle演示

文档

  • postMessage(兼容性)
  • 使用Web Worker(兼容性)
  • 结构化克隆算法
  • 可传输对象