我有一个包含对象和数组的嵌套数据结构。如何提取信息,即访问特定或多个值(或键)?
例如:
var data = {
code: 42,
items: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}]
};
我怎样才能访问name
中的第二个项目items
?
我有一个包含对象和数组的嵌套数据结构。如何提取信息,即访问特定或多个值(或键)?
例如:
var data = {
code: 42,
items: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}]
};
我怎样才能访问name
中的第二个项目items
?
JavaScript 只有一种可以包含多个值的数据类型:Object。一个阵列是对象的一种特殊形式。
(Plain) 对象具有形式
{key: value, key: value, ...}
数组具有以下形式
[value, value, ...]
数组和对象都公开一个key -> value
结构。数组中的键必须是数字,而任何字符串都可以用作对象中的键。键值对也称为“属性”。
可以使用点表示法访问属性
const value = obj.someProperty;
或括号表示法,如果属性名称不是有效的 JavaScript标识符名称[spec],或者名称是变量的值:
// 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];
因此,只能使用括号表示法访问数组元素:
const value = arr[5]; // arr.5 would be a syntax error
// property name / index as variable
const x = 5;
const value = arr[x];
JSON 是数据的文本表示,就像 XML、YAML、CSV 等一样。要处理这样的数据,首先必须将其转换为 JavaScript 数据类型,即数组和对象(以及如何处理这些刚刚解释过)。在 JavaScript 中解析 JSON的问题中解释了如何解析 JSON ?.
如何访问数组和对象是 JavaScript 的基本知识,因此建议阅读MDN JavaScript 指南,尤其是部分
嵌套数据结构是一个数组或对象,它引用其他数组或对象,即它的值是数组或对象。可以通过连续应用点或括号表示法来访问此类结构。
下面是一个例子:
const data = {
code: 42,
items: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}]
};
假设我们要访问name
第二项的 。
以下是我们如何逐步做到这一点:
正如我们所见,data
是一个对象,因此我们可以使用点表示法访问它的属性。该items
属性的访问方式如下:
data.items
该值是一个数组,要访问它的第二个元素,我们必须使用括号表示法:
data.items[1]
该值是一个对象,我们再次使用点表示法来访问该name
属性。所以我们最终得到:
const item_name = data.items[1].name;
或者,我们可以对任何属性使用括号表示法,特别是如果名称包含会使点表示法使用无效的字符:
const item_name = data['items'][1]['name'];
undefined
回来?大多数情况下,当您获取 时undefined
,对象/数组根本没有具有该名称的属性。
const foo = {bar: {baz: 42}};
console.log(foo.baz); // undefined
使用console.log
或console.dir
检查对象/数组的结构。您尝试访问的属性实际上可能是在嵌套对象/数组上定义的。
console.log(foo.bar.baz); // 42
如果属性名称未知或我们要访问的对象的所有属性/数组元素,我们可以使用for...in
[MDN]环为目的和for
[MDN]环用于在所有属性/元素阵列进行迭代。
对象
要迭代 的所有属性data
,我们可以像这样迭代对象:
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
}
根据对象的来源(以及您想要做什么),您可能必须在每次迭代中测试该属性是否真的是对象的属性,还是继承的属性。你可以用Object#hasOwnProperty
[MDN]来做到这一点。
作为for...in
with 的替代方法hasOwnProperty
,您可以使用Object.keys
[MDN]来获取属性名称数组:
Object.keys(data).forEach(function(prop) {
// `prop` is the property name
// `data[prop]` is the property value
});
数组
为了迭代data.items
数组的所有元素,我们使用一个for
循环:
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`.
}
也可以使用for...in
迭代数组,但有一些原因应该避免这样做:为什么在 JavaScript 中使用数组的 'for(var item in list)' 被认为是不好的做法?.
随着浏览器对 ECMAScript 5 支持的增加,数组方法forEach
[MDN] 也成为一个有趣的替代方案:
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)
});
在支持 ES2015 (ES6) 的环境中,您还可以使用[MDN]循环,它不仅适用于数组,还适用于任何可迭代的:for...of
for (const item of data.items) {
// `item` is the array element, **not** the index
}
在每次迭代中,for...of
直接给我们迭代的下一个元素,没有“索引”可以访问或使用。
除了未知的键之外,数据结构的“深度”(即有多少嵌套对象)也可能是未知的。如何访问深度嵌套的属性通常取决于确切的数据结构。
但是如果数据结构包含重复模式,例如二叉树的表示,解决方案通常包括递归 [维基百科]访问数据结构的每个级别。
这是获取二叉树的第一个叶节点的示例:
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);
访问具有未知键和深度的嵌套数据结构的更通用方法是测试值的类型并相应地采取行动。
这是一个示例,它将嵌套数据结构中的所有原始值添加到数组中(假设它不包含任何函数)。如果我们遇到一个对象(或数组),我们只需toArray
再次调用该值(递归调用)。
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));
由于复杂对象或数组的结构不一定很明显,我们可以检查每一步的值来决定如何进一步移动。console.log
[MDN]和console.dir
[MDN]帮助我们做到这一点。例如(Chrome 控制台的输出):
> console.log(data.items)
[ Object, Object ]
在这里我们看到这data.items
是一个包含两个元素的数组,这两个元素都是对象。在 Chrome 控制台中,甚至可以立即展开和检查对象。
> console.log(data.items[1])
Object
id: 2
name: "bar"
__proto__: Object
这告诉我们这data.items[1]
是一个对象,在展开它之后我们看到它具有三个属性id
,name
和__proto__
。后者是用于对象原型链的内部属性。但是,原型链和继承超出了这个答案的范围。
您可以通过这种方式访问它
data.items[1].name
或者
data["items"][1]["name"]
两种方式都是平等的。
对象和数组有很多内置方法可以帮助您处理数据。
注意:在许多示例中,我使用了箭头函数。它们类似于函数表达式,但它们在this
词法上绑定了值。
Object.keys()
, Object.values()
(ES 2017) 和Object.entries()
(ES 2017)Object.keys()
返回对象键Object.values()
的数组,返回对象值Object.entries()
的数组,并以格式返回对象的键和相应值的数组[key, value]
。
const obj = {
a: 1
,b: 2
,c: 3
}
console.log(Object.keys(obj)) // ['a', 'b', 'c']
console.log(Object.values(obj)) // [1, 2, 3]
console.log(Object.entries(obj)) // [['a', 1], ['b', 2], ['c', 3]]
Object.entries()
使用 for-of 循环和解构赋值const obj = {
a: 1
,b: 2
,c: 3
}
for (const [key, value] of Object.entries(obj)) {
console.log(`key: ${key}, value: ${value}`)
}
Object.entries()
用for-of 循环和解构赋值来迭代结果非常方便。
For-of 循环可让您迭代数组元素。语法是for (const element of array)
(我们可以const
用var
或替换let
,但const
如果我们不打算修改 ,最好使用element
)。
解构赋值允许您从数组或对象中提取值并将它们分配给变量。在这种情况下,const [key, value]
意味着不是将[key, value]
数组分配给 ,而是将该数组element
的第一个元素分配给key
,将第二个元素分配给value
。它相当于:
for (const element of Object.entries(obj)) {
const key = element[0]
,value = element[1]
}
如您所见,解构使这变得更简单。
Array.prototype.every()
和 Array.prototype.some()
如果指定的回调函数为数组的每个元素every()
返回,true
则该方法返回。如果指定的回调函数为某些(至少一个)元素返回,则该方法返回。true
some()
true
true
const arr = [1, 2, 3]
// true, because every element is greater than 0
console.log(arr.every(x => x > 0))
// false, because 3^2 is greater than 5
console.log(arr.every(x => Math.pow(x, 2) < 5))
// true, because 2 is even (the remainder from dividing by 2 is 0)
console.log(arr.some(x => x % 2 === 0))
// false, because none of the elements is equal to 5
console.log(arr.some(x => x === 5))
Array.prototype.find()
和 Array.prototype.filter()
这些find()
方法返回满足提供的回调函数的第一个元素。该filter()
方法返回满足提供的回调函数的所有元素的数组。
const arr = [1, 2, 3]
// 2, because 2^2 !== 2
console.log(arr.find(x => x !== Math.pow(x, 2)))
// 1, because it's the first element
console.log(arr.find(x => true))
// undefined, because none of the elements equals 7
console.log(arr.find(x => x === 7))
// [2, 3], because these elements are greater than 1
console.log(arr.filter(x => x > 1))
// [1, 2, 3], because the function returns true for all elements
console.log(arr.filter(x => true))
// [], because none of the elements equals neither 6 nor 7
console.log(arr.filter(x => x === 6 || x === 7))
Array.prototype.map()
该map()
方法返回一个数组,其中包含对数组元素调用提供的回调函数的结果。
const arr = [1, 2, 3]
console.log(arr.map(x => x + 1)) // [2, 3, 4]
console.log(arr.map(x => String.fromCharCode(96 + x))) // ['a', 'b', 'c']
console.log(arr.map(x => x)) // [1, 2, 3] (no-op)
console.log(arr.map(x => Math.pow(x, 2))) // [1, 4, 9]
console.log(arr.map(String)) // ['1', '2', '3']
Array.prototype.reduce()
该reduce()
方法通过调用提供的具有两个元素的回调函数将数组减少为单个值。
const arr = [1, 2, 3]
// Sum of array elements.
console.log(arr.reduce((a, b) => a + b)) // 6
// The largest number in the array.
console.log(arr.reduce((a, b) => a > b ? a : b)) // 3
该reduce()
方法采用可选的第二个参数,即初始值。当您调用的数组reduce()
可以有零个或一个元素时,这很有用。例如,如果我们想创建一个函数sum()
,它接受一个数组作为参数并返回所有元素的总和,我们可以这样写:
const sum = arr => arr.reduce((a, b) => a + b, 0)
console.log(sum([])) // 0
console.log(sum([4])) // 4
console.log(sum([2, 5])) // 7
如果您尝试item
通过id
or从示例结构访问 an name
,而不知道它在数组中的位置,最简单的方法是使用underscore.js库:
var data = {
code: 42,
items: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}]
};
_.find(data.items, function(item) {
return item.id === 2;
});
// Object {id: 2, name: "bar"}
根据我的经验,使用高阶函数而不是for
orfor..in
循环会导致代码更易于推理,因此更易于维护。
只有我的 2 美分。
有时,可能需要使用字符串访问嵌套对象。简单的方法是第一级,例如
var obj = { hello: "world" };
var key = "hello";
alert(obj[key]);//world
但是对于复杂的 json,情况通常不是这样。随着 json 变得越来越复杂,在 json 中查找值的方法也变得复杂。导航 json 的递归方法是最好的,并且如何利用递归将取决于正在搜索的数据类型。如果涉及条件语句,json 搜索可能是一个很好的工具。
如果正在访问的属性已知,但路径很复杂,例如在此对象中
var obj = {
arr: [
{ id: 1, name: "larry" },
{ id: 2, name: "curly" },
{ id: 3, name: "moe" }
]
};
并且你知道你想要得到对象中数组的第一个结果,也许你想使用
var moe = obj["arr[0].name"];
但是,这将导致异常,因为没有具有该名称的对象的属性。能够使用它的解决方案是展平对象的树方面。这可以递归地完成。
function flatten(obj){
var root = {};
(function tree(obj, index){
var suffix = toString.call(obj) == "[object Array]" ? "]" : "";
for(var key in obj){
if(!obj.hasOwnProperty(key))continue;
root[index+key+suffix] = obj[key];
if( toString.call(obj[key]) == "[object Array]" )tree(obj[key],index+key+suffix+"[");
if( toString.call(obj[key]) == "[object Object]" )tree(obj[key],index+key+suffix+".");
}
})(obj,"");
return root;
}
现在,可以将复杂对象展平
var obj = previous definition;
var flat = flatten(obj);
var moe = flat["arr[0].name"];//moe
这是jsFiddle Demo
正在使用的这种方法。