【问题标题】:How can I access local scope dynamically in javascript?如何在javascript中动态访问本地范围?
【发布时间】:2010-10-10 13:51:26
【问题描述】:

如果你想动态使用全局函数和变量,你可以使用:

window[functionName](window[varName]);

是否可以对局部范围内的变量做同样的事情?

这段代码可以正常工作,但目前使用的是 eval,我正在考虑其他方法。

var test = function(){
    //this = window
    var a, b, c; //private variables

    var prop = function(name, def){
        //this = window
        eval(name+ ' = ' + (def.toSource() || undefined) + ';');    

        return function(value){
            //this = test object
            if ( !value) {
                return eval('(' + name + ')');
            }
            eval(name + ' = value;')
            return this;
        };

    };

    return {
        a:prop('a', 1),
        b:prop('b', 2),
        c:prop('c', 3),
        d:function(){
            //to show that they are accessible via to methods
            return [a,b,c];
        }
    };
}();

>>>test
Object
>>>test.prop
undefined
>>>test.a
function()
>>>test.a()
1 //returns the default
>>>test.a(123)
Object //returns the object
>>>test.a()
123 //returns the changed private variable
>>>test.d()
[123,2,3]

【问题讨论】:

  • 我明白为什么我现在感到困惑了,这是因为您将变量命名为 abc 以及返回的名称。你应该改变那些名字,这让我很困惑。无论如何,已经出现了比我更好的答案,所以我现在只能推迟到 crescentfresh。
  • @Gothdo 您链接到的问题是在问一些不同的问题。他试图访问的变量是全局变量。接受的答案也使用全局变量。应该更改链接的问题。

标签: javascript scope


【解决方案1】:

要回答您的问题,不,如果不使用eval(),就无法在本地范围内进行动态变量查找。

最好的选择是让你的“范围”只是一个普通的对象[字面](即"{}"),并将你的数据粘贴在那里。

