Array.prototype.slice.call()如何工作?


474

我知道它用于使参数成为真实的数组,但是我不明白使用时会发生什么 Array.prototype.slice.call(arguments)


2
^稍有不同,因为该链接询问的是DOM节点,而不是参数。而且我认为这里的答案通过描述内部“这”要好得多。
sqram 2015年

Answers:


870

在幕后发生的事情是,当.slice()通常被调用时,它this是一个Array,然后它仅在该Array上进行迭代并完成其工作。

如何this.slice()功能的阵列?因为当您这样做时:

object.method();

...该object自动成为价值thismethod()。因此:

[1,2,3].slice()

...将[1,2,3]Array设置为thisin 的值.slice()


但是,如果您可以用其他东西代替this价值呢?只要您替换的任何东西都具有数字.length属性,以及一堆作为数字索引的属性,它就应该起作用。这种类型的对象通常称为类数组对象

.call().apply()方法让你手动设置的值this的函数。因此,如果我们的值设置this.slice()一个阵列状物体.slice()只会认为它的工作与Array,并会做它的东西。

以这个简单的对象为例。

var my_object = {
    '0': 'zero',
    '1': 'one',
    '2': 'two',
    '3': 'three',
    '4': 'four',
    length: 5
};

显然这不是一个Array,但是如果您可以将其设置为的this.slice(),那么它将正常工作,因为它看起来像Array一样.slice()可以正常工作。

var sliced = Array.prototype.slice.call( my_object, 3 );

示例: http //jsfiddle.net/wSvkv/

如您在控制台中看到的,结果就是我们所期望的:

['three','four'];

因此,当您将arguments对象设置为的this值时,就会发生这种情况.slice()。因为arguments具有一个.length属性和一堆数字索引,所以.slice()就像在真正的数组上工作一样进行工作。


7
好答案!但是可悲的是,如果您的对象键是字符串值(如实际单词中那样),则不能以这种方式转换任何对象。这将失败,因此请保持对象内容为'0':'value'而不是'stringName' :'值'。
joopmicroop 2013年

6
@Michael:可以读取开放源JS实现的源代码,但是仅参考“ ECMAScript”语言规范会更简单。这里有一个链接Array.prototype.slice方法的说明。

1
由于对象键没有顺序,因此此特定的演示在其他浏览器中可能会失败。并非所有供应商都按创建顺序对对象的键进行排序。
vsync

1
@vsync:这for-in是不保证顺序的语句。所使用的算法.slice()定义了以给定对象(或Array或其他对象0)的开头和结尾(不包括)的数字顺序.length。因此,可以保证顺序在所有实现中都是一致的。
Cookie怪物

7
@vsync:这不是一个假设。如果强制执行,则可以从任何对象获取订单。假设我有var obj = {2:"two", 0:"zero", 1: "one"}。如果使用for-in枚举对象,则无法保证顺序。但是,如果使用for,我们可以手动执行以下命令:for (var i = 0; i < 3; i++) { console.log(obj[i]); }。现在我们知道对象的属性将以for循环定义的升序数字顺序到达。就是.slice()那样 不管它是否有实际的数组。它始于0并以递增循环访问属性。
Cookie怪物

88

arguments对象实际上不是Array的实例,并且不具有任何Array方法。所以,arguments.slice(...)将不会起作用,因为arguments对象没有slice方法。

数组确实具有此方法,并且由于arguments对象与数组非常相似,因此两者是兼容的。这意味着我们可以将数组方法与arguments对象一起使用。并且由于数组方法是在考虑数组的基础上构建的,因此它们将返回数组而不是其他参数对象。

那为什么要使用Array.prototype呢?的Array是,我们创建从(新阵列的对象new Array()),而这些新的阵列被传递的方法和属性,像切片。这些方法存储在[Class].prototype对象中。因此,出于效率的考虑,我们无需从原型中直接获取切片方法,而无需通过(new Array()).slice.call()or 访问slice方法[].slice.call()。这样一来,我们不必初始化新的数组。

但是,为什么我们必须首先这样做呢?好了,正如您所说,它将参数对象转换为Array实例。但是,我们使用slice的原因更多是“ hack”。您猜到了,slice方法将获取一个数组的切片,并将该切片作为新数组返回。不向其传递任何参数(以arguments对象作为上下文除外)会使slice方法获取传递的“数组”(在本例中为arguments对象)的完整块,并将其作为新数组返回。


您可能由于以下原因而使用切片:jspatterns.com/arguments-considered-harmful
KooiInc 2011年

44

通常,打电话

var b = a.slice();

将数组复制a到中b。但是,我们做不到

var a = arguments.slice();

因为arguments它不是一个真正的数组,也没有slice作为方法。Array.prototype.sliceslice数组的函数,并callthis设置运行函数arguments


2
thanx但为什么使用prototype?不是slice本机Array方法吗?
ilyo 2011年

