mackxu
子曰:学而时习之,不亦说乎?

案例复现

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个有效方法

posted on 2014-03-20 00:17  mackxu  阅读(410)  评论(0编辑  收藏  举报