在JavaScript中,[].slice.call的解释是什么?

248

我偶然发现了一个将DOM NodeList转换为常规数组的巧妙快捷方式,但我必须承认,我并不完全理解它是如何工作的:

[].slice.call(document.querySelectorAll('a'), 0)

它从一个空数组[]开始,然后使用slicecall的结果转换为一个新数组,对吧?

我不理解的部分是call。它如何将document.querySelectorAll('a')从NodeList转换为普通数组?


12
Array.prototype.slice.call(document.querySelectorAll('a')); 是你编写的代码片段的正确书写方式。 - vdegenne
13
顺便提一句,现代的(直观易懂的)ES6方法是使用 Array.from。因此,例如以下代码会达到相同的效果:Array.from(document.querySelectorAll('a'))。 - rugk
这回答解决了你的问题吗?Array.prototype.slice.call()是如何工作的? - Henke
9个回答

185
这里的操作是使用call()slice()作为NodeList的函数进行调用。在这种情况下,slice()创建一个空数组,然后遍历它所运行的对象(最初是一个数组,现在是NodeList)并将该对象的元素附加到它所创建的空数组中,最终返回的就是该数组。这是一篇关于此问题的文章:article on this编辑:

因此,它从一个空数组[]开始,然后使用slice将调用结果转换为新数组,对吗?

那不对。 [].slice返回一个函数对象。函数对象具有一个函数call(),该函数调用将第一个参数分配给this的函数; 换句话说,使函数认为它是从参数(由document.querySelectorAll('a')返回的NodeList)而不是从数组调用的。

71
请注意,尽管这与说Array.prototype.slice.call(...)在语义上等效,但它实际上只是实例化一个数组对象([])来访问其原型切片方法。这是一种浪费的实例化。相反,如果您在计算字符数,则可以使用Array.prototype.slice.call(...),这样更加简洁。 - Ben Zotto
请注意,此方法仅适用于 IE 8 及以下版本的 Array 对象,因此您将无法克隆 NodeList。 - Livingston Samuel
5
[]Array更可靠,因为Array可能被覆盖成其他内容。如果需要重复使用Array#slice,建议进行缓存。 - Mathias Bynens
2
如果有其他人正在寻找在IE8中执行此操作的方法,请查看此问题https://dev59.com/VHA75IYBdhLWcg3wm6ex - Liam Newmarch
1
我实际上在backbone.js源代码中看到了这种模式:var array = []; var push = array.push; var slice = array.slice; var splice = array.splice;他这样做是为了@MathiasBynens提到的安全问题吗? - owensmartin
@MathiasBynens 我认为覆盖 Array 本身会是一种反模式,比创建原型函数更糟糕。 - Mantas

157

在JavaScript中,对象的方法可以在运行时绑定到另一个对象。简而言之,JavaScript允许一个对象“借用”另一个对象的方法:

object1 = {
    name: 'Frank',
    greet() {
        alert(`Hello ${this.name}`);
    }
};

object2 = {
    name: 'Andy'
};

// Note that object2 has no greet method,
// but we may "borrow" from object1:

object1.greet.call(object2); // Will show an alert with 'Hello Andy'
callapply 方法可以让你在 JavaScript 中对函数对象(在 JavaScript 中,函数本身也是对象)进行这样的操作。 因此,在您的代码中,您可以说 NodeList 借用了数组的 slice 方法。.slice() 返回另一个数组作为其结果,这将成为您可以使用的“转换”数组。

深入解释 JavaScript 对象函数的抽象概念,现在你可以自己将其应用于 Array.prototype 或者 [].prototypecall 函数。 - Sourabh

34

它从一个Array中检索slice函数。然后调用该函数,但使用document.querySelectorAll的结果作为this对象,而不是实际数组。


24

它是一种将类似数组的对象转换为真正数组的技术。

