¿Cómo obtener claves de matriz en Javascript?


80

Tengo una matriz creada con este código:

var widthRange = new Array();
widthRange[46] = { min:0,  max:52 };
widthRange[66] = { min:52, max:70 };
widthRange[90] = { min:70, max:94 };

Quiero obtener cada uno de los valores 46, 66, 90 en un bucle. Lo intenté, for (var key in widthRange)pero esto me da un montón de propiedades adicionales (supongo que son funciones en el objeto). No puedo usar un bucle for regular ya que los valores no son secuenciales.


9
Parece que tiene datos que, si bien tienen teclas numéricas, en realidad no son datos de matriz. Miraría usar un objeto regular aquí.
Quentin

@Quentin Se llama matriz dispersa. En cuanto al rendimiento, es mejor usar una matriz en lugar de un objeto aquí. Además, en cuanto al rendimiento, la mejor respuesta es ni siquiera en la lista: Array.prototype.forEach. Llamar Object.keysa una matriz no funciona bien porque los navegadores no la optimizan. for(var key in array)es malo porque atraviesa el prototipo y encadena cada tecla numérica que encuentra (convertir dobles a base10 es muy lento). forEach, sin embargo, fue diseñado exactamente con el propósito de iterar matrices dispersas y le dará a su código un rendimiento excelente en comparación con otras soluciones
Jack Giffin

Respuestas:


96

Debe llamar a la hasOwnPropertyfunción para verificar si la propiedad está realmente definida en el objeto en sí (a diferencia de su prototipo), así:

for (var key in widthRange) {
    if (key === 'length' || !widthRange.hasOwnProperty(key)) continue;
    var value = widthRange[key];
}

Tenga en cuenta que necesita una verificación separada para length.
Sin embargo, no debería utilizar una matriz aquí en absoluto; debería utilizar un objeto normal. Todos los objetos de Javascript funcionan como matrices asociativas.

Por ejemplo:

var widthRange = { };  //Or new Object()
widthRange[46] = { sel:46, min:0,  max:52 };
widthRange[66] = { sel:66, min:52, max:70 };
widthRange[90] = { sel:90, min:70, max:94 };

2
De Verdad? Vaya, aprendo algo todos los días aquí, y muy a menudo es de ti SLaks :-) Me sorprende que Array realice un seguimiento de sus índices establecidos explícitamente de esa manera. Quizás no debería estarlo.
Puntiagudo

@SLaks: Gracias, cambiarlo a un objeto parece la mejor solución. Una pregunta más: ¿hay alguna forma de iterar en orden inverso?
DisgruntledGoat

No; tendrá que hacerlo usted mismo.
SLaks

2
¿Por qué necesita un cheque por separado length? ¿No está marcado como [[DontEnum]]en todos los navegadores?
Roatin Marth

@Roatin: No lo sé. Más vale prevenir que lamentar.
SLaks


18

Si está realizando algún tipo de manipulación o inspección de matriz / colección, le recomiendo usar Underscore.js . Es pequeño, bien probado y le ahorrará días / semanas / años de dolor de cabeza de javascript. Aquí está su función de teclas:

Llaves

Recupera todos los nombres de las propiedades del objeto.

_.keys({one : 1, two : 2, three : 3});
=> ["one", "two", "three"]

4

Digamos que su matriz se ve como arr = [ { a: 1, b: 2, c: 3 }, { a: 4, b: 5, c: 6 }, { a: 7, b: 8, c: 9 } ](o posiblemente otras claves) que podría hacer

arr.map((o) => {
    return Object.keys(o)
}).reduce((prev, curr) => {
    return prev.concat(curr)
}).filter((col, i, array) => {
    return array.indexOf(col) === i
});

["a", "b", "c"]


3
for (var i = 0; i < widthRange.length; ++i) {
  if (widthRange[i] != null) {
    // do something
  }
}

Realmente no puede obtener solo las claves que ha configurado porque no es así como funciona una matriz. Una vez que configura el elemento 46, también tiene configurado de 0 a 45 (aunque son nulos).

Siempre puedes tener dos matrices:

var widthRange = [], widths = [], newVal = function(n) {
  widths.push(n);
  return n;
};
widthRange[newVal(26)] = { whatever: "hello there" };

for (var i = 0; i < widths.length; ++i) {
  doSomething(widthRange[widths[i]]);
}

edita bien puede ser que aquí estoy todo mojado ...


Las matrices solo mantienen un registro de la longitud, no atraviesan y crean muchos objetos. Básicamente, cuando se encuentra un nuevo índice, comprueba si ese nuevo índice es mayor que la longitud ... si es que ese nuevo índice se convierte en la nueva longitud. Por eso, es posible obtener solo los índices que definió con un bucle for-in
Bob


1

Tu ejemplo original funciona bien para mí:

<html>
<head>
</head>
<body>
<script>
var widthRange = new Array();
widthRange[46] = { sel:46, min:0,  max:52 };
widthRange[66] = { sel:66, min:52, max:70 };
widthRange[90] = { sel:90, min:70, max:94 };

