“Array.prototype.slice.call”是如何工作的

How does `Array.prototype.slice.call` work?

本文关键字:何工作 工作 prototype Array slice call      更新时间:2023-09-26

我知道它是用来使arguments成为真正的Array,但我不明白使用Array.prototype.slice.call(arguments);时会发生什么。

底层发生的事情是,当正常调用.slice()时,this是一个数组,然后它只是遍历该数组,并完成它的工作。

.slice()函数中的this如何成为数组?因为当你这样做时:

object.method();

object自动成为method()中的this值。所以有了:

[1,2,3].slice()

[1,2,3]数组设置为.slice()中的this值。


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

.call().apply() 方法允许您手动设置函数中this的值。因此,如果我们.slice() this的值设置为类似数组的对象.slice()只会假设它正在使用 Array,并且会做它的事情。

以这个普通对象为例。

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

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

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

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

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

['three','four'];

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

arguments 对象实际上不是 Array 的实例,并且没有任何 Array 方法。因此,arguments.slice(...)不起作用,因为参数对象没有切片方法。

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

那么为什么要使用Array.prototype呢?Array是我们从(new Array()(创建新数组的对象,这些新数组是传递的方法和属性,如slice。这些方法存储在[Class].prototype对象中。因此,为了提高效率,我们只是直接从原型中获取它,而不是通过(new Array()).slice.call()[].slice.call()访问切片方法。这样我们就不必初始化新数组了。

但是,为什么我们首先必须这样做呢?好吧,正如您所说,它将参数对象转换为 Array 实例。然而,我们使用切片的原因更像是一种"黑客",而不是任何东西。切片方法将采用数组的切片,并将该切片作为新数组返回。不向其传递任何参数(除了 arguments 对象作为其上下文(会导致 slice 方法获取传递的"数组"的完整块(在本例中为 arguments 对象(并将其作为新数组返回。

正常情况下,调用

var b = a.slice();

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

var a = arguments.slice();

因为arguments没有slice作为方法(它不是一个真正的数组(。

Array.prototype.slice是数组的slice函数。 .call运行此slice函数,this值设置为 arguments

Array.prototype.slice.call(arguments( 是将参数转换为数组的老式方法。

在 ECMAScript 2015 中,您可以使用 Array.from 或 spread 运算符:

let args = Array.from(arguments);
let args = [...arguments];

首先,你应该阅读函数调用在JavaScript中是如何工作的。我怀疑仅凭这一点就足以回答你的问题。但以下是正在发生的事情的摘要:

Array.prototype.sliceArray的原型中提取slice方法。但是直接调用它是行不通的,因为它是一个方法(不是函数(,因此需要一个上下文(调用对象,this(,否则它会抛出Uncaught 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);
// 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]

这是因为,正如 MDN 指出的那样

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

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

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

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

Slice 只接受对象(感谢现有的 arguments.length 属性(并在执行所有操作后返回转换的数组对象。

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

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

这解释了上面的陈述。

它使用数组具有的slice方法,并调用它,其thisarguments对象。这意味着它会调用它,就好像你arguments.slice()假设arguments有这样的方法一样。

创建没有任何参数的切片将简单地获取所有元素 - 因此它只是将元素从arguments复制到数组。

Array.prototype.slice=function(start,end){
    let res=[];
    start=start||0;
    end=end||this.length
    for(let i=start;i<end;i++){
        res.push(this[i])
    }
    return res;
}

当您执行以下操作时:

Array.prototype.slice.call(arguments) 

arguments成为slicethis的值,然后slice返回一个数组

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

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

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

因此,当您调用Array.prototype.slice.apply(arguments, [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"]

也许有点晚了,但所有这些混乱的答案是 call(( 在 JS 中用于继承。例如,如果我们将其与 Python 或 PHP 进行比较,则调用分别用作 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/en-US/docs/Learn/JavaScript/Objects/Inheritance

/*
    arguments: get all args data include Length .
    slice : clone Array
    call: Convert Object which include Length to Array
    Array.prototype.slice.call(arguments): 
        1. Convert arguments to Array
        2. Clone Array arguments
*/
//normal
function abc1(a,b,c){
    console.log(a);
} 
//argument
function: function abc2(){
    console.log(Array.prototype.slice.call(arguments,0,1))
}
abc1('a','b','c');
//a
abc2('a','b','c');
//a