2
请注意,这Array是一个构造函数,而对应的“类”为Array.prototype。您也可以使用[].slice
user123444555621 2011年

4
IlyaD slice是每个Array实例的方法,但不是Array构造函数。您用于prototype访问构造函数的理论实例的方法。
Delan Azabani 2011年

23

首先,您应该阅读JavaScript中函数调用的工作方式。我怀疑仅此一项就足以回答您的问题。但是,这里是正在发生的事情的摘要:

Array.prototype.slice提取方法从的原型。但是直接调用它是行不通的,因为它是一个方法(不是函数),因此需要一个上下文(一个调用对象,),否则它将抛出slice ArraythisUncaught TypeError: Array.prototype.slice called on null or undefined

call()方法允许您指定方法的上下文,基本上使这两个调用等效:

someObject.slice(1, 2);
slice.call(someObject, 1, 2);

除非前者要求slice方法存在于someObject原型链中(就像它所做的那样Array),而后者则允许将上下文(someObject)手动传递给该方法。

另外,后者的缩写为:

var slice = Array.prototype.slice;
slice.call(someObject, 1, 2);

与以下内容相同:

Array.prototype.slice.call(someObject, 1, 2);

22
// We can apply `slice` from  `Array.prototype`:
Array.prototype.slice.call([]); //-> []

// Since `slice` is available on an array's prototype chain,
'slice' in []; //-> true
[].slice === Array.prototype.slice; //-> true

// … we can just invoke it directly:
[].slice(); //-> []

// `arguments` has no `slice` method
'slice' in arguments; //-> false

// … but we can apply it the same way:
Array.prototype.slice.call(arguments); //-> […]

// In fact, though `slice` belongs to `Array.prototype`,
// it can operate on any array-like object:
Array.prototype.slice.call({0: 1, length: 1}); //-> [1]


9

正如MDN所述,这是因为

arguments对象不是数组。它类似于数组,但除长度外没有任何数组属性。例如,它没有pop方法。但是可以将其转换为实数数组:

在这里,我们在调用slice本机对象,Array而不是在其实现上,这就是为什么额外的.prototype

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

4

不要忘记,这种行为的底层基础是完全集成在JS引擎中的类型转换。

切片仅接受对象(由于现有的arguments.length属性),并在对该对象进行所有操作后返回强制转换的数组对象。

如果尝试使用INT值处理String方法,则可以测试相同的逻辑:

String.prototype.bold.call(11);  // returns "<b>11</b>"

这就解释了上面的陈述。


1

它使用slice方法数组has并以其thisarguments对象的方式对其进行调用。这意味着它就像您arguments.slice()假设arguments具有此方法一样调用它。

创建不带任何参数的切片将仅占用所有元素-因此它仅将元素从复制arguments到数组中。


1

假设您有: function.apply(thisArg, argArray )

apply方法调用一个函数,传入将绑定到此函数的对象以及一个可选的参数数组。

slice()方法选择数组的一部分,然后返回新数组。

因此,当您调用Array.prototype.slice.apply(arguments, [0])数组切片方法时,将对参数调用(绑定)。


1

也许有点晚了,但是所有这些混乱的答案是,在JS中将call()用于继承。例如,如果将其与Python或PHP进行比较,则将call分别用作super()。init()或parent :: _ construct()。

这是其用法的一个示例,该示例澄清了所有问题:

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

参考:https : //developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Inheritance


0

当正常调用.slice()时,这是一个数组,然后仅对该数组进行迭代并完成其工作。

 //ARGUMENTS
function func(){
  console.log(arguments);//[1, 2, 3, 4]

  //var arrArguments = arguments.slice();//Uncaught TypeError: undefined is not a function
  var arrArguments = [].slice.call(arguments);//cp array with explicity THIS  
  arrArguments.push('new');
  console.log(arrArguments)
}
func(1,2,3,4)//[1, 2, 3, 4, "new"]

-1

我只是为了提醒自己...

    Array.prototype.slice.call(arguments);
==  Array.prototype.slice(arguments[1], arguments[2], arguments[3], ...)
==  [ arguments[1], arguments[2], arguments[3], ... ]

或者只是使用这个方便的函数$ A将大多数东西变成一个数组。

function hasArrayNature(a) {
    return !!a && (typeof a == "object" || typeof a == "function") && "length" in a && !("setInterval" in a) && (Object.prototype.toString.call(a) === "[object Array]" || "callee" in a || "item" in a);
}

function $A(b) {
    if (!hasArrayNature(b)) return [ b ];
    if (b.item) {
        var a = b.length, c = new Array(a);
        while (a--) c[a] = b[a];
        return c;
    }
    return Array.prototype.slice.call(b);
}

用法示例...

function test() {
    $A( arguments ).forEach( function(arg) {
        console.log("Argument: " + arg);
    });
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.