var i = 1;
for (var key in widthRange)
{
    document.write("Key #" + i + " = " + key + "; &nbsp;&nbsp;&nbsp; min/max = " + widthRange[key].min + "/" + widthRange[key].max + "<br />");
    i++;
}
</script>
</html>

Resultados en el navegador (Firefox 3.6.2 en Windows XP):

Key #1 = 46;     min/max = 0/52
Key #2 = 66;     min/max = 52/70
Key #3 = 90;     min/max = 70/94

2
Apuesto a que está importando Prototype o algo así. En general, esos bucles "in" son riesgosos.
Puntiagudo

Bueno, estoy usando jQuery pero también Joomla que usa mootools, algo está agregando esas propiedades.
DisgruntledGoat

1

Creo que deberías usar un Object ( {}) y no una matriz ( []) para esto.

Un conjunto de datos está asociado con cada clave. Grita por usar un objeto. Hacer:

var obj = {};
obj[46] = { sel:46, min:0,  max:52 };
obj[666] = { whatever:true };

// This is what for..in is for
for (var prop in obj) {
  console.log(obj[prop]);
}

Quizás algunas cosas de utilidad como esta puedan ayudar:

window.WidthRange = (function () {
  var obj = {};
  return {
    getObj: function () {return obj;}
    , add: function (key, data) {
        obj[key] = data;
        return this; // enabling chaining
      }
  }
})();

// Usage (using chaining calls):
WidthRange.add(66, {foo: true})
.add(67, {bar: false})
.add(69, {baz: 'maybe', bork:'absolutely'});

var obj = WidthRange.getObj();
for (var prop in obj) {
  console.log(obj[prop]);
}

0

Parece funcionar.

var widthRange = new Array();
widthRange[46] = { sel:46, min:0,  max:52 };
widthRange[66] = { sel:66, min:52, max:70 };
widthRange[90] = { sel:90, min:70, max:94 };

for (var key in widthRange)
{
    document.write(widthRange[key].sel + "<br />");
    document.write(widthRange[key].min + "<br />");
    document.write(widthRange[key].max + "<br />");
}

0

Escribí una función que funciona bien con cada instancia de Objects (las matrices son esas).

Object.prototype.toArray = function()
{
    if(!this)
    {
      return null;
    }

    var c = [];

    for (var key in this) 
    {
        if ( ( this instanceof Array && this.constructor === Array && key === 'length' ) || !this.hasOwnProperty(key) ) 
        {
            continue;
        }

        c.push(this[key]);
    }

    return c;
};

Uso:

var a   = [ 1, 2, 3 ];
a[11]   = 4;
a["js"] = 5;

console.log(a.toArray());

var b = { one: 1, two: 2, three: 3, f: function() { return 4; }, five: 5 };
b[7] = 7;

console.log(b.toArray());

Salida:

> [ 1, 2, 3, 4, 5 ]
> [ 7, 1, 2, 3, function () { return 4; }, 5 ]

Puede ser útil para cualquiera.


2
Extender un prototipo que usted no creó es una muy mala idea, por muy conveniente que crea que es.
doug65536

0

... ????

Alternativamente, si tiene una lista de elementos que desea utilizar ...

var range = [46, 66, 90]
    , widthRange=[]
    , write=[];

    widthRange[46] = { min:0, max:52 }; 
    widthRange[66] = { min:52, max:70 }; 
    widthRange[90] = { min:70, max:94 }; 

for(var x=0; x<range.length; x++){var key, wr;

    key = range[x];

    wr = widthRange[key] || false;

    if(wr===false){continue;}

    write.push(['key: #',key, ', min: ', wr.min, 'max:', wr.max].join(''));

    }

0

Para sus datos de entrada:

let widthRange = new Array()
widthRange[46] = { min:0,  max:52 }
widthRange[61] = { min:52, max:70 }
widthRange[62] = { min:52, max:70 }
widthRange[63] = { min:52, max:70 }
widthRange[66] = { min:52, max:70 }
widthRange[90] = { min:70, max:94 }

Enfoque declarativo:

const relevantKeys = [46,66,90]
const relevantValues = Object.keys(widthRange)
    .filter(index => relevantKeys.includes(parseInt(index)))
    .map(relevantIndex => widthRange[relevantIndex])

Object.keyspara obtener las claves, usando parseIntpara lanzarlas como números. filterpara conseguir solo los que quieras. mappara construir una matriz a partir del objeto original de solo los índices que busca, ya que Object.keyspierde los valores del objeto.

Depurar:

console.log(widthRange)
console.log(relevantKeys)
console.log(relevantValues)

0

La pregunta es bastante antigua, pero hoy en día puede usar forEach, que es eficiente y conservará las claves como números:

let keys = widthRange.map((v,k) => k).filter(i=>i!==undefined))

Esto recorre widthRange y crea una nueva matriz con el valor de las claves, y luego filtra todas las ranuras de sparce tomando solo los valores que están definidos.

(Mala idea, pero para thorughness: Si la ranura 0 siempre estaba vacío, que podría reducirse a filter(i=>i)ofilter(Boolean)

Y, puede ser menos eficiente, pero los números se pueden lanzar con let keys = Object.keys(array).map(i=>i*1)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.