案例复现
var obj = {
plus: function(arg0, arg1) { return arg0 + arg1; }
};
function callMethod(context, method, arg0, arg1) {
var shift = [].shift;
shift.call(arguments);
shift.call(arguments);
// Cannot call method 'apply' of undefined
return context[method].apply(context, arguments);
}
var result = callMethod(obj, 'plus', 17, 2);
该函数出错的原因是arguments对象并不是函数参数的副本。要注意的是,所有命名参数都是arguments对象中对应索引的别名。因此,即使通过shift方法移除arguments对象中的元素之后,context仍然是arguments[0]的别名,method仍然是arguments[1]的别名。这意味着,我们想提取的是context['plus'],但结果是17[2]。此时一切开始失控了!
代码注释版
function callMethod(context, method, arg0, arg1) {
var shift = [].shift;
shift.call(arguments);
shift.call(arguments); // 此时的arguments: [1, 2]
// context是arguments[0]的别名,值为1
// 同理method的值为2
// Cannot call method 'apply' of undefined
return context[method].apply(context, arguments);
}
解决方案
我们可以通过复制arguments对象修复callMethod函数的实现。
// 解决方案
function callMethod(context, method, arg0, arg1) {
var args = [].slice.call(arguments, 2);
return context[method].apply(context, args);
}
总结
- 永远不要修改arguments对象
- 使用
[].slice.call(arguments)
将arguments对象复制到一个真正的数组中再进行修改
参考:编写高质量JavaScript代码的68个有效方法