O que o 'corpo de um for in do erro JSLint deve ser envolvido em uma instrução if' significa?

242

Eu usei JSLint em um arquivo JavaScript meu. Ele lançou o erro:

for( ind in evtListeners ) {

Problema na linha 41, caractere 9: o corpo de um for for deve ser envolvido em uma instrução if para filtrar propriedades indesejadas do protótipo.

O que isto significa?

jrharshath
fonte
5
Por padrão, 'in' itera também sobre as propriedades herdadas. Geralmente, o corpo é envolvido if (evtListeners.hasOwnProperty(ind))para restringir o processamento apenas às propriedades (não herdadas). Ainda assim, em alguns casos, você realmente deseja iterar sobre todas as propriedades, incluindo as herdadas. Nesse caso, o JSLint obriga a envolver o corpo do loop em uma instrução if para decidir quais propriedades você realmente deseja. Isto irá funcionar e fazer JSLint feliz: if (evtListeners[ind] !== undefined)
xorcus
1
A maioria das respostas está desatualizada. uma solução atualizada pode ser encontrada em stackoverflow.com/a/10167931/3138375 #
eli-bd

Respostas:

430

Primeiro de tudo, nunca use um for inloop para enumerar sobre uma matriz. Nunca. Use bom e velho for(var i = 0; i<arr.length; i++).

A razão por trás disso é o seguinte: cada objeto em JavaScript tem um campo especial chamado prototype. Tudo o que você adicionar a esse campo estará acessível em todos os objetos desse tipo. Suponha que você deseje que todas as matrizes tenham uma nova função interessante chamada filter_0que filtrará os zeros.

Array.prototype.filter_0 = function() {
    var res = [];
    for (var i = 0; i < this.length; i++) {
        if (this[i] != 0) {
            res.push(this[i]);
        }
    }
    return res;
};

console.log([0, 5, 0, 3, 0, 1, 0].filter_0());
//prints [5,3,1]

Essa é uma maneira padrão de estender objetos e adicionar novos métodos. Muitas bibliotecas fazem isso. No entanto, vamos ver como for infunciona agora:

var listeners = ["a", "b", "c"];
for (o in listeners) {
    console.log(o);
}
//prints:
//  0
//  1
//  2
//  filter_0

Você vê? De repente, pensa que filter_0 é outro índice de matriz. Obviamente, não é realmente um índice numérico, mas for inenumera através de campos de objetos, não apenas índices numéricos. Então, agora estamos enumerando todos os índices numéricos e filter_0 . Mas filter_0não é um campo de nenhum objeto de matriz específico, todo objeto de matriz possui essa propriedade agora.

Felizmente, todos os objetos têm um hasOwnPropertymétodo que verifica se esse campo realmente pertence ao próprio objeto ou se é simplesmente herdado da cadeia de protótipos e, portanto, pertence a todos os objetos desse tipo.

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}
 //prints:
 //  0
 //  1
 //  2

Note que embora este código funciona como esperado para arrays, você não deve nunca, nunca mais , o uso for ine for each inpara arrays. Lembre-se de que for inenumera os campos de um objeto, não os índices ou valores da matriz.

var listeners = ["a", "b", "c"];
listeners.happy = "Happy debugging";

for (o in listeners) {
    if (listeners.hasOwnProperty(o)) {
       console.log(o);
    }
}

 //prints:
 //  0
 //  1
 //  2
 //  happy
