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
.