O que [] .forEach.call () faz no JavaScript?


135

Eu estava olhando alguns trechos de código e encontrei vários elementos que chamavam uma função em uma lista de nós com um forEach aplicado a uma matriz vazia.

Por exemplo, eu tenho algo como:

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

mas não consigo entender como isso funciona. Alguém pode me explicar o comportamento da matriz vazia na frente do forEach e como callfunciona?

Respostas:


203

[]é uma matriz.
Essa matriz não é usada.

Está sendo colocado na página, porque o uso de uma matriz fornece acesso a protótipos de matriz, como .forEach.

Isso é apenas mais rápido do que digitar Array.prototype.forEach.call(...);

Em seguida, forEaché uma função que assume uma função como entrada ...

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

... e para cada elemento em this(onde thisé do tipo matriz, na medida em que possui um lengthe você pode acessar suas partes como this[1]), ele passa três coisas:

  1. o elemento na matriz
  2. o índice do elemento (o terceiro elemento passaria 2)
  3. uma referência à matriz

Por fim, .callé um protótipo que possui funções (é uma função que é chamada em outras funções).
.callpegará seu primeiro argumento e substituirá thisdentro da função regular o que você passou call, como o primeiro argumento ( undefinedou nullserá usado windowno JS diário, ou será o que você passou, se estiver no "modo estrito"). O restante dos argumentos será passado para a função original.

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

Portanto, você está criando uma maneira rápida de chamar a forEachfunção e está mudando thisda matriz vazia para uma lista de todas as <a>tags e, para cada <a>ordem, está chamando a função fornecida.

EDITAR

Conclusão Lógica / Limpeza

Abaixo, há um link para um artigo sugerindo que descartemos as tentativas de programação funcional, e seguimos o loop inline manual todas as vezes, porque essa solução é um hack-ish e desagradável.

Eu diria que, enquanto .forEaché menos útil do que suas contrapartes, .map(transformer), .filter(predicate), .reduce(combiner, initialValue), ainda serve a propósitos quando tudo que você realmente quer fazer é modificar o mundo exterior (não a matriz), N-vezes, ao ter acesso a qualquer arr[i]ou i.

Então, como lidamos com a disparidade, já que o Lema é claramente um cara talentoso e experiente, e eu gostaria de imaginar que sei o que estou fazendo / para onde estou indo (de vez em quando ... vezes que é o primeiro aprendizado)?

A resposta é realmente bastante simples, e algo que o tio Bob e Sir Crockford enfrentariam, devido à supervisão:

limpe-o .

function toArray (arrLike) { // or asArray(), or array(), or *whatever*
  return [].slice.call(arrLike);
}

var checked = toArray(checkboxes).filter(isChecked);
checked.forEach(listValues);

Agora, se você está questionando se precisa fazer isso, você mesmo, a resposta pode não ser ...
exatamente isso é feito por ... ... todas as bibliotecas (?) Com recursos de ordem superior atualmente.
Se você estiver usando lodash, sublinhado ou até jQuery, todos terão uma maneira de pegar um conjunto de elementos e executar uma ação n vezes.
Se você não estiver usando uma coisa dessas, escreva por conta própria.

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);
};

Atualização para ES6 (ES2015) e além

Não apenas o método auxiliar slice( )/ array( )/ etc facilitará a vida das pessoas que desejam usar as listas da mesma forma que usam matrizes (como deveriam), mas também para as pessoas que têm o luxo de operar nos navegadores ES6 + dos relativamente próximos. No futuro, ou de "transpilar" hoje em Babel, você tem recursos de linguagem incorporados, que tornam esse tipo de coisa desnecessário.

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 ];

Super limpo e muito útil.
Procure os operadores Descansar e Espalhar ; experimentá-los no site BabelJS; se sua pilha de tecnologia estiver em ordem, use-a em produção com Babel e uma etapa de construção.


