原始简化答案
如果您不介意放弃 IE 并主要支持更多当前浏览器,请使用更新的方法(请查看 kangax's es6 table 以了解兼容性)。您可以为此使用 es2015 generators。我已经相应地更新了@TheHippo 的答案。当然,如果你真的想要 IE 支持,你可以使用babel JavaScript 转译器。
// Implementation of Traverse
function* traverse(o, path=[]) {
for (var i in o) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath,o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i], itemPath);
}
}
}
// Traverse usage:
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse({
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
})) {
// do something here with each key and value
console.log(key, value, path, parent);
}
如果您只想要自己的可枚举属性(基本上是非原型链属性),您可以将其更改为使用 Object.keys 和 for...of 循环进行迭代:
function* traverse(o,path=[]) {
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath,o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i],itemPath);
}
}
}
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse({
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
})) {
// do something here with each key and value
console.log(key, value, path, parent);
}
编辑:这个编辑后的答案解决了无限循环遍历。
停止讨厌的无限对象遍历
这个编辑后的答案仍然提供了我原始答案的额外好处之一,它允许您使用提供的generator function 以使用更简洁的iterable interface(考虑使用for of 循环,如for(var a of b)其中b 是可迭代的,a 是可迭代的元素)。通过使用生成器函数以及作为一个更简单的 api,它还有助于代码重用,因此您不必在任何想要深入迭代对象属性的地方重复迭代逻辑,并且还可以 @987654339 @ 退出循环,如果您想提前停止迭代。
我注意到尚未解决且不在我的原始答案中的一件事是,您应该小心遍历任意(即任何“随机”集合)对象,因为 JavaScript 对象可以是自引用的。这创造了无限循环遍历的机会。然而,未修改的 JSON 数据不能自引用,因此如果您使用这个特定的 JS 对象子集,您不必担心无限循环遍历,您可以参考我的原始答案或其他答案。这是一个非结束遍历的示例(注意它不是一段可运行的代码,否则它会导致您的浏览器选项卡崩溃)。
此外,在我编辑的示例中的生成器对象中,我选择使用Object.keys 而不是for in,它只迭代对象上的非原型键。如果您想要包含原型键,您可以自己换掉它。请参阅下面我的原始答案部分,了解Object.keys 和for in 的两种实现。
更糟 - 这将在自引用对象上无限循环:
function* traverse(o, path=[]) {
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath, o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* traverse(o[i], itemPath);
}
}
}
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
// this self-referential property assignment is the only real logical difference
// from the above original example which ends up making this naive traversal
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
为了避免这种情况,你可以在闭包中添加一个集合,这样当函数第一次被调用时,它就开始构建它已经看到的对象的内存,并且一旦遇到已经看到的对象就不会继续迭代.下面的代码 sn-p 就是这样做的,因此可以处理无限循环的情况。
更好 - 这不会在自引用对象上无限循环:
function* traverse(o) {
const memory = new Set();
function * innerTraversal (o, path=[]) {
if(memory.has(o)) {
// we've seen this object before don't iterate it
return;
}
// add the new object to our memory.
memory.add(o);
for (var i of Object.keys(o)) {
const itemPath = path.concat(i);
yield [i,o[i],itemPath, o];
if (o[i] !== null && typeof(o[i])=="object") {
//going one step down in the object tree!!
yield* innerTraversal(o[i], itemPath);
}
}
}
yield* innerTraversal(o);
}
//your object
var o = {
foo:"bar",
arr:[1,2,3],
subo: {
foo2:"bar2"
}
};
/// this self-referential property assignment is the only real logical difference
// from the above original example which makes more naive traversals
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
console.log(o);
//that's all... no magic, no bloated framework
for(var [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}
编辑:此答案中的所有上述示例都经过编辑,以包含根据@supersan's request 从迭代器产生的新路径变量。 path 变量是一个字符串数组,其中数组中的每个字符串代表每个键,这些键被访问以从原始源对象获取结果迭代值。路径变量可以输入lodash's get function/method。或者你可以编写你自己的 lodash 的 get 版本,它只处理这样的数组:
function get (object, path) {
return path.reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object);
}
const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(get(example, ["a", "0"]));
console.log(get(example, ["c", "d", "0"]));
console.log(get(example, ["b"]));
// these paths do not exist on the object
console.log(get(example, ["e", "f", "g"]));
console.log(get(example, ["b", "f", "g"]));
你也可以这样设置函数:
function set (object, path, value) {
const obj = path.slice(0,-1).reduce((obj, pathItem) => obj ? obj[pathItem] : undefined, object)
if(obj && obj[path[path.length - 1]]) {
obj[path[path.length - 1]] = value;
}
return object;
}
const example = {a: [1,2,3], b: 4, c: { d: ["foo"] }};
// these paths exist on the object
console.log(set(example, ["a", "0"], 2));
console.log(set(example, ["c", "d", "0"], "qux"));
console.log(set(example, ["b"], 12));
// these paths do not exist on the object
console.log(set(example, ["e", "f", "g"], false));
console.log(set(example, ["b", "f", "g"], null));
2020 年 9 月编辑:我添加了一个父对象,以便更快地访问上一个对象。这可以让您更快地构建反向遍历器。您也可以随时修改遍历算法以进行广度优先搜索而不是深度优先搜索,这实际上可能更可预测,事实上这里是a TypeScript version with Breadth First Search。由于这是一个 JavaScript 问题,我将把 JS 版本放在这里:
var TraverseFilter;
(function (TraverseFilter) {
/** prevents the children from being iterated. */
TraverseFilter["reject"] = "reject";
})(TraverseFilter || (TraverseFilter = {}));
function* traverse(o) {
const memory = new Set();
function* innerTraversal(root) {
const queue = [];
queue.push([root, []]);
while (queue.length > 0) {
const [o, path] = queue.shift();
if (memory.has(o)) {
// we've seen this object before don't iterate it
continue;
}
// add the new object to our memory.
memory.add(o);
for (var i of Object.keys(o)) {
const item = o[i];
const itemPath = path.concat([i]);
const filter = yield [i, item, itemPath, o];
if (filter === TraverseFilter.reject)
continue;
if (item !== null && typeof item === "object") {
//going one step down in the object tree!!
queue.push([item, itemPath]);
}
}
}
}
yield* innerTraversal(o);
}
//your object
var o = {
foo: "bar",
arr: [1, 2, 3],
subo: {
foo2: "bar2"
}
};
/// this self-referential property assignment is the only real logical difference
// from the above original example which makes more naive traversals
// non-terminating (i.e. it makes it infinite loop)
o.o = o;
//that's all... no magic, no bloated framework
for (const [key, value, path, parent] of traverse(o)) {
// do something here with each key and value
console.log(key, value, path, parent);
}