【问题标题】:How to add properties to the current execution context dynamically in JavaScript?如何在 JavaScript 中动态地将属性添加到当前执行上下文中?
【发布时间】:2013-10-01 22:46:13
【问题描述】:

我正在阅读 JavaScript 语言的教科书。在研究闭包主题时,我提出了以下问题。

考虑这个功能:

function foo() {
    extractPropsToCurrentContext({'prop1' : 'hello', 'prop2' : 123});
}

我想要上面代码的结果,等于

function foo() {
    var prop1 = 'hello';
    var prop2 = 123;
}

所以,我的问题是,如何实现函数 extractPropsToCurrentContext(/* Object */) ?

澄清一下,我想将对象的这些属性提取到执行上下文中,而不是在“this”指针下。 (因此,那些提取的道具应该在该函数中私有)。

要澄清的另一件事是,您不能假设 foo 将使用 'new' 调用。 (如新的 foo())

更新:

我的意思是,我们有没有可能使用任何hacky技巧来绕过浏览器的限制,以更接近我们想要的结果?比如,几年前,我们发明了 JSONP 用于跨域、长拉用于消息推送等?

【问题讨论】:

  • 你有任何理由怀疑它实际上是可能的吗?有时我也想要同样的东西。但我不认为它可以做到。
  • 我还不知道。所以,这就是我在这里问这个问题的原因。也许有人对这个有趣的问题有任何想法。 @ScottSauyet
  • JavaScript 不会将lexical environments 暴露给您的代码(在某些情况下超出global environment,例如浏览器中的window 和Node.js 中的global)。因此,如果不构造声明语句和eval()'在它的目标范围内将它的引用传递给另一个函数以进行修改甚至创建局部变量,这是不可能的。
  • 但是我们有没有可能使用任何 hacky 技巧来绕过限制,以接近结果?比如,jsonp 用于跨域,长拉用于消息推送? @JonathanLonowski
  • 不确定它是否能回答您的问题,但您可以在所有函数中声明一个本地对象 var CurrentContext={},然后将所有属性附加到该对象。

标签: javascript


【解决方案1】:

我想将对象的那些属性提取到执行上下文中

execution context 实际上由三件事组成:

  • ThisBinding,您说您不想更改或扩展它。

  • VariableEnvironment,它保存声明的变量和函数。这就是您的相等代码将更改的内容。您可以使用以下 hack 对其进行更改:

    function getVariableDeclaration(obj) {
        return "var " + Object.keys(obj).map(function(name) {
            return name + " = " + JSON.stringify(obj[name]);
        }).join(",\n    ") + ";";
    }
    function foo() {
        eval(getVariableDeclaration({'prop1' : 'hello', 'prop2' : 123}));
        debugger;
    }
    foo();
    

    但是,这仅适用于非严格模式。详情请查看§10.4.2。此外,此 hack 目前仅限于 JSON 可序列化的值,如果您需要分配任意值,它会变得更加丑陋 - eval 必须在您想要更改的环境中使用。

  • LexicalEnvironment,它保存当前的标识符绑定(并且在执行过程中可能会更改in contrast to the VariableEnvironment)。这可能不是您想要的,但可以通过with 语句轻松修改:

    function foo() {
        with ({'prop1' : 'hello', 'prop2' : 123}) {
            debugger;
        }
    }
    foo();
    

    同样,它在严格模式下不起作用。请参阅 §12.10§10.2.2.3 了解它的工作原理和影响其他绑定。

如您所见,严格模式禁止对(非全局)执行上下文进行任何更改,因为这使得绑定成为非静态且不可优化的。大多数代码也变得更难理解,所以这通常被认为是一种不好的做法。

从学术角度思考这些 hack 是可以的(以了解语言的工作原理),但绝不应在生产中使用它们。无论您有什么想法需要这样的技术,都有更好的解决方案。

【讨论】:

  • 不要吓唬人。但是eval is evil,当输入来自不受信任的来源时。
  • @thefourtheye:嗯,getVariableDeclaration 绝对值得信赖,而且 JSON 不能包含可执行代码 :-)
【解决方案2】:

只是想分享我尴尬的解决方案。这个想法是从父函数的其余部分构造新函数并调用它 在eval

function cc(fnname, ctxname) {
    'use strict';
    var __evalfn = function () {
        'use strict';
        var __fn = __fnname,
            __ctx = __ctxname,
            __fnStr = __fn.toString(),
            __cccall = 'return eval(cc("'+__sfnname+'", "'+__sctxname+'"));',
            __cccallPos = __fnStr.indexOf(__cccall),
            __newFn = '(function () { var ',
            __decl = [], __restBody;
        if (__cccallPos === -1) { throw new Error("Can't find cc call"); }
        __restBody = __fnStr.slice(__cccallPos+__cccall.length);
        for (var __k in __ctx) {
            __decl.push(__k + '=' + __sctxname + '['+JSON.stringify(__k)+']');
        }
        __newFn += __decl.join(',') + ';';
        __newFn += __restBody;
        __newFn += ')()';
        return eval(__newFn);
    },
    __evalStr = __evalfn.toString()
                      .replace(/__fnname/g, fnname)
                      .replace(/__sfnname/g, JSON.stringify(fnname))
                      .replace(/__ctxname/g, ctxname)
                      .replace(/__sctxname/g, JSON.stringify(ctxname));
    return '('+__evalStr+')()';
}

var fn = function () {
    var a=10, ctx = {'b':10, 'c':'hello'};
    return eval(cc("fn", "ctx"));
    console.log(a,b,c);
    return a+b;
};

> fn();
> 10 10 "hello"
> <- 20

它将要求您将return eval(cc("fn", "ctx")); 放入您的函数中。 fn 是变量保持函数的名称,您在其中使用ccctx 是持有新绑定的对象的名称。 cc 的一个优点 是它允许 ctx 中的任何值,从数字和字符串到使用用户定义的构造函数创建的对象。

【讨论】:

  • return 之后的“不死”代码的有趣想法(虽然我不是很喜欢它):-)
  • 一些改进空间:您可以将__d 的名称作为另一个参数传递,就像您对fn 所做的那样 - 不需要固定名称。如果您确实使用了indexOf 而不是search,则不需要在cccall 中构造正则表达式。 k 始终是一个字符串,您无需担心。但是您应该在属性访问器中对其进行转义,例如通过JSON.stringify。难道__ret 变量不是不必要的吗?
  • @Bergi 谢谢你,我修改了。现在它使用了两个嵌套的 eval (-_-)
  • 哦,令人愉快!变量声明和restBody 在(替换的)__evalFn 的范围内是eval,这只是一个小缺点(使用debugger; 语句检查范围链)。也许你可以把它变成return__newFn(甚至没有IEFE?),然后是return '(eval('+__evalStr+'))';
猜你喜欢
  • 1970-01-01
  • 2011-01-15
  • 1970-01-01
  • 1970-01-01
  • 2017-12-06
  • 2010-09-20
  • 2012-01-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多