如何在javascript中动态访问本地范围?

IT技术 javascript scope
2021-01-27 15:23:43

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

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]
4个回答

要回答您的问题,不,没有办法在本地范围内进行动态变量查找而不使用eval().

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

不,就像新月说的那样。下面是一个示例,说明如何在没有 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一样,但是当你不知道当地人是什么时......
2021-03-16 15:23:43
@Michael 对不起,我不明白你的意思。你能举个例子吗?或者甚至写你自己的答案?
2021-03-19 15:23:43
@Michael 你能举个例子说明什么时候有用,什么地方不能使用对象吗?
2021-04-04 15:23:43
我没有答案,我读过的一切似乎都表明这是不可能的......浏览器中的全局变量可以用类似的东西枚举for (i in window); 如果这对函数中的本地人来说是可能的,那就太好了。
2021-04-08 15:23:43
在某些情况下,它对于内省很有用,例如序列化闭包。
2021-04-08 15:23:43

我想到你居然 排序 就可以了,即使不使用eval!

我可能错了,所以如果我错了,请纠正我,但我发现如果私有变量在本地范围内作为参数声明,而不是使用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)];
    });
} 

propsprops的功能,可以提供作为返回的对象内部方法,它到底会是这个样子:

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.

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

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 有什么意义呢?
2021-03-13 15:23:43
没有一个。我刚刚添加了这一点,因此用法与安南的示例相同。我同意,在实践中这将是完全愚蠢的。
2021-03-21 15:23:43
代码的原因是模拟 getter 和 setter 如何以跨浏览器的方式工作。因此,我的原始代码不正确(现已修复),因为创建的变量 prop() 对对象的其余部分不可用。您的方式允许直接访问和操作属性。
2021-03-21 15:23:43
阅读其他人的代码是一个很好的理由。即使公开暴露的 'someProperty' 也没有告诉我某些东西是否打算被任何旧函数使用,或者对象的作者是否希望另一个对象改变它。
2021-04-08 15:23:43