其中一些对象包括:

  • 函数中的 arguments
  • NodeList(请记住,它们的内容在获取后可能会更改!因此将它们转换为数组是一种冻结它们的方式)
  • jQuery 集合,又称 jQuery 对象(一些文档:API类型学习

这有许多用途,例如对象是通过引用传递的,而数组是按值传递的。

并且请注意第一个参数 0 可以省略,这里有详细解释

为了完整起见,还有 jQuery.makeArray()


20
这段代码将 document.querySelectorAll('a') 从一个 NodeList 转换为普通数组?
以下是我们拥有的代码:
[].slice.call(document.querySelectorAll('a'), 0)

让我们先拆开它,

  []    // Array object
.slice // Accessing the function 'slice' present in the prototype of Array
.call // Accessing the function 'call' present in the prototype of function object(slice)
(document.querySelectorAll('a'),0) 
    // 'call' can have arguments like, (thisArg, arg1,arg2...n). 
   // So here we are passing the 'thisArg' as an array like object,
  // that is a 'nodeList'. It will be served as 'this' object inside of slice function.
 // And finally setting 'start' argument of slice as '0' and leaving the 'end' 
// argument as 'undefined'

步骤1:执行call函数

  • 除了thisArg之外,在call内,其余的参数将被附加到参数列表中。
  • 现在,函数slice将通过将其this值绑定为thisArg(来自document.querySelector的类似数组对象)并使用参数列表来调用。即]包含0的参数start

步骤2:在call内部调用的slice函数的执行

  • start will be assigned to a variable s as 0
  • since end is undefined, this.length will be stored in e
  • an empty array will be stored in a variable a
  • After making the above settings the following iteration will be happened

    while(s < e) {
      a.push(this[s]);
      s++;
    }
    
  • the filled up array a will be returned as the result.

P.S 为了更好地理解我们的场景,原始算法callslice中必要的步骤已被忽略。


1
非常好的逐步解释。太棒了!谢谢 :) - kittu
1
很好的解释。 - Naveen DA

10
[].slice.call(document.querySelectorAll('.slide'));
  1. querySelectorAll()方法返回文档中与指定选择器匹配的所有元素。

  2. call()方法使用给定的this值和逐个提供的参数调用函数。

  3. slice()方法将选定的元素作为新数组对象返回。

因此,这行代码返回一个由[object HTMLDivElement]组成的数组。这里有六个class名为"slide"的div,所以数组长度将会是6。

var arraylist = [].slice.call(document.querySelectorAll('.slide'));
console.log(arraylist);
<div class="slideshow">
  <div class="slide">
    first slider1
  </div>
  <div class="slide">
    first slider2
  </div>
  <div class="slide">
    first slider3
  </div>
  <div class="slide">
    first slider4
  </div>
  <div class="slide">
    first slider5
  </div>
  <div class="slide">
    first slider6
  </div>
</div>


8

从ES6开始,您可以使用 Array.from(element.children)Array.from({length: 5}) 来简单地创建数组。


4

在2020年代,我们使用

[...document.querySelectorAll('.slide')]

如果你想使用 map 或 filter,但不再需要使用 forEach,那么这是非常有用的,因为现在 forEach 可以在从 document.querySelectorAll('.slide') 返回的集合上工作。


0

这也许会有所帮助。

slice方法

描述:

slice方法不会改变原始数组。它返回原始数组中元素的浅拷贝。原始数组的元素被复制到返回的数组中。

slice()方法将从数组的start到end(不包括end)选择的一部分浅拷贝到一个新的数组对象中。原始数组不会被修改。 了解更多:Reference/Global_Objects/Array/slice

call方法

描述:

call()允许将属于一个对象的函数/方法分配并调用另一个对象。

call()方法使用给定的this值和逐个提供的参数调用函数。 call()为函数/方法提供了一个新的this值。使用call(),您可以编写一次方法,然后在另一个对象中继承它,而无需为新对象重新编写该方法。

查看更多: 参考/全局对象/函数/call


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