vava
fonte
43
Você não deve usar for inpara iterar sobre matrizes porque o idioma não exige a ordem na qual for ina enumeração será feita sobre uma matriz. Pode não estar em ordem numérica. Além disso, se você usar a construção de estilo `for (i = 0; i <array.length; i ++), poderá ter certeza de que está apenas repetindo os índices numéricos na ordem e sem propriedades alfanuméricas.
Breton
Obrigado! Vou guardar isso como uma referência.
usar o seguinte código
Eu me peguei olhando para essa resposta novamente porque estava convencido de que esse pedaço de JSLint estava quebrado. Eu tinha um código que era basicamente: for (o em listeners) {if (listeners.hasOwnProperty (i)) {console.log (o); }} O problema é que eu tive um bug, mudei os nomes das variáveis ​​i para o e perdi uma referência. JSLint é inteligente o suficiente para garantir que você está verificando hasOwnProperty para a propriedade correta no objeto correto.
drewish
12
pois in, no entanto, não há problema em iterar sobre a propriedade de um objeto. O OP nunca disse que a entrada foi aplicada a uma matriz. A hasOwnProperty é uma prática recomendada, no entanto, há casos em que você não deseja - por exemplo, se um objeto se estende por outro, e você deseja listar os objetos e as propriedades de um 'pai'.
gotofritz
3
Penso que, em vez de afastar as pessoas dos for-inloops (o que é incrível, a propósito), devemos educá-los como eles funcionam (feitos corretamente nesta resposta) e apresentá-los para Object.defineProperty()que eles possam estender seus protótipos com segurança sem quebrar nada. Aliás, a extensão dos protótipos de objetos nativos não deve ser feita sem Object.defineProperty.
Robert Rossmann 5/05
87

Douglas Crockford, autor de jslint, escreveu (e falou) sobre esse assunto muitas vezes. Há uma seção nesta página do site dele que cobre isso:

para declaração

A classe de instruções deve ter o seguinte formato:

for (initialization; condition; update) {
    statements
}

for (variable in object) {
    if (filter) {
        statements
    } 
}

A primeira forma deve ser usada com matrizes e com loops de um número predeterminável de iterações.

O segundo formulário deve ser usado com objetos. Esteja ciente de que os membros adicionados ao protótipo do objeto serão incluídos na enumeração. É aconselhável programar defensivamente usando o método hasOwnProperty para distinguir os membros verdadeiros do objeto:

for (variable in object) {
    if (object.hasOwnProperty(variable)) {
        statements
    } 
}

Crockford também tem uma série de vídeos no teatro YUI, onde ele fala sobre isso. A série de vídeos / palestras de Crockford sobre javascript é imperdível se você é levemente sério sobre javascript.

Bretão
fonte
21

Ruim: (jsHint lançará um erro)

for (var name in item) {
    console.log(item[name]);
}

Boa:

for (var name in item) {
  if (item.hasOwnProperty(name)) {
    console.log(item[name]);
  }
}
Taro Alan
fonte
8

A resposta da Vava está na marca. Se você usa jQuery, a $.each()função cuida disso, portanto, é mais seguro de usar.

$.each(evtListeners, function(index, elem) {
    // your code
});
HRJ
fonte
5
Se o desempenho é alguma consideração aqui, eu não recomendaria usar $.each(ou underscore.js _.each) se você puder se safar do forloop bruto . O jsperf tem alguns testes de comparação que valem a pena ser executados.
nickb 5/12/11
3
Este ( jsperf.com/each-vs-each-vs-for-in/3 ) é mais realista porque emprega o filtro proto básica
dvdrtrgn
7

@all - tudo no JavaScript é um objeto (), então declarações como "use isso apenas em objetos" são um pouco enganadoras. Além disso, o JavaScript não é fortemente digitado, de forma que 1 == "1" é verdadeiro (embora 1 === "1" não seja, Crockford é importante nisso). Quando se trata do conceito programático de matrizes em JS, a digitação é importante na definição.

@Brenton - Não há necessidade de ser um ditador de terminologia; "matriz associativa", "dicionário", "hash", "objeto", todos esses conceitos de programação se aplicam a uma estrutura em JS. É o nome (chave, índice) pares de valores, onde o valor pode ser qualquer outro objeto (cadeias também são objetos)

Então, new Array()é o mesmo que[]

new Object() é aproximadamente semelhante a {}

var myarray = [];

Cria uma estrutura que é uma matriz com a restrição de que todos os índices (também conhecidos como chaves) devem ser um número inteiro. Também permite a atribuição automática de novos índices via .push ()

var myarray = ["one","two","three"];

É realmente melhor tratado via for(initialization;condition;update){

Mas e quanto a:

var myarray = [];
myarray[100] = "foo";
myarray.push("bar");

Tente o seguinte:

var myarray = [], i;
myarray[100] = "foo";
myarray.push("bar");
myarray[150] = "baz";
myarray.push("qux");
alert(myarray.length);
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}

Talvez não seja o melhor uso de uma matriz, mas apenas uma ilustração de que as coisas nem sempre são claras.

Se você conhece suas chaves e, definitivamente, se elas não são números inteiros, sua única opção de estrutura como matriz é o objeto.

var i, myarray= {
   "first":"john",
   "last":"doe",
   100:"foo",
   150:"baz"
};
for(i in myarray){
    if(myarray.hasOwnProperty(i)){  
        alert(i+" : "+myarray[i]);
    }
}
passear
fonte
"Só usar isso em objetos" significa não usá-lo em Arrays ou qualquer outra coisa que se estende Object, caso contrário, como você aponta, seria muito tolo uma vez que tudo se estende Objeto
Juan Mendes
'"matriz associativa", "dicionário", "hash", "objeto", todos esses conceitos de programação se aplicam a uma estrutura em JS.' Não. São conceitos diferentes de idiomas diferentes, com semelhanças entre si. Mas se você assumir que eles são / exatamente os mesmos / e que são usados ​​da mesma maneira, para os mesmos propósitos, você está se preparando para cometer alguns erros realmente estúpidos que você poderia evitar, sendo menos ignorante sobre como a linguagem você está usando obras.
Breton
2

Certamente é um pouco extremo dizer

... nunca use um loop for para enumerar sobre uma matriz. Nunca. Use bom e velho para (var i = 0; i <comprimento da arr.; I ++)

?

Vale destacar a seção no extrato de Douglas Crockford

... A segunda forma deve ser usada com objetos ...

Se você precisar de uma matriz associativa (também conhecida como hashtable / dictionary) em que as chaves sejam nomeadas em vez de indexadas numericamente, será necessário implementá-la como um objeto, por exemplo var myAssocArray = {key1: "value1", key2: "value2"...};.

Nesse caso, myAssocArray.lengthserá nulo (porque esse objeto não possui uma propriedade 'length') e você i < myAssocArray.lengthnão o levará muito longe. Além de proporcionar maior conveniência, eu esperaria que matrizes associativas ofereçam vantagens de desempenho em muitas situações, pois as chaves da matriz podem ser propriedades úteis (ou seja, propriedade ou nome do ID de um membro da matriz), o que significa que você não precisa iterar por um longo tempo. matriz avaliando repetidamente as instruções if para encontrar a entrada da matriz que você procura.

De qualquer forma, obrigado também pela explicação das mensagens de erro do JSLint, vou usar a verificação 'isOwnProperty' agora ao interagir através de minhas inúmeras matrizes associativas!

tomfumb
fonte
1
Você está profundamente confuso. Não existe "matrizes associativas" em javascript. Esse é estritamente um conceito de php.
Breton
É verdade que esses objetos não têm uma lengthpropriedade, mas você pode fazê-lo de outra forma:var myArr = []; myArr['key1'] = 'hello'; myArr['key2'] = 'world';
nyuszika7h
3
@ Nyuszika7H Esse é o caminho errado. Se você não precisa da Matriz indexada com número inteiro, não deve usá- var myArr = []la. var myArr = {}No PHP, elas são a mesma coisa, mas não no JS.
19712 Juan Mendes
A "matriz" associativa não é uma matriz.
Vincent McNabb 5/05
0

Apenas para adicionar ao tópico de em / para / $. Each, adicionei um caso de teste jsperf para usar $ .each vs for em: http://jsperf.com/each-vs-for-in/2

Diferentes navegadores / versões lidam com isso de maneira diferente, mas parece que $ .each e diretamente para in são as opções mais baratas em termos de desempenho.

Se você estiver usando o for para iterar através de um array / objeto associativo, sabendo o que está procurando e ignorando tudo o mais, use $ .each se usar o jQuery ou apenas para o in (e depois uma pausa; alcançou o que você sabe que deveria ser o último elemento)

Se você estiver iterando através de uma matriz para executar algo com cada par de chaves, deve usar o método hasOwnProperty se NÃO usar o jQuery e usar $ .each se usar o jQuery.

Sempre use for(i=0;i<o.length;i++)se você não precisar de uma matriz associativa ... lol chrome executou 97% mais rápido do que um para dentro ou$.each

Benno
fonte