有没有一种方法可以在JavaScript中遍历可能包含自身的对象?

3
我想在Javascript中搜索特定字符串并遍历对象。不幸的是,该对象的构建方式使得简单地使用源代码和Ctrl-F查找字符串变得不可能,而且尝试遍历对象的递归函数也有可能永远陷入其中。
基本上,这个对象包含自己。不止一次,在很多地方都包含了自己。我不能简单地说“排除这些键”,因为这个对象是混淆的,所以我们会花费整天的时间列出键,并且即使我们完成了这个过程,我们也没有查看所有数据。
此外,我需要能够遍历__proto__和prototype,因为有些有用的字符串也隐藏在那里(但仅适用于函数和对象)。
虽然我更喜欢类似于findStuff(object,/string/ig)的函数,但这可能很难,因此任何功能只要明确标记了控制流一旦发现特定对象(函数、字符串等)后会经过的区域即可。谢谢,对于这样一个烦人的问题,我感到抱歉。
编辑:如果有帮助的话,我正在尝试遍历编译后的Construct2运行时对象。我不会把整个东西放在这里,因为无论多么宽容的pastebin都不适合它,而且我也不想意外发布没有权限提供的资源。(不过别担心,我不是在尝试盗版,我只是在尝试弄清楚一些用户界面功能。)
4个回答

2

当你遍历一个可能存在循环的对象时,保持已经遍历过的对象的备忘录,并且在你之前见过当前对象时停止遍历是一种标准技术。你可以使用Set来实现。


2
保持一个已递归对象的列表,然后将每个新对象与该列表进行比较。最初的回答。

const data = {
  foo: {
    bar: 1
  },
  one: 1,
  jaz: {
    hello: {
      x: 1
    }
  }
};

data.bar = data.foo;
data.foo.foo = data.foo;
data.jaz.hello.foo = data;

function search_for_1() {
  const seen = [];
  search(data);

  function search(object) {
    Object.values(object).forEach(value => {
      if (typeof value === "object") {
        if (seen.includes(value)) {
          console.log("Seen this already");
        } else {
          seen.push(value);
          search(value);
        }
      } else {
        if (value === 1) {
          console.log("Found 1");
        }
      }
    });
  }
}

search_for_1();


2
我认为,WeakSet 的查找性能比数组搜索要高得多,但是否可以使用取决于环境。 - Jonas Wilms

2
你可以使用WeakSet来跟踪已经遍历过的对象:

您可以使用WeakSet来跟踪已经遍历过的对象:


"Original Answer" 翻译成 "最初的回答"。
 function traverseOnce(obj, cb) {
   const visited = new WeakSet();
   (function traverse(obj) {
     for(const [key, value] of Object.entries(obj)) {
       if(typeof value === "object" && value !== null) {
          if(visited.has(value)) continue;
          visited.add(value);
          cb(value);
          traverse(value);
       }
      }
   })(obj);
 }

通过使用WeakSet,您可以获得O(1)的查找时间,并且确保不会发生内存泄漏。

可用于:

 const nested = { other: { a: 1 } };
 nested.self = nested;

 traverseOnce(nested, console.log);
 // nested: { other, self }
 // other: { a: 1 }

您还可以使用符号来标记已遍历的对象,将new WeakSet()替换为Symbol(),将visited.has(value)替换为value[visited],将visuted.add(value)替换为value[visited] = true;

注:Symbol是一种基本数据类型,表示独一无二的值。在这里,我们使用它来代替WeakSet作为标记已遍历对象的容器。


仅使用WeakSet是因为虽然Symbol的想法非常聪明,但它会修改遍历的每个对象的状态,这并不好。谢谢! - Twilight Sparkle
无论哪种方式是“更好”的,在你的使用场景中需要确定。我会认为(弱)集合方法在所有情况下都更为优越,因为您所需的所有清理工作仅涉及退出函数,而我所能看到的没有任何不利因素。 - Amadan
@amadan 它让我思考。如果函数退出,WeakSet 必须被垃圾回收,而符号非常轻量级。另一方面,我认为标志将保留在对象上,这意味着它们的大小会稍微增加,并且当添加标志时隐藏类必须更改。不过,这可能是过早的优化。 - Jonas Wilms
@JonasWilms 在 traverseOnce 的结尾加上 delete visited; 不就可以解决 WeakMap 的所有问题了吗? - Twilight Sparkle
@no_boot_device 好吧,垃圾收集器仍然会启动。我想我想太多了。“它确实修改了遍历到的每个对象的状态”...你说得对。我想我应该改变我的答案。 - Jonas Wilms

0

不要重复造轮子,有专门的库来处理这种事情。

我们使用 object-scan 来进行所有数据处理。一旦你理解了它,它非常强大。以下是它如何适用于你的问题。

// const objectScan = require('object-scan');

const traverse = (data) => objectScan(['**'], {
  filterFn: ({ key, value, parent }) => {
    // do something here
  },
  breakFn: ({ isCircular }) => isCircular === true
})(data);

const circular = { name: 'Max', age: 5, sex: undefined, details: { color: 'black', breed: undefined } };
circular.sex = circular;
circular.details.breed = circular;

console.log(traverse(circular));
/* =>
 [ [ 'details', 'breed' ],
   [ 'details', 'color' ],
   [ 'details' ],
   [ 'sex' ],
   [ 'age' ],
   [ 'name' ] ]
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

声明:本人是object-scan的作者。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接