¿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.

Cabra descontenta
fuente
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 };
SLaks
fuente
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
84

Las claves en cadena se pueden consultar con Object.keys(array).

thSoft
fuente
4
Esto devuelve las claves como cadenas, no números, para que todos sepan.
Hutch Moore
La mejor solución ... TY
Carlos Galeano
¡Me alegró el día! : D <3 Esto lo resolvió donde array.keys()no devolvió nada.
Christopher Stock
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"]
Mike McKay
fuente
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"]

Morten
fuente
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 ...

Puntiagudo
fuente
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
2
widthRange.map(function(_, i) { return i });

o

widthRange.map((_, i) => i);
alexndreazevedo
fuente
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
Andy Shellam
fuente
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]);
}
npup
fuente
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 />");
}
Michael D. Irizarry
fuente
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.

schwarzkopfb
fuente
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(''));

    }
Yann Carter
fuente
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)
S ..
fuente
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)

SamGoody
fuente