【讨论】:

    【解决方案2】:

    不,就像crescentfresh 说的那样。下面是一个示例,说明如何在没有 eval 的情况下使用内部私有对象来实现。

    var test = function () {
      var prv={ };
      function prop(name, def) {
        prv[name] = def;
        return function(value) {
          // if (!value) is true for 'undefined', 'null', '0', NaN, '' (empty string) and false.
          // I assume you wanted undefined. If you also want null add: || value===null
          // Another way is to check arguments.length to get how many parameters was
          // given to this function when it was called.
          if (typeof value === "undefined"){
            //check if hasOwnProperty so you don't unexpected results from
            //the objects prototype.
            return Object.prototype.hasOwnProperty.call(prv,name) ? prv[name] : undefined;
          }
          prv[name]=value;
          return this;
        }
      };
    
      return pub = {
        a:prop('a', 1),
        b:prop('b', 2),
        c:prop('c', 3),
        d:function(){
          //to show that they are accessible via two methods
          //This is a case where 'with' could be used since it only reads from the object.
          return [prv.a,prv.b,prv.c];
        }
      };
    }();
    

    【讨论】:

    • 在函数本身中枚举局部变量呢?换句话说,像 eval,但是当你不知道本地人是什么时......
    • @Michael 对不起,我不明白你的意思。你能举个例子吗?或者甚至可以自己写答案?
    • 我没有答案,我读过的所有内容似乎都表明这是不可能的......浏览器中的全局变量可以用for (i in window)之类的东西枚举;如果函数中的本地人可以这样做,那就太好了。
    • @Michael 你能举个例子说明什么时候有用,什么时候不能使用对象吗?
    • 它在某些情况下对自省很有用,例如序列化闭包。
    【解决方案3】:

    我认为你实际上 有点 可以,即使不使用 eval!

    我可能是错的所以如果我错了请纠正我,但我发现如果 private 变量在本地范围内声明为参数,而不是使用var,即:

    function (a, b, c) { ...

    而不是

    function () { var a, b, c; ...

    这意味着如果在函数调用中为它们提供了任何值,则这些变量/参数将与函数的arguments 对象绑定在一起,即:

    function foo (bar) {
        arguments[0] = 'changed...';
        console.log(bar); // prints 'changed...'
    
        bar = '...yet again!';
        console.log(arguments[0]); // prints '..yet again!'
    }
    
    foo('unchanged'); // it works (the bound is created)
    // logs 'changed...'
    // logs '...yet again!'
    
    foo(undefined); // it works (the bound is created)
    // logs 'changed...'
    // logs '...yet again!'
    
    foo(); // it doesn't work if you invoke the function without the 'bar' argument
    // logs undefined
    // logs 'changed...'
    

    在那些情况下(它可以工作),如果您以某种方式存储/保存被调用函数的 arguments 对象,那么您可以从 arguments 对象更改任何与参数相关的插槽,并且更改将自动反映在变量本身中,即:

    // using your code as an example, but changing it so it applies this principle
    var test = function (a, b, c) {
        //this = window
        var args = arguments, // preserving arguments as args, so we can access it inside prop
            prop = function (i, def) {
                //this = window
    
                // I've removed .toSource because I couldn't apply it on my tests
                //eval(name+ ' = ' + (def.toSource() || undefined) + ';');
                args[i] = def || undefined;   
    
                return function (value) {
                    //this = test object
                    if (!value) {
                        //return eval('(' + name + ')');
                        return args[i];
                    }
    
                    //eval(name + ' = value;');
                    args[i] = value;
                    return this;
                };
            };
    
        return {
            a: prop(0, 1),
            b: prop(1, 2),
            c: prop(2, 3),
            d: function () {
                // to show that they are accessible via to methods
                return [a, b, c];
            }
        };
    }(0, 0, 0);
    

    如果您可以将值作为参数传递给函数这一事实让您感到烦恼,您总是可以用另一个匿名函数包装它,这样您就真的无法访问作为参数传递的第一个定义的值,即:

    var test = (function () {
        // wrapping the function with another anomymous one
        return (function (a, b, c) {
            var args = arguments, 
                prop = function (i, def) {
                    args[i] = def || undefined;   
    
                    return function (value) {
                        if (!value) {
                            return args[i];
                        }
    
                        args[i] = value;
                        return this;
                    };
                };
    
            return {
                a: prop(0, 1),
                b: prop(1, 2),
                c: prop(2, 3),
                d: function () {
                    return [a, b, c];
                }
            };
        })(0, 0, 0);
    })();
    

    完全动态访问示例

    我们可以通过将函数本身 (arguments.callee) 作为字符串并使用正则表达式过滤其参数来将所有参数变量名称映射到一个数组中:

    var argsIdx = (arguments.callee + '').replace(/function(\s|\t)*?\((.*?)\)(.|\n)*/, '$2').replace(/(\s|\t)+/g, '').split(',')
    

    现在有了数组中的所有变量,我们现在可以知道每个函数的arguments槽索引对应的变量名,然后声明一个函数(在我们的例子中是prop) 读/写变量:

    function prop (name, value) {
        var i = argsIdx.indexOf(name);
    
        if (i === -1) throw name + ' is not a local.';
        if (arguments.hasOwnProperty(1)) args[i] = value;
    
        return args[i];
    }
    

    我们还可以动态地将每个变量添加为属性,就像问题示例中一样:

    argsIdx.forEach(function (name, i) {
        result[name] = prop.bind(null, name);
    });
    

    最后我们可以添加一个按名称检索变量的方法(默认情况下全部),如果true作为第一个参数传递,它会返回硬编码数组,其中包含所有变量的标识符,以证明他们正在改变:

    function props (flgIdent) {
        var names = [].slice.call(arguments.length > 0 ? arguments : argsIdx);
    
        return flgIdent === true ? [a, b, c, d, e, f] : names.map(function (name) {
            return args[argsIdx.indexOf(name)];
        });
    } 
    

    propprops 函数可以作为返回对象中的方法使用,最终它可能看起来像这样:

    var test = (function () {
        return (function (a, b, c, d, e, f) { 
            var argsIdx = (arguments.callee + '').replace(/function(\s|\t)*?\((.*?)\)(.|\n)*/, '$2').replace(/(\s|\t)+/g, '').split(','), 
                args = arguments, 
                result = {
                    prop: function (name, value) {
                        var i = argsIdx.indexOf(name);
    
                        if (i === -1) throw name + ' is not a local.';
                        if (arguments.hasOwnProperty(1)) args[i] = value;
    
                        return args[i];
                    },
                    props: function (flgIdent) {
                        var names = [].slice.call(arguments.length > 0 ? arguments : argsIdx);
    
                        return flgIdent === true ? [a, b, c, d, e, f] : names.map(function (name) {
                            return args[argsIdx.indexOf(name)];
                        });
                    }
                };
    
            args.length = argsIdx.length;
            argsIdx.forEach(function (name, i) {
                result[name] = result.prop.bind(null, name);
            });
    
            return result;
        })(0, 0, 0, 0, 0, 0);
    })();
    

    结论

    没有 eval 就不可能读/写函数的局部作用域变量,但是如果这些变量是函数的参数并且给定了值,您可以绑定这些变量函数的arguments 对象的变量标识符并间接arguments 对象本身读取/写入它们。

    【讨论】:

      【解决方案4】:

      希望我没有过度简化,但是像使用对象这样简单的事情呢?

      var test = {
          getValue : function(localName){
              return this[localName];
          },
          setValue : function(localName, value){
              return this[localName] = value;
          }
      };
      
      >>> test.a = 123
      >>> test.getValue('a')
      123
      >>> test.a
      123
      
      >>> test.setValue('b', 999)
      999
      >>> test.b
      999
      

      【讨论】:

      • 如果你只想直接访问对象的局部变量,那么定义 getter 或 setter 有什么意义呢?
      • 没有。我刚刚添加了这一点,因此用法与安南的示例相同。我同意,这在实践中完全是愚蠢的。
      • 编写代码的原因是为了模拟 getter 和 setter 如何以跨浏览器的方式工作。因此,我的原始代码不正确(现已修复),因为创建的变量 prop() 不适用于对象的其余部分。您的方式允许直接访问和操作属性。
      • 阅读别人的代码是一个很好的理由。即使公开暴露的 'someProperty' 并没有告诉我某些东西是否打算被任何旧函数使用,或者对象的作者是否期望另一个对象来更改它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-19
      • 2016-08-01
      相关资源
      最近更新 更多