迭代嵌套的JavaScript对象

New*_*his 50 javascript iteration

我正在尝试遍历嵌套对象以检索由字符串标识的特定对象.在下面的示例对象中,标识符字符串是"label"属性.我无法绕过如何遍历树以返回适当的对象.任何帮助或建议将不胜感激.

var cars = {
  label: 'Autos',
  subs: [
    {
      label: 'SUVs',
      subs: []
    },
    {
      label: 'Trucks',
      subs: [
        {
          label: '2 Wheel Drive',
          subs: []
        },
        {
          label: '4 Wheel Drive',
          subs: [
            {
              label: 'Ford',
              subs: []
            },
            {
              label: 'Chevrolet',
              subs: []
            }
          ]
        }
      ]
    },
    {
      label: 'Sedan',
      subs: []
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

Pet*_*son 43

您可以创建这样的递归函数来对cars对象进行深度优先遍历.

var findObjectByLabel = function(obj, label) {
    if(obj.label === label) { return obj; }
    for(var i in obj) {
        if(obj.hasOwnProperty(i)){
            var foundLabel = findObjectByLabel(obj[i], label);
            if(foundLabel) { return foundLabel; }
        }
    }
    return null;
};
Run Code Online (Sandbox Code Playgroud)

这可以这样称呼

findObjectByLabel(car, "Chevrolet");
Run Code Online (Sandbox Code Playgroud)

  • 递归对于非常深的对象是不好的.你会得到堆栈溢出. (4认同)
  • @ArjunU。拥有数百级深度的对象是相当罕见的。 (3认同)
  • @Vishal如果没有`obj.hasOwnProperty(i)`,将包含自定义原型属性.例如,如果你定义`Array.prototype.first = function(a){return a [0]}`,那么`for(var i in [])`将包含prototype属性`first`. (3认同)
  • 我收到此错误“RangeError:超出最大调用堆栈大小” (3认同)

Men*_*ris 16

如果你想深深迭代到一个复杂的(嵌套)对象为每个键和值,你可以这样做使用Object.keys() 递归

const iterate = (obj) => {
    Object.keys(obj).forEach(key => {

    console.log(`key: ${key}, value: ${obj[key]}`)

    if (typeof obj[key] === 'object') {
            iterate(obj[key])
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

REPL示例

  • 如果对象在嵌套项方面“大”,您将收到堆栈溢出错误。最好使用迭代技术。 (2认同)
  • 清晰简洁!解决了我的用例(幸运的是不是一个大堆栈)。感谢分享! (2认同)

Jac*_*fin 13

- , ,

function forEachNested(O, f, cur){
    O = [ O ]; // ensure that f is called with the top-level object
    while (O.length) // keep on processing the top item on the stack
        if(
           !f( cur = O.pop() ) && // do not spider down if `f` returns true
           cur instanceof Object && // ensure cur is an object, but not null 
           [Object, Array].includes(cur.constructor) //limit search to [] and {}
        ) O.push.apply(O, Object.values(cur)); //search all values deeper inside
}
Run Code Online (Sandbox Code Playgroud)

要使用上述函数,请将数组作为第一个参数传递,将回调函数作为第二个参数传递。回调函数在调用时将接收 1 个参数:正在迭代的当前项目。

function forEachNested(O, f, cur){
    O = [ O ]; // ensure that f is called with the top-level object
    while (O.length) // keep on processing the top item on the stack
        if(
           !f( cur = O.pop() ) && // do not spider down if `f` returns true
           cur instanceof Object && // ensure cur is an object, but not null 
           [Object, Array].includes(cur.constructor) //limit search to [] and {}
        ) O.push.apply(O, Object.values(cur)); //search all values deeper inside
}
Run Code Online (Sandbox Code Playgroud)

“作弊”替代方案可能是JSON.stringify用于迭代。但是,JSON.stringify将调用toString它传递的每个对象的方法,如果您对toString.

function forEachNested(O, f, v){
    typeof O === "function" ? O(v) : JSON.stringify(O,forEachNested.bind(0,f));
    return v; // so that JSON.stringify keeps on recursing
}
Run Code Online (Sandbox Code Playgroud)

(function(){"use strict";

var cars = {"label":"Autos","subs":[{"label":"SUVs","subs":[]},{"label":"Trucks","subs":[{"label":"2 Wheel Drive","subs":[]},{"label":"4 Wheel Drive","subs":[{"label":"Ford","subs":[]},{"label":"Chevrolet","subs":[]}]}]},{"label":"Sedan","subs":[]}]};

var lookForCar = prompt("enter the name of the car you are looking for (e.g. 'Ford')") || 'Ford';
lookForCar = lookForCar.replace(/[^ \w]/g, ""); // incaseif the user put quotes or something around their input
lookForCar = lookForCar.toLowerCase();

var foundObject = null;
forEachNested(cars, function(currentValue){
    if(currentValue.constructor === Object &&
      currentValue.label.toLowerCase() === lookForCar) {
        foundObject = currentValue;
    }
});
if (foundObject !== null) {
    console.log("Found the object: " + JSON.stringify(foundObject, null, "\t"));
} else {
    console.log('Nothing found with a label of "' + lookForCar + '" :(');
}

function forEachNested(O, f, cur){
    O = [ O ]; // ensure that f is called with the top-level object
    while (O.length) // keep on processing the top item on the stack
        if(
           !f( cur = O.pop() ) && // do not spider down if `f` returns true
           cur instanceof Object && // ensure cur is an object, but not null 
           [Object, Array].includes(cur.constructor) //limit search to [] and {}
        ) O.push.apply(O, Object.values(cur)); //search all values deeper inside
}

})();
Run Code Online (Sandbox Code Playgroud)

然而,虽然上述方法对于演示目的可能有用,Object.values但 Internet Explorer 不支持,并且代码中有许多非常糟糕的地方:

  1. 代码更改了输入参数的值(参数)[第 2 行和第 5 行],
  2. 代码在每个项目上调用Array.prototype.pushArray.prototype.pop[第 5 行和第 8 行],
  3. 代码只对构造函数进行指针比较,它不适用于窗口外对象 [第 7 行],
  4. 代码复制了从Object.values[第 8 行]返回的数组,
  5. 代码未本地化window.Objectwindow.Object.values[第 9 行],
  6. 并且代码不必要地在数组上调用 Object.values [第 8 行]。

下面是一个快得多的版本,应该比任何其他解决方案都快得多。下面的解决方案修复了上面列出的所有性能问题。然而,它以一种截然不同的方式进行迭代:它首先迭代所有数组,然后迭代所有对象。它继续迭代其当前类型,直到完全耗尽,包括正在迭代的当前风味的当前列表中的迭代子值。然后,该函数迭代所有其他类型。通过在切换之前迭代直到耗尽,迭代循环变得比其他方式更热并且迭代得更快。这个方法还有一个额外的好处:对每个值调用的回调函数传递了第二个参数。第二个参数是从返回的数组Object.values 在父哈希对象或父数组本身上调用。

var getValues = Object.values; // localize
var type_toString = Object.prototype.toString;
function forEachNested(objectIn, functionOnEach){
    "use strict";
    functionOnEach( objectIn );
    
    // for iterating arbitrary objects:
    var allLists = [  ];
    if (type_toString.call( objectIn ) === '[object Object]')
        allLists.push( getValues(objectIn) );
    var allListsSize = allLists.length|0; // the length of allLists
    var indexLists = 0;
    
    // for iterating arrays:
    var allArray = [  ];
    if (type_toString.call( objectIn ) === '[object Array]')
        allArray.push( objectIn );
    var allArraySize = allArray.length|0; // the length of allArray
    var indexArray = 0;
    
    do {
        // keep cycling back and forth between objects and arrays
        
        for ( ; indexArray < allArraySize; indexArray=indexArray+1|0) {
            var currentArray = allArray[indexArray];
            var currentLength = currentArray.length;
            for (var curI=0; curI < currentLength; curI=curI+1|0) {
                var arrayItemInner = currentArray[curI];
                if (arrayItemInner === undefined &&
                    !currentArray.hasOwnProperty(arrayItemInner)) {
                    continue; // the value at this position doesn't exist!
                }
                functionOnEach(arrayItemInner, currentArray);
                if (typeof arrayItemInner === 'object') {
                    var typeTag = type_toString.call( arrayItemInner );
                    if (typeTag === '[object Object]') {
                        // Array.prototype.push returns the new length
                        allListsSize=allLists.push( getValues(arrayItemInner) );
                    } else if (typeTag === '[object Array]') {
                        allArraySize=allArray.push( arrayItemInner );
                    }
                }
            }
            allArray[indexArray] = null; // free up memory to reduce overhead
        }
         
        for ( ; indexLists < allListsSize; indexLists=indexLists+1|0) {
            var currentList = allLists[indexLists];
            var currentLength = currentList.length;
            for (var curI=0; curI < currentLength; curI=curI+1|0) {
                var listItemInner = currentList[curI];
                functionOnEach(listItemInner, currentList);
                if (typeof listItemInner === 'object') {
                    var typeTag = type_toString.call( listItemInner );
                    if (typeTag === '[object Object]') {
                        // Array.prototype.push returns the new length
                        allListsSize=allLists.push( getValues(listItemInner) );
                    } else if (typeTag === '[object Array]') {
                        allArraySize=allArray.push( listItemInner );
                    }
                }
            }
            allLists[indexLists] = null; // free up memory to reduce overhead
        }
    } while (indexLists < allListsSize || indexArray < allArraySize);
}
Run Code Online (Sandbox Code Playgroud)

function forEachNested(O, f, v){
    typeof O === "function" ? O(v) : JSON.stringify(O,forEachNested.bind(0,f));
    return v; // so that JSON.stringify keeps on recursing
}
Run Code Online (Sandbox Code Playgroud)

如果您在循环引用方面遇到问题(例如,对象 A 的值是对象 A 本身,例如对象 A 包含自身),或者您只需要键,那么可以使用以下较慢的解决方案。

function forEachNested(O, f){
    O = Object.entries(O);
    var cur;
    function applyToEach(x){return cur[1][x[0]] === x[1]} 
    while (O.length){
        cur = O.pop();
        f(cur[0], cur[1]);
        if (typeof cur[1] === 'object' && cur[1].constructor === Object && 
          !O.some(applyToEach))
            O.push.apply(O, Object.entries(cur[1]));
    }
}
Run Code Online (Sandbox Code Playgroud)

因为这些方法不使用任何类型的递归,所以这些函数非常适合可能有数千个深度级别的领域。堆栈限制因浏览器而异,因此在 Javascript 中递归到未知深度并不是很明智。

  • 你如何运行这个函数?你通过什么? (2认同)