Não há uma boa razão para não poder usar a transformação de não array em array ... ... apenas não bagunce seu código sem fazer nada além de colar a mesma linha feia em todos os lugares.


Agora estou um pouco confuso porque fiz: console.log (this); Eu sempre obter o objeto de janela E eu pensei que .Call altera o valor deste
Muhammad Saleh

.call não alterar o valor this, que é por isso que você está usando callcom o forEach. Se forEach se parece forEach (fn) { var i = 0; var len = this.length; for (; i < length; i += 1) { fn( this[i], i, this ); }, alterando thisdizendo forEach.call( newArrLike )significa que ele usará esse novo objeto como this.
Norguard 17/04

2
O @MuhammadSaleh .callnão altera o valor de thisdentro do que você passa forEach, .callaltera o valor de this dentro de forEach . Se você está confuso sobre o motivo pelo qual thisnão é passado forEachpara a função que você queria chamar, você deve procurar o comportamento do thisJavaScript. Como um aditivo, aplicável apenas aqui (e mapa / filtro / reduzir), existe um segundo argumento para forEach, que define thisno interior da função arr.forEach(fn, thisInFn)ou [].forEach.call(arrLike, fn, thisInFn);ou uso apenas .bind; arr.forEach(fn.bind(thisInFn));
Norguard 19/04

1
@ thetrystero esse é exatamente o ponto. []existe apenas como uma matriz, para que você possa usar .callo .forEachmétodo e altere thispara o conjunto em que deseja trabalhar. .callse parece com isso: function (thisArg, a, b, c) { var method = this; method(a, b, c); }exceto que há magia negra interna para definir thisdentro methodpara igualar o que você passou como thisArg.
Norguard

1
tão resumidamente ... Array.from ()?
zakius 17/02/19

53

O querySelectorAllmétodo retorna a NodeList, que é semelhante a uma matriz, mas não é exatamente uma matriz. Portanto, ele não possui um forEachmétodo (que objetos de matriz herdam via Array.prototype).

Como a NodeListé semelhante a uma matriz, os métodos da matriz realmente funcionarão nela; portanto, usando [].forEach.callvocê está invocando o Array.prototype.forEachmétodo no contexto da NodeList, como se você pudesse simplesmente fazê-lo yourNodeList.forEach(/*...*/).

Observe que o literal da matriz vazia é apenas um atalho para a versão expandida, que você provavelmente verá com bastante frequência:

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

7
Então, para esclarecer, não há nenhuma vantagem em utilizar [].forEach.call(['a','b'], cb)mais ['a','b'].forEach(cb)em aplicações cotidianas com matrizes padrão, apenas quando se tenta iterate sobre array-como estruturas que não têm forEachem seu próprio protótipo? Isso está correto?
Matt Fletcher

2
@ MattFletcher: Sim, está correto. Ambos vão funcionar, mas por que complicar demais as coisas? Basta chamar o método diretamente na própria matriz.
precisa saber é o seguinte

Legal, obrigado. E não sei por que, talvez apenas pelo crédito de rua, impressionando velhinhas na ALDI e coisas do tipo. Eu vou ficar com [].forEach():)
Matt Fletcher

var section = document.querySelectorAll(".section"); section.forEach((item)=>{ console.log(item.offsetTop) }) funciona bem
daslicht

@daslicht não em versões mais antigas do IE! (que fazem , no entanto, apoiar forEachem matrizes, mas não objetos NodeList)
Djave

21

As outras respostas explicaram muito bem esse código, então adicionarei uma sugestão.

Este é um bom exemplo de código que deve ser refatorado para simplicidade e clareza. Em vez de usar [].forEach.call()ou Array.prototype.forEach.call()toda vez que você fizer isso, crie uma função simples:

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

Agora você pode chamar esta função em vez do código mais complicado e obscuro:

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

5

Pode ser melhor escrito usando

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

});

