zoukankan      html  css  js  c++  java
  • [译]JavaScript:循环对象检测

    原文:http://blog.vjeux.com/2011/javascript/cyclic-object-detection.html


    包含循环结构的对象称之为循环对象,循环对象无法遍历,因为在遍历过程中会产生死循环.本文讲了三种用来检测一个对象是否循环对象的技术.

    译者注:创建循环对象

    作者没有讲怎么创建一个循环对象,我觉的有必要讲一下.循环对象是一个自身的某个属性指向自己的对象.可以这样来创建.

    var foo = {};
    foo["bar"] = foo;
    jQuery.param(foo);  //这是一个死循环,浏览器报错InternalError: too much recursion

    包含一个循环对象的对象也是循环对象

    var obj = {key:foo}
    jQuery.param(obj);  //InternalError: too much recursion

    还有一种Mozilla的私有技术可以创建循环对象,叫井号变量,不过从Firefox12起,已经废弃

    var foo = #1= {bar: #1#} 
    jQuery.param(a);            //InternalError: too much recursion

    你肯定用过循环对象,因为:

    window.window.window.window.window.window.window === window

    给对象的每个属性加标记

    想要检测一个对象内部是否包含了循环结构,最先想到的方法就是给每个节点添加标记.在遍历过程中,如果我们遇到一个已经被标记过的节点,也就说明该对象包含了循环结构.

    这种方法修改了一个不属于我们的对象.这是很危险的,会有很多其他的影响.

    • 使用什么来作为唯一的不与现有属性重复的标记键?我用了Math.random,但是这样仍然有可能和对象已有的属性重复,不仅会导致错误的判断结果,还会让该属性被误删除!
    • 添加一个新的属性,然后再删除它,这样会反复改变对象的内存占用,很可能导致内存拷贝(memory copy)以及内存空洞(memory holes).
    • 该方法不能处理Sealed objectsProxies.(译者注:因为无法添加属性)
    function isCyclic (obj) {
      var seenObjects = [];
      var mark = String(Math.random());
     
      function detect (obj) {
        if (typeof obj === 'object') {
          if (mark in obj) {
            return false;
          }
          obj[mark] = true;
          seenObjects.push(obj);
          for (var key in obj) {
            if (obj.hasOwnProperty(key) && !detect(obj[key])) {
              return false;
            }
          }
        }
        return true;
      }
     
      var result = detect(obj);
     
      for (var i = 0; i < seenObjects.length; ++i) {
        delete seenObjects[i][mark];
      }
     
      return result;
    }

    把标记存储在另外一个独立的数据结构中

    显然,我们应该避免编辑原对象,但该怎么避免呢?我想到的办法是使用一个数组把访问过的节点存储下来.然后使用indexOf方法,判断我们是否访问过这个节点,可以,这是一个O(n²)的复杂度,而上面的方法是O(n).

    function isCyclic (obj) {
      var seenObjects = [];
     
      function detect (obj) {
        if (typeof obj === 'object') {
          if (seenObjects.indexOf(obj) !== -1) {
            return true;
          }
          seenObjects.push(obj);
          for (var key in obj) {
            if (obj.hasOwnProperty(key) && detect(obj[key])) {
              return true;
            }
          }
        }
        return false;
      }
     
      return detect(obj);
    }

    underscore.js使用了这种方法来检测循环对象.

    利用原生的JSON.stringify

    最后一种方法有点技巧性.如果浏览器支持ES5中的JSON对象.JSON.stringify会在处理循环对象时抛出异常.

    function isCyclic(obj) {
        var isNativeJSON = typeof JSON !== 'undefined' && JSON.stringify.toString().match(/\n\s*\[native code\]\s*\n/);
        if (!isNativeJSON) {
            throw 'Native JSON.stringify is not available, can\'t use this technique.';
        }
        try {
            JSON.stringify(obj);
            return false;
        } catch (e) {
            return true;
        }
    }

    总结

    得出的结论有点让人沮丧,因为没有一种技术能完全满足我们.为了能够写出一种完美的进行循环对象检测的代码,我们需要一种哈希表结构的对象,但目前还没有(译者注:我们现在有Map了).

    如果你感兴趣的话,上面的这些源代码都存储在github上,另外还有用来比较这三种技术性能的jsperf.

    如果你需要存储或加载某个循环结构,可以使用Douglas Crockford的 decycle & retrocycle functions.

  • 相关阅读:
    Zend Studio
    mysql workbench
    phpmyadmin
    navicat for mysql
    phpstorm
    django中 debug-toolbar插件 crm项目补充
    clean_data 和 instance的区别
    elasticsearch 第一章 初识elasticsearch
    爬虫第七章 scrapy中间件 + 基于crawlSpider全站爬取网络数据
    爬虫第六章 scrapy的具体应用 5大核心组件 scrapy持久化存储 请求传参
  • 原文地址:https://www.cnblogs.com/ziyunfei/p/2725488.html
Copyright © 2011-2022 走看看