【问题标题】:What does [].forEach.call() do in JavaScript?[].forEach.call() 在 JavaScript 中做了什么?
【发布时间】:2013-04-09 19:42:09
【问题描述】:

我查看了一些 sn-ps 代码,发现多个元素在节点列表上调用函数,并将 forEach 应用于空数组。

例如我有类似的东西:

[].forEach.call( document.querySelectorAll('a'), function(el) {
   // whatever with the current node
});

但我不明白它是如何工作的。谁能解释一下 forEach 前面的空数组的行为以及call 的工作原理?

【问题讨论】:

    标签: javascript arrays foreach ecmascript-5 nodelist


    【解决方案1】:

    [] 是一个数组。
    这个数组根本不用。

    它被放在页面上,因为使用数组可以让您访问数组原型,例如.forEach

    这比输入Array.prototype.forEach.call(...);

    接下来,forEach 是一个将函数作为输入的函数...

    [1,2,3].forEach(function (num) { console.log(num); });
    

    ...对于this 中的每个元素(其中this 类似于数组,因为它有一个length 并且您可以像this[1] 一样访问它的部分)它将传递三件事:

    1. 数组中的元素
    2. 元素的索引(第三个元素将通过2
    3. 对数组的引用

    最后,.call 是函数具有的原型(它是一个在其他函数上调用的函数)。
    .call 将采用它的第一个参数并将常规函数内部的 this 替换为您所需要的任何内容传递call,作为第一个参数(undefinednull 将在日常JS 中使用window,或者将是您传递的任何内容,如果在“严格模式”下)。其余参数将传递给原始函数。

    [1, 2, 3].forEach.call(["a", "b", "c"], function (item, i, arr) {
        console.log(i + ": " + item);
    });
    // 0: "a"
    // 1: "b"
    // 2: "c"
    

    因此,您正在创建一种快速调用forEach 函数的方法,并且您正在将this 从空数组更改为所有<a> 标记的列表,并且对于每个<a> in-命令,你正在调用提供的函数。

    编辑

    逻辑结论/清理

    下面有一篇文章的链接,该链接建议我们放弃对函数式编程的尝试,并且每次都坚持手动的内联循环,因为这种解决方案是 hack-ish 且难看。

    我想说,虽然 .forEach 的帮助不如其对应的 .map(transformer).filter(predicate).reduce(combiner, initialValue),但当您真正想做的只是修改外部世界(而不是数组),n 次,同时可以访问 arr[i]i

    那么我们如何处理这种差异,因为 Motto 显然是一个有才华和知识渊博的人,我想想象我知道我在做什么/我要去哪里(不时...... ..其他时候是头脑先学习)?

    答案实际上很简单,由于疏忽,鲍勃叔叔和克罗克福德爵士都会面对:

    清理它

    function toArray (arrLike) { // or asArray(), or array(), or *whatever*
      return [].slice.call(arrLike);
    }
    
    var checked = toArray(checkboxes).filter(isChecked);
    checked.forEach(listValues);
    

    现在,如果您质疑自己是否需要这样做,答案很可能是否定的......
    这个确切的事情是由... ...现在每个(?)具有高阶功能的库完成的。
    如果您使用 lodash 或 underscore 甚至 jQuery,它们都会有一种方法来获取一组元素,并执行 n 次操作。
    如果你不使用这样的东西,那么一定要自己写。

    lib.array = (arrLike, start, end) => [].slice.call(arrLike, start, end);
    lib.extend = function (subject) {
      var others = lib.array(arguments, 1);
      return others.reduce(appendKeys, subject);
    };
    

    ES6(ES2015) 及更高版本的更新

    slice( )/array( )/etc 辅助方法不仅可以让那些想要像使用数组一样使用列表的人生活更轻松(因为他们应该),但对于那些拥有奢侈的人来说在不久的将来在 ES6+ 浏览器中运行,或者在今天的 Babel 中“转译”,你有内置的语言特性,这使得这种类型的事情变得不必要。

    function countArgs (...allArgs) {
      return allArgs.length;
    }
    
    function logArgs (...allArgs) {
      return allArgs.forEach(arg => console.log(arg));
    }
    
    function extend (subject, ...others) { /* return ... */ }
    
    
    var nodeArray = [ ...nodeList1, ...nodeList2 ];
    

    超级干净,非常有用。
    查找 RestSpread 运算符;在 BabelJS 网站上试用它们;如果您的技术栈井井有条,请通过 Babel 和构建步骤在生产环境中使用它们。


    没有充分的理由不能使用从非数组到数组的转换... ...只是不要弄乱你的代码什么都不做但是粘贴相同丑陋的线条,无处不在。

    【讨论】:

    • 现在我有点困惑,因为如果我这样做了:console.log(this);我总是会得到 window 对象而且我认为 .call 会改变 this 的值
    • @MuhammadSaleh .call 不会更改您传递给forEach 的内部this 的值,.call 会更改this 的值forEach 内部。如果您对为什么 this 没有从 forEach 传递到您想要调用的函数感到困惑,您应该在 JavaScript 中查找 this 的行为。作为附录,仅适用于此处(和 map/filter/reduce),forEach 有第二个参数,它将this 设置在函数arr.forEach(fn, thisInFn)[].forEach.call(arrLike, fn, thisInFn); 内,或者只使用.bindarr.forEach(fn.bind(thisInFn));
    • @thetrystero 这正是重点。 [] 只是作为一个数组存在,因此您可以 .call .forEach 方法,并将 this 更改为您想要处理的集合。 .call 看起来像这样:function (thisArg, a, b, c) { var method = this; method(a, b, c); } 除了有内部的黑魔法设置 this 内部 method 等于你传递的 thisArg
    • 总之... Array.from()?
    • @LukeHutchison 也就是说,[].forEach.call(arr, fn) 将运行 arr 的长度,传递到 call;不是在forEach 开头使用的数组(如前两句所述,随后详细说明)。初始数组的长度完全无关紧要。当您使用forEach 函数的第二个参数时,您使用的是索引;就像您将函数直接传递给forEach 而不是call。如果你记录第一个参数,你会得到"a", "b", "c",而不是1, 2, 3,因为call 替换this 以及forEach 的工作原理。
    【解决方案2】:

    querySelectorAll method 返回一个NodeList,它类似于数组,但不完全是数组。因此,它没有forEach 方法(数组对象通过Array.prototype 继承)。

    由于NodeList 类似于数组,因此数组方法实际上会对其起作用,因此通过使用[].forEach.call,您将在NodeList 的上下文中调用Array.prototype.forEach 方法,就好像您已经能够简单地做yourNodeList.forEach(/*...*/)

    请注意,空数组文字只是扩展版本的快捷方式,您可能也会经常看到:

    Array.prototype.forEach.call(/*...*/);
    

    【讨论】:

    • 所以,澄清一下,在使用标准数组的日常应用程序中使用[].forEach.call(['a','b'], cb) 比使用['a','b'].forEach(cb) 没有任何优势,只是在尝试迭代没有forEach 的类数组结构时在他们自己的原型上?对吗?
    • @MattFletcher:是的,没错。两者都会起作用,但为什么要把事情复杂化呢?只需直接在数组本身上调用该方法即可。
    • 酷,谢谢。而且我不知道为什么,也许只是为了街头信誉,给阿尔迪的老太太留下深刻印象等等。我会坚持[].forEach() :)
    • var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) 效果很好
    • @daslicht 不在旧版本的 IE 中! (但是,确实支持数组上的forEach,但不支持NodeList对象)
    【解决方案3】:

    其他答案已经很好地解释了这段代码,所以我只是添加一个建议。

    这是一个很好的代码示例,应该为了简单和清晰而进行重构。不要每次执行此操作时都使用[].forEach.call()Array.prototype.forEach.call(),而是使用它创建一个简单的函数:

    function forEach( list, callback ) {
        Array.prototype.forEach.call( list, callback );
    }
    

    现在你可以调用这个函数而不是更复杂和晦涩的代码了:

    forEach( document.querySelectorAll('a'), function( el ) {
       // whatever with the current node
    });
    

    【讨论】:

      【解决方案4】:

      使用它可以更好地编写

      Array.prototype.forEach.call( document.querySelectorAll('a'), function(el) {
      
      });
      

      document.querySelectorAll('a') 的作用是返回一个类似于数组的对象,但它不继承自 Array 类型。 所以我们从Array.prototype对象中调用forEach方法,上下文作为document.querySelectorAll('a')返回的值

      【讨论】:

        【解决方案5】:
        [].forEach.call( document.querySelectorAll('a'), function(el) {
           // whatever with the current node
        });
        

        基本相同:

        var arr = document.querySelectorAll('a');
        arr.forEach(function(el) {
           // whatever with the current node
        });
        

        【讨论】:

          【解决方案6】:

          一个空数组在其原型中有一个属性forEach,它是一个函数对象。 (空数组只是获取对所有Array 对象都具有的forEach 函数的引用的一种简单方法。)函数对象又具有call 属性,它也是一个函数。当您调用 Function 的 call 函数时,它会使用给定的参数运行该函数。第一个参数在被调用函数中变为this

          您可以找到call 函数here 的文档。 forEach 的文档是 here

          【讨论】:

            【解决方案7】:

            想更新这个老问题:

            在现代浏览器中使用[].foreach.call() 循环遍历元素的理由已经基本结束。我们可以直接使用document.querySelectorAll("a").foreach()

            NodeList 对象是节点的集合,通常由 属性如 Node.childNodes 和方法如 document.querySelectorAll().

            虽然 NodeList 不是 Array,但 可以对其进行迭代 使用 forEach()。它也可以使用转换为真正的数组 Array.from().

            但是,一些旧浏览器没有实现 NodeList.forEach() 也不是 Array.from()。这可以通过使用来规避 Array.prototype.forEach() — 请参阅本文档的示例。

            【讨论】:

              【解决方案8】:

              只需添加一行:

              NodeList.prototype.forEach = HTMLCollection.prototype.forEach = Array.prototype.forEach;
              

              瞧!

              document.querySelectorAll('a').forEach(function(el) {
                // whatever with the current node
              });
              

              享受:—)

              警告: NodeList 是一个全局类。如果您编写公共图书馆,请不要使用此建议。但是,当您在网站或 node.js 应用程序上工作时,这是提高自我效能的一种非常方便的方法。

              【讨论】:

                【解决方案9】:

                只是一个我总是最终使用的快速而肮脏的解决方案。我不会碰原型,就像好的做法一样。当然,有很多方法可以让它变得更好,但你明白了。

                const forEach = (array, callback) => {
                  if (!array || !array.length || !callback) return
                  for (var i = 0; i < array.length; i++) {
                    callback(array[i], i);
                  }
                }
                
                forEach(document.querySelectorAll('.a-class'), (item, index) => {
                  console.log(`Item: ${item}, index: ${index}`);
                });
                

                【讨论】:

                  【解决方案10】:

                  此页面上有很多很好的信息(请参阅answer+answer+comment),但我最近遇到了与 OP 相同的问题,并且花了一些时间来了解整个情况。所以,这是一个简短的版本:

                  目标是在类似数组的 NodeList 上使用 Array 方法,而 NodeList 本身没有这些方法。

                  一个较旧的模式通过Function.call() 选择了Array 的方法,并使用了一个数组字面量([])而不是Array.prototype,因为它的类型更短:

                  [].forEach.call(document.querySelectorAll('a'), a => {})
                  

                  一种较新的模式(ECMAScript 2015 后)是使用Array.from()

                  Array.from(document.querySelectorAll('a')).forEach(a => {})
                  

                  【讨论】:

                    【解决方案11】:

                    [] 总是返回一个新数组,它等同于new Array(),但保证返回一个数组,因为Array 可以被用户覆盖,而[] 不能。所以这是获取Array原型的安全方法,那么如前所述,call用于在arraylike节点列表(this)上执行函数。

                    使用给定的 this 值和提供的参数调用函数 分别。 mdn

                    【讨论】:

                    • 虽然[] 实际上总是返回一个数组,而window.Array 可以被任何东西覆盖(在所有旧浏览器中),window.Array.prototype.forEach 也可以被任何东西覆盖。在不支持 getter/setter 或对象密封/冻结(冻结导致其自身的可扩展性问题)的浏览器中,无论哪种情况都不能保证安全。
                    • 随意编辑答案以添加有关可能覆盖prototype 或其方法的额外信息:forEach
                    【解决方案12】:

                    Norguard 解释了 什么 [].forEach.call() 做了并且James Allardice 为什么 我们这样做了:因为 querySelectorAll 返回一个没有 forEach 方法的 NodeList ...

                    除非您拥有现代浏览器,例如 Chrome 51+、Firefox 50+、Opera 38、Safari 10。

                    如果没有,您可以添加Polyfill

                    if (window.NodeList && !NodeList.prototype.forEach) {
                        NodeList.prototype.forEach = function (callback, thisArg) {
                            thisArg = thisArg || window;
                            for (var i = 0; i < this.length; i++) {
                                callback.call(thisArg, this[i], i, this);
                            }
                        };
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 2012-02-12
                      • 2011-07-24
                      • 2011-10-21
                      • 2012-02-26
                      相关资源
                      最近更新 更多