`Array.prototype.slice.call` 是如何工作的?

Posted

技术标签:

【中文标题】`Array.prototype.slice.call` 是如何工作的?【英文标题】:How does `Array.prototype.slice.call` work? 【发布时间】:2011-10-26 18:45:25 【问题描述】:

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

【问题讨论】:

^ 略有不同,因为该链接询问的是 DOM 节点而不是参数。而且我认为通过在内部描述“this”是什么,这里的答案要好得多。 【参考方案1】:

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

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

【讨论】:

【参考方案2】:

通常,调用

var b = a.slice();

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

var a = arguments.slice();

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

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

【讨论】:

thanx 但是为什么要使用prototypeslice 不是原生的 Array 方法吗? 注意Array是一个构造函数,对应的“类”是Array.prototype。你也可以使用[].slice IlyaD, slice 是每个 Array 实例的方法,但不是 Array 构造函数。您使用prototype 来访问构造函数的理论实例的方法。 call() 运行什么函数? @DavidSpector 我已经编辑以澄清这一点。【参考方案3】:

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

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

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

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

【讨论】:

apply() 与 bind() 有何不同?【参考方案4】:

因为MDN notes

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

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

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

【讨论】:

不明白。 Array 不是类名吗?那么调用Array的静态方法应该和调用Array.prototype中的同一个方法一样吧?【参考方案5】:

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

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

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

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

【讨论】:

您可能想要使用切片,原因如下:jspatterns.com/arguments-considered-harmful【参考方案6】:

.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()假设它正在处理一个数组,并且会这样做它的东西。

以这个普通的对象为例。

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

【讨论】:

很好的答案!但遗憾的是,如果您的对象键是字符串值,就像实际单词一样,您不能以这种方式转换任何对象。这将失败,所以将您的对象内容保持为 '0':'value' 而不是像 'stringName' :'价值'。 @Michael:阅读开源 JS 实现的源代码是可能的,但参考“ECMAScript”语言规范会更简单。 Here's a link 到 Array.prototype.slice 方法描述。 由于对象键没有顺序,这个特定的演示在其他浏览器中可能会失败。并非所有供应商都按创建顺序对其对象的键进行排序。 @vsync:for-in 语句不能保证顺序。 .slice() 使用的算法定义了一个以0 开头并以给定对象(或数组或其他)的.length 结尾(不包括)的数字顺序。所以顺序保证在所有实现中都是一致的。 @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 开始,并以升序循环访问属性。【参考方案7】:

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

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

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

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

这就解释了上面的陈述。

【讨论】:

我试过这个,并省略了“原型”。导致未定义的错误。为什么?原型不是包含了所有的方法,所以它们可以被新的对象继承吗?【参考方案8】:
// 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】:

当 .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"]

【讨论】:

【参考方案10】:

首先,您应该阅读how function invocation works in javascript。我怀疑仅此一项就足以回答您的问题。但这里是正在发生的事情的摘要:

Array.prototype.sliceArray 的prototype 中提取slice method。但是直接调用它是行不通的,as it's a method (not a function),因此需要一个上下文(调用对象,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);

【讨论】:

为什么要使用 .call() 而不是 bind()?它们有什么不同吗? @DavidSpector : ***.com/questions/15455009/…【参考方案11】:

也许有点晚了,但所有这些混乱的答案是 call() 在 JS 中用于继承。 例如,如果我们将其与 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/en-US/docs/Learn/JavaScript/Objects/Inheritance

【讨论】:

【参考方案12】:

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

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

let args = Array.from(arguments);

let args = [...arguments];

【讨论】:

你的意思是Array.slice.call(1,2)返回值[1,2]?为什么叫“call”而不是“argsToArray”? “调用”不就是调用函数的意思吗? 我明白了。 call 的意思是调用函数 slice(),它对数组的作用就像 substr 对字符串的作用。是吗?【参考方案13】:
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变成thisslice的值,然后slice返回一个数组

【讨论】:

你没有说“呼叫”是为了什么。 call 调用的是什么函数? 我认为它正在调用“slice”,它应该是一个函数(如字符串的 substr),但被表示为一个对象。是吗?【参考方案14】:
/*
    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

【讨论】:

如果您可以为您的答案提供一些上下文,这样会更好地了解您的答案的其他人可以轻松理解您提供的解决方案。

以上是关于`Array.prototype.slice.call` 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript arguments 伪数组