我认为您实际上 可以 ,即使不使用 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)];
});
}
prop和props函数可以作为返回对象中的方法使用,最终它可能看起来像这样:
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
读/写它们.