JavaScript的slice和splice方法的一个问题

24

我遇到了以下代码:

var f = function () {
    var args = Array.prototype.slice.call(arguments).splice(1);

    // some more code 
};

基本上,args 中的结果是 arguments 的副本,不包含其第一个元素。

但我不明白的是为什么将 farguments(它是将函数输入参数转换为类数组对象的对象)对象传递给 slice 方法,以及如何使用 slice(1) 移除第一个元素(位置位于索引0处)。

请问有人可以为我解释一下吗?

P.S. 这段代码来自于偏应用函数


Array.splice(start, deleteCount) 实际上会删除元素,重新索引数组并更改其长度。 - Shaik Md N Rasool
3个回答

41

<注>
那个链接的答案中的实际代码是:

var args = Array.prototype.slice.call(arguments, 1);

首先,slice方法通常用于复制调用它的数组

i.e. "slice",而不是"splice"
</Note>

var a = ['a', 'b', 'c'];
var b = a.slice();  // b is now a copy of a
var c = a.slice(1); // c is now ['b', 'c']

所以简短的回答是,代码基本上是在模拟:

arguments.slice(1); // discard 1st argument, gimme the rest

然而,你不能直接这样做。特殊的arguments对象(在所有JavaScript函数的执行上下文中可用),虽然类似于数组,支持通过数值键使用[]运算符进行索引,但实际上并不是一个数组;你不能对其进行.push.pop.slice等操作。
代码实现的方法是通过“欺骗”slice函数(再次强调,该函数不适用于arguments对象)通过Function.prototype.callarguments的上下文中运行:
Array.prototype.slice // get a reference to the slice method
                      // available on all Arrays, then...
  .call(              // call it, ...
    arguments,        // making "this" point to arguments inside slice, and...
    1                 // pass 1 to slice as the first argument
  )

Array.prototype.slice.call(arguments).splice(1)实现了相同的功能,但是会多调用一次splice(1),这会导致从Array.prototype.slice.call(arguments)返回的数组中从索引1开始到数组末尾的元素被移除。在IE中,splice(1)无法正常工作(它需要一个第二个参数来告诉它要删除多少项,而IE和ECMAScript都要求提供该参数)。


抱歉代码有所不同,但我复制了我在此问题中链接到的答案中粘贴的代码https://dev59.com/23RC5IYBdhLWcg3wOOP1,但作者在此期间更改了脚本。 再次抱歉。 - Andreas Grech
@Crescent Flesh,我已经更改了问题中的链接,指向原始代码。 - Andreas Grech
新鲜如月牙,真实可感。@Andreas,是的,我看到了。我相信我的结束段落也涉及到了splice(1),对吧? - Crescent Fresh
有些事情让我感到困扰,我必须说出来:slice()应该带一个参数。虽然今天在所有浏览器中都可以不带参数使用,这就是为什么我在我的答案中保留它的原因。但从技术上讲,最后一个例子Array.prototype.slice.call(arguments)应该是Array.prototype.slice.call(arguments, 0)(即将0作为切片函数的第一个参数传递)。如果省略,则默认为0(同样,在所有浏览器中)。 - Crescent Fresh
这是我最近记忆中最详细、最逐步分解代码原理的最佳教程。厉害的东西,谢谢。 - SimplGy
+1 很棒的答案。顺便说一下,“特殊参数对象”链接无法使用。也许它已经移动到这里:https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope/arguments - user405398

3
var args = Array.prototype.slice.call(arguments).splice(1);

首先复制了arguments(*),然后以一种非标准的方式从中删除除第一个项之外的所有内容,并将这些被删除的项赋值给args

生成的额外数组随后被修改并丢弃,这是相当多余的。最好像您链接到的答案版本那样说:

var args = Array.prototype.slice.call(arguments, 1);

部分函数应用也是function.bind方法的一个特性,在ECMAScript第五版中得到了标准化。在浏览器实现该特性之前,您可以从此答案底部获取JS本地版本作为备选方案。

*:array.slice()是复制数组的常规习语,而array.slice(1)是获取尾巴的常规习语。它必须通过Array.prototype显式调用,因为arguments不是一个数组,即使它看起来像一个数组,所以没有正常的数组方法。这是JavaScript的另一个奇怪错误。

您经常会看到人们在不是数组的对象上使用Array.prototype方法。ECMAScript第三版标准极力强调,这对类似于arguments的类数组是可以的,但不建议在其他可能是宿主对象的类数组上这样做,例如NodeList或HTMLCollection。虽然在今天的许多浏览器中,在非数组上调用Array.prototype方法可能会有效,但唯一可以安全执行的地方是在arguments上。


ES5现在明确记录了许多Array.prototype方法可以用于任何类似数组的对象,因此可以放心依赖它继续工作。 - natevw

0
splice 方法返回一个由被删除元素组成的数组,而原始数组(或类似数组对象)则会被截断到 splice 的索引处。
使用 slice 创建一个副本可以保留原始参数数组,以便稍后在函数中使用。
在这种情况下,可以通过 args = [].slice.call(arguments, 1) 来获得相同的结果。
function handleArguments(){
 var A= [].slice.call(arguments).splice(1);
 //arguments is unchanged
 var s= 'A='+A+'\narguments.length='+arguments.length;

 var B= [].splice.call(arguments, 1);
 // arguments now contains only the first parameter
 s+= '\n\nB='+B+'\narguments.length='+arguments.length;
 return s;
}

// test
alert(handleArguments(1, 2, 3, 4));

returned value:
//var A= [].slice.call(arguments).splice(1);
A=2,3,4
arguments.length=4

//var B= [].splice.call(arguments, 1);
B=2,3,4
arguments.length=1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接