JavaScript中替代jQuery的each()函数的方法是什么?

17
在jQuery中,我们有一个名为each的函数,如下所示。
$('button').each(function(i) {
  $('button').eq(i).css('background', 'red');
});

我们如何使用普通JavaScript替换这段代码?


好的,有Array.prototype.forEach(),但它本身不能替换那个代码。 - Pointy
只需使用 for...of 循环。 - Bergi
5个回答

26
使用querySelectorAll()进行DOM选择

浏览器有几种从DOM中选择元素的方法,但可能最灵活和方便的是 querySelectorAll。它允许您使用CSS样式选择器从给定的根目录中抓取所有匹配的元素。

在您的情况下,它应该像这样:

document.querySelectorAll("button");

缩短 querySelectorAll()

虽然这很好,但有点冗长,因此创建一个包装函数来缩短它并不罕见。这就是我们在这里要做的,给它起名为Q

function Q(root, selector) {
  if (typeof root === "string") {
    selector = root
    root = document
  }
  return root.querySelectorAll(selector)
}

第一个参数是您进行选择的上下文,第二个参数是选择器。如果仅传递字符串,则会将document用作上下文。

因此,现在您的DOM选择将是这样的,我们将在此后使用:

Q("button");

借用 Array.prototype.forEach

使用函数的 .call() 方法,从 Array.prototype 借用 forEach 方法用于循环操作是一种相当常见的函数式编程方法,像这样:

Array.prototype.forEach.call(Q("buttons"), function(el) {
  el.style.background = "red";
});

或者在最现代的浏览器中,我们可以使用箭头函数来简化它:

Array.prototype.forEach.call(Q("buttons"), el => el.style.background = "red");

将借用的.forEach()方法进行绑定和缓存

如果在应用程序早期使用函数原型的bind()方法将.forEach()方法绑定到.call()this值,那么.forEach()的借用就可以被缩短。

const each = Function.call.bind(Array.prototype.forEach);

这样,您就可以像调用函数一样调用它,将元素集合作为第一个参数传递。

each(Q("button"), function(el) {
  el.style.background = "red";
});

或者在一些最新的浏览器中,可以再次使用箭头函数:

each(Q("button"), el => el.style.background = "red");

使用Array.from()

Array.from() 方法被引入用来方便地将类数组对象转换为真正的数组。这使您可以直接使用 .forEach(),并且可以使用简单的 polyfill(请参阅文档链接) 来修补旧版浏览器。

Array.from(Q("button")).forEach(function(el) {
  el.style.background = "red";
});

如果您直接在上面的Q函数中调用Array.from,则可以直接调用.forEach()

Q("button").forEach(function(el) {
  el.style.background = "red";
});

使用for-of循环

在最新的浏览器中,您可以使用for-of循环替代,使代码非常简短和干净:

for (const el of Q("button")) {
  el.style.background = "red";
}

这种方法不需要转换为 Array 或使用 .forEach


转译现代代码

对于上面需要最新浏览器的示例,有可用的转译器(例如Babel),将最新标准翻译成在旧浏览器中能够工作的代码。


创建自定义 each()

顺便提一下,如果您希望 this 指向当前元素或具有任何其他特定行为,则可以使用基本的 each 实现来接收集合和回调。

function each(a, callback) {
  for (var i = 0; i < a.length; i++) {
    callback.call(a[i], a[i], i, a);
  }
}

虽然在这种情况下使用this通常是不必要的,因为你已经将元素作为参数了。


不错。但是在 jQuery 的 $(...).each() 中,this 将指向元素。 - haim770
@haim770:没错,不过我认为OP只是想知道如何在JS中实现这种循环,而不一定要像在jQuery中那样完全一致。 - user1106925
1
@squint 为什么你把这个设为了 CW? - Praveen Kumar Purushothaman
3
@PraveenKumar:我的回答都是这样的(或者在我发现非CW的情况下会更改)。我希望其他人感到舒适,可以向我发布的答案中添加信息和改进。 - user1106925

