Preliminares
JavaScript tiene un solo tipo de datos que puede contener múltiples valores: Objeto . Una matriz es una forma especial de objeto.
(Normal) Los objetos tienen la forma
{key: value, key: value, ...}
Las matrices tienen la forma
[value, value, ...]
Tanto las matrices como los objetos exponen una key -> value
estructura. Las claves en una matriz deben ser numéricas, mientras que cualquier cadena se puede usar como clave en los objetos. Los pares clave-valor también se denominan "propiedades" .
Se puede acceder a las propiedades utilizando la notación de puntos
const value = obj.someProperty;
o notación de paréntesis , si el nombre de la propiedad no sería un nombre de identificador de JavaScript válido [especificación] , o si el nombre es el valor de una variable:
// the space is not a valid character in identifier names
const value = obj["some Property"];
// property name as variable
const name = "some Property";
const value = obj[name];
Por esa razón, solo se puede acceder a los elementos de la matriz mediante la notación de paréntesis:
const value = arr[5]; // arr.5 would be a syntax error
// property name / index as variable
const x = 5;
const value = arr[x];
Espera ... ¿qué hay de JSON?
JSON es una representación textual de datos, al igual que XML, YAML, CSV y otros. Para trabajar con dichos datos, primero debe convertirse a tipos de datos de JavaScript, es decir, matrices y objetos (y cómo se explicó cómo trabajar con ellos). ¿Cómo analizar JSON se explica en la pregunta Parse JSON en JavaScript? .
Material de lectura adicional
Cómo acceder a matrices y objetos es un conocimiento fundamental de JavaScript y, por lo tanto, es recomendable leer la Guía de JavaScript de MDN , especialmente las secciones
Acceder a estructuras de datos anidados
Una estructura de datos anidados es una matriz u objeto que se refiere a otras matrices u objetos, es decir, sus valores son matrices u objetos. Se puede acceder a dichas estructuras aplicando consecutivamente la notación de punto o paréntesis.
Aquí hay un ejemplo:
const data = {
code: 42,
items: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}]
};
Supongamos que queremos acceder name
al segundo elemento.
Así es como podemos hacerlo paso a paso:
Como podemos ver data
es un objeto, por lo tanto, podemos acceder a sus propiedades utilizando la notación de puntos. Se items
accede a la propiedad de la siguiente manera:
data.items
El valor es una matriz, para acceder a su segundo elemento, tenemos que usar la notación de paréntesis:
data.items[1]
Este valor es un objeto y usamos la notación de puntos nuevamente para acceder a la name
propiedad. Entonces eventualmente obtenemos:
const item_name = data.items[1].name;
Alternativamente, podríamos haber usado la notación de corchetes para cualquiera de las propiedades, especialmente si el nombre contenía caracteres que lo hubieran hecho inválido para el uso de la notación de puntos:
const item_name = data['items'][1]['name'];
Estoy tratando de acceder a una propiedad pero solo obtengo la undefined
devolución.
La mayoría de las veces cuando obtienes undefined
, el objeto / matriz simplemente no tiene una propiedad con ese nombre.
const foo = {bar: {baz: 42}};
console.log(foo.baz); // undefined
Use console.log
o console.dir
e inspeccione la estructura del objeto / matriz. La propiedad a la que está intentando acceder podría estar definida en un objeto / matriz anidado.
console.log(foo.bar.baz); // 42
¿Qué sucede si los nombres de las propiedades son dinámicos y no los conozco de antemano?
Si los nombres de las propiedades son desconocidos o si queremos acceder a todas las propiedades de un objeto / elementos de una matriz, podemos usar el bucle for...in
[MDN] para los objetos y el for
[MDN] bucle para que las matrices iteren sobre todas las propiedades / elementos.
Objetos
Para iterar sobre todas las propiedades de data
, podemos iterar sobre el objeto así:
for (const prop in data) {
// `prop` contains the name of each property, i.e. `'code'` or `'items'`
// consequently, `data[prop]` refers to the value of each property, i.e.
// either `42` or the array
}
Dependiendo de dónde provenga el objeto (y qué desea hacer), es posible que deba probar en cada iteración si la propiedad es realmente una propiedad del objeto o si es una propiedad heredada. Puede hacer esto con Object#hasOwnProperty
[MDN] .
Como alternativa a for...in
with hasOwnProperty
, puede usar Object.keys
[MDN] para obtener una matriz de nombres de propiedades :
Object.keys(data).forEach(function(prop) {
// `prop` is the property name
// `data[prop]` is the property value
});
Matrices
Para iterar sobre todos los elementos de la data.items
matriz , usamos un for
bucle:
for(let i = 0, l = data.items.length; i < l; i++) {
// `i` will take on the values `0`, `1`, `2`,..., i.e. in each iteration
// we can access the next element in the array with `data.items[i]`, example:
//
// var obj = data.items[i];
//
// Since each element is an object (in our example),
// we can now access the objects properties with `obj.id` and `obj.name`.
// We could also use `data.items[i].id`.
}
También se podría usar for...in
para iterar sobre las matrices, pero hay razones por las cuales esto debe evitarse: ¿por qué es 'for (elemento var en la lista)' con las matrices consideradas una mala práctica en JavaScript? .
Con la creciente compatibilidad del navegador de ECMAScript 5, el método de matriz forEach
[MDN] también se convierte en una alternativa interesante:
data.items.forEach(function(value, index, array) {
// The callback is executed for each element in the array.
// `value` is the element itself (equivalent to `array[index]`)
// `index` will be the index of the element in the array
// `array` is a reference to the array itself (i.e. `data.items` in this case)
});
En entornos compatibles con ES2015 (ES6), también puede usar el bucle [MDN] , que no solo funciona para matrices, sino para cualquier iterable :for...of
for (const item of data.items) {
// `item` is the array element, **not** the index
}
En cada iteración, for...of
directamente nos da el siguiente elemento de la iterable, no hay "índice" para acceder o usar.
¿Qué pasa si la "profundidad" de la estructura de datos es desconocida para mí?
Además de las claves desconocidas, la "profundidad" de la estructura de datos (es decir, cuántos objetos anidados) tiene, también puede ser desconocida. La forma de acceder a propiedades profundamente anidadas generalmente depende de la estructura de datos exacta.
Pero si la estructura de datos contiene patrones repetitivos, por ejemplo, la representación de un árbol binario, la solución generalmente incluye acceder recursivamente [Wikipedia] a cada nivel de la estructura de datos.
Aquí hay un ejemplo para obtener el primer nodo hoja de un árbol binario:
function getLeaf(node) {
if (node.leftChild) {
return getLeaf(node.leftChild); // <- recursive call
}
else if (node.rightChild) {
return getLeaf(node.rightChild); // <- recursive call
}
else { // node must be a leaf node
return node;
}
}
const first_leaf = getLeaf(root);
const root = {
leftChild: {
leftChild: {
leftChild: null,
rightChild: null,
data: 42
},
rightChild: {
leftChild: null,
rightChild: null,
data: 5
}
},
rightChild: {
leftChild: {
leftChild: null,
rightChild: null,
data: 6
},
rightChild: {
leftChild: null,
rightChild: null,
data: 7
}
}
};
function getLeaf(node) {
if (node.leftChild) {
return getLeaf(node.leftChild);
} else if (node.rightChild) {
return getLeaf(node.rightChild);
} else { // node must be a leaf node
return node;
}
}
console.log(getLeaf(root).data);
Una forma más genérica de acceder a una estructura de datos anidados con claves y profundidad desconocidas es probar el tipo del valor y actuar en consecuencia.
Aquí hay un ejemplo que agrega todos los valores primitivos dentro de una estructura de datos anidados en una matriz (suponiendo que no contenga ninguna función). Si nos encontramos con un objeto (o matriz) simplemente llamamos toArray
nuevamente a ese valor (llamada recursiva).
function toArray(obj) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(toArray(value)); // <- recursive call
}
else {
result.push(value);
}
}
return result;
}
const data = {
code: 42,
items: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}]
};
function toArray(obj) {
const result = [];
for (const prop in obj) {
const value = obj[prop];
if (typeof value === 'object') {
result.push(toArray(value));
} else {
result.push(value);
}
}
return result;
}
console.log(toArray(data));
Ayudantes
Dado que la estructura de un objeto complejo o matriz no es necesariamente obvia, podemos inspeccionar el valor en cada paso para decidir cómo avanzar. console.log
[MDN] y console.dir
[MDN] nos ayudan a hacer esto. Por ejemplo (salida de la consola de Chrome):
> console.log(data.items)
[ Object, Object ]
Aquí vemos que data.items
es una matriz con dos elementos que son ambos objetos. En la consola de Chrome, los objetos pueden incluso expandirse e inspeccionarse de inmediato.
> console.log(data.items[1])
Object
id: 2
name: "bar"
__proto__: Object
Esto nos dice que data.items[1]
es un objeto, y después de expandirlo vemos que tiene tres propiedades id
, name
y __proto__
. Esta última es una propiedad interna utilizada para la cadena prototipo del objeto. Sin embargo, la cadena de prototipos y la herencia están fuera del alcance de esta respuesta.