O que é does é document.querySelectorAll('a')retorna um objeto semelhante a uma matriz, mas não herda do Arraytipo. Então, chamamos o forEachmétodo do Array.prototypeobjeto com o contexto como o valor retornado pordocument.querySelectorAll('a')


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

É basicamente o mesmo que:

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

2

Deseja atualizar esta pergunta antiga:

O motivo para usar o [].foreach.call()loop através de elementos nos navegadores modernos acabou principalmente. Nós podemos usar document.querySelectorAll("a").foreach()diretamente.

Os objetos NodeList são coleções de nós, geralmente retornados por propriedades como Node.childNodes e métodos como document.querySelectorAll ().

Embora o NodeList não seja uma matriz, é possível iterá-lo com forEach () . Também pode ser convertido em uma matriz real usando Array.from ().

No entanto, alguns navegadores mais antigos não implementaram NodeList.forEach () nem Array.from (). Isso pode ser contornado usando Array.prototype.forEach () - consulte o Exemplo deste documento.


1

Uma matriz vazia possui uma propriedade forEachem seu protótipo, que é um objeto Function. (A matriz vazia é apenas uma maneira fácil de obter uma referência à forEachfunção que todos os Arrayobjetos possuem.) Os objetos de função, por sua vez, possuem uma callpropriedade que também é uma função. Quando você invoca a função de uma Função call, ela executa a função com os argumentos fornecidos. O primeiro argumento se torna thisna função chamada.

Você pode encontrar documentação para a callfunção aqui . A documentação para forEachestá aqui .


1

Basta adicionar uma linha:

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

E pronto!

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

Aproveitar :-)

Aviso: NodeList é uma classe global. Não use esta recomendação se estiver escrevendo uma biblioteca pública. No entanto, é uma maneira muito conveniente de aumentar a auto-eficácia quando você trabalha no site ou no aplicativo node.js.


1

Apenas uma solução rápida e suja que eu sempre uso. Eu não tocaria em protótipos, assim como boas práticas. Claro, existem várias maneiras de melhorar isso, mas você entendeu.

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}`);
});

0

[]sempre retorna uma nova matriz, é equivalente a, new Array()mas é garantido, que retorne uma matriz, pois Arraypode ser sobrescrita pelo usuário e []não pode. Portanto, esta é uma maneira segura de obter o protótipo de Array, então, conforme descrito, callé usado para executar a função no nodelist de matriz (this).

Chama uma função com um determinado valor e argumentos fornecidos individualmente. mdn


Embora []o fato sempre retorne uma matriz, o while window.Arraypode ser substituído por qualquer coisa (em todos os navegadores antigos), assim também pode window.Array.prototype.forEachser substituído por qualquer coisa. Nos dois casos, não há garantia de segurança em navegadores que não suportam getters / setters ou vedação / congelamento de objetos (o congelamento causa seus próprios problemas de extensibilidade).
Norguard 19/04

Sinta-se livre para editar a resposta para adicionar a informação extra sobre a possibilidade de substituir os prototypeou é métodos: forEach.
Xotic750

0

Norguard explicou o que [].forEach.call() faz e James Allardice POR QUE o fazemos: porque querySelectorAll retorna um NodeListque não possui um método forEach ...

A menos que você tenha um navegador moderno como o Chrome 51+, Firefox 50+, Opera 38, Safari 10.

Caso contrário, você pode adicionar um 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);
        }
    };
}

0

Muitas informações boas nesta página (consulte resposta + resposta + comentário ), mas recentemente tive a mesma pergunta que o OP, e demorou algumas escavações para obter uma imagem completa. Então, aqui está uma versão curta:

O objetivo é usar Arraymétodos em uma matriz NodeListque não possua esses métodos.

Um padrão mais antigo cooptava os métodos de Array via Function.call()e usava uma matriz literal ( []) em vez de Array.prototypeser mais curta para digitar:

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

Um padrão mais recente (pós ECMAScript 2015) é usar Array.from():

Array.from(document.querySelectorAll('a')).forEach(a => {})
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.