5
你可以使用函数getElementsByTagName(),该函数查询DOM中特定标记的元素并返回元素集合。你可以在document上调用此函数,或者更具体地选择一个元素,使用document.getElementById(),然后查找该元素内部的标记。
无论哪种方式,一旦你有了元素集合,就可以循环遍历该集合并相应地应用样式。
//query the document for all <button> elements
var buttons = document.getElementsByTagName('button');

/* -- OR -- */

//query the document for all buttons inside of a specific element
var container = document.getElementById('container');
var buttons = container.getElementsByTagName('button');

//loop over the collection of buttons and set each background to 'red'
for(var i=0; i<buttons.length; i++) {
    buttons[i].style.background = "red";
}

编辑:我意识到这不是jQuery的each函数背后的JS。OP并没有说他想特别看到那个,只是想用JS实现$ .each()功能的一种方法(因为有很多可能性)。这个例子只是使用一个非常基本概念的微不足道的方法。


4
var buttons = document.querySelectorAll("button");
for(var x in buttons){
  var e = buttons[x];
  e.innerHTML="stuff";
}

正如Squint指出的那样,应该是buttons[x]和var x。对此我很抱歉。

噢,忘记了 e=buttons[x]。 - MyNameIsUser9123

3

由于它不使用forEach,因此受到更多浏览器的支持。

如果使用getElementsByTagName,请将该函数添加到HTMLCollection中。

HTMLCollection.prototype.each = function(callback){
    for(var i=0; i<this.length; i++) callback(this[i]);
};

document.getElementsByTagName('div').each(function(div){
    div.innerHTML = "poo";
});

如果使用querySelectorAll,您可以将相同的函数附加到NodeList

NodeList.prototype.each = function(callback){
    for(var i=0; i<this.length; i++) callback(this[i]);
};

document.querySelectorAll('div').each(function(div){
    div.innerHTML = "podo";
});

为了满足评论者的要求,我将明确说明:如果这是您正在编写的库,或者它将与可能覆盖它的库一起使用,那么您显然需要考虑其他方法。
示例代码:https://jsfiddle.net/er5amk8j/

1
不要修改你没有所有权的对象。 - Michał Perłakowski
@Gothdo 不要发表一些只适用于特定情境,而在其他情境下毫无意义的笼统言论。 - I wrestled a bear once.
如果您认为这不适用于您的情况,请解释一下。 - Michał Perłakowski
我说了我想说的话。这是人们一直在反复吟唱关于“eval”的口头禅。如果这个功能没有任何价值或目的,它就不会成为标准的一部分。你(和所有与你一起反复吟唱的人)通过对使用哪些功能以及不使用哪些功能做出笼统的陈述,实际上是在造成更多的伤害而非益处。你是提出主张的人,因此解释的责任在于你。 - I wrestled a bear once.
1
答案已经包含在我所提供的问题中: "问题在于原型可以在多个位置上被修改。例如,一个库将向数组的原型添加map方法,而您自己的代码将添加相同但有不同目的的方法。这样一来,其中一个实现就会出错。" - Michał Perłakowski
1
你最初的陈述是“不要这样做”,它没有任何例外或条件。没有条件,你的陈述是不准确和无益的,而有了适当的条件,它也只是勉强相关。 - I wrestled a bear once.

2

但是你在示例中使用$.each()的方式有些可疑。使用this访问元素会更加容易和快速。

$('button').each(function(i) {
  $(this).css('background', 'red');
});

最简单的替换方法是使用简单的函数闭包,但它并不美观。

var $buttons = $('button');

for(var i = 0; i < $buttons.length; i++){
  (function(i){
    $buttons.eq(i).css('background', 'red');
  })(i);
}

或者使用.call()来调用函数,从而设置this

var $buttons = $('button');

for(var i = 0; i < $buttons.length; i++){
  (function(i){
    $(this).css('background', 'red');    
  }).call($buttons[i], i);
}

在第二个例子中,IIFE 实际上是不必要的,因为 i 在循环中立即被使用而不是在回调中稍后被使用。但你关于 OP 的技巧是正确的。在循环中一遍又一遍地调用 $('button') 会很慢。 - user1106925
@squint 你说得对。但是我故意扩展了Michael Hamilton的答案,以显示尽管结果相同,但替换.each()的方法并不正确。 - Bizniztime

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