【问题标题】:Circular dependency between JavaScript Class and jQuery objectJavaScript 类和 jQuery 对象之间的循环依赖
【发布时间】:2013-08-09 10:52:19
【问题描述】:

我正在尝试使用现有的工作代码库并使用 JavaScript 使其面向对象。我的系统采用一对多关系中包含 groupsitems 的 JSON,并在页面上将其可视化。这些项目可以移动到不同的组中,并且它们在这些组中的位置也需要计算。因此,需要建立了解周围团体和门票的活动。

我正在使用John Resig's 简单的JavaScript 继承设置来建立两个类,ItemGroup。当每个Item 被实例化时,它会引用它的父Group我的问题出现在我想建立我的事件时,最容易用以下函数解释:

var Group = Class.extend({
  ...
  // Calculate where to place the new item within the group
  calculate_new_position: function(item) {
    var pos = -1;
    // Loop through all DOM items in groups body
    $(".item", this.elBody).each(function(i) {

      // Retrieve it's class object
      var next = $(this).data("_obj");

      // Calculating things using the class reference
      var lowerPrio = item.tData.priority < next.tData.priority,
          lowerId =  item.id < next.id;

      if(lowerPrio || lowerId) {
        pos = i;
        return false;
      }
    });
    return pos;
  },
  ...
)};

注意上面sn-p中.data("_obj")的使用。本质上,当我需要对项目进行排序等操作时,我需要知道组中每个项目的 DOM(视图/控制器)对应的对象(模型)。现在,我可以建立我的Group 类,这样当我创建每个Item 时,我会从我的Group 中添加对它的引用(例如Group.items = [i1, i2...]),而不是迭代DOM 元素,我可以迭代 Item 实例。但是我认为我会遇到类似的问题,例如当我想将Item 移动到不同的Group 时(因为Group 不会知道Item)。

长话短说:有一个类在实例化时创建一个 DOM 元素,然后又指向该类,这在本质上是危险的/幼稚的/毫无意义的吗?这感觉像是循环依赖和一些递归的噩梦,但也许对对象的引用并不是一件可怕的事情。如果我在做其他任何事情真的愚蠢,并且有一个更简单的方法,那么也请指出这一点。

【问题讨论】:

  • 你看过Backbone.js是怎么做的吗? view 对象包含对 model(您的 Item、Group 类)和 DOM 元素 el 的引用。有多种方法,Backbone.js 不推荐任何一种方法:backbonejs.org/#FAQ-tim-toady

标签: javascript jquery oop circular-dependency


【解决方案1】:

任何现代浏览器垃圾收集器都可以处理循环引用。如果您丢失对对象的所有引用并从 DOM 中删除所有 HTML 节点,您的对象将被垃圾回收(而它在 DOM 中,浏览器将看到对您的对象的引用并防止它被垃圾回收)。不过要小心使用事件处理程序,如果您不删除它们,它们也会保留引用。

我听说一些旧版本的 IE 确实存在循环引用的问题。更多深入解释: Precise explanation of JavaScript <-> DOM circular reference issue

从那个答案: jQuery 的 .data() 使事情更可靠,因为旧版本的 IE 在添加到 DOM 元素的属性方面存在特殊问题,并且没有正确处理涉及这些属性中数据的循环引用,因此不会在应该释放的时候释放东西有(泄漏)。 .data() 通过仅在 DOM 元素上使用一个添加的属性来解决此问题,该属性是一个安全、不泄漏的字符串。然后,该字符串是一个 javascript 对象的键,该对象可以包含您希望与 DOM 元素关联的所有属性。因为这些属性都存储在纯javascript中,浏览器没有循环引用错误,这样做不会导致泄漏。

您可能想要查看 D3.js,它的功能与您想要的类似。它将数据绑定到 DOM 元素并提供生成可视化的简单方法: http://d3js.org/

使用 D3,您可以将一个数字数组绑定到一个圆形 SVG 标签数组,并使每个圆的半径基于与其关联的数组的编号。

编辑:我忘了提到如果你使用 $.remove() 事件处理程序也会被删除。但是,例如,如果您有一个位于闭包内的事件处理程序,该处理程序还引用了您的(已删除)HTML 节点,则它不会被垃圾收集。这通常不是什么大问题,因为一旦它在 DOM 之外,它就不会消耗太多资源,而且据我所知,不可能递归地堆叠这些闭包引用。

【讨论】:

    【解决方案2】:

    当使用jQuery.data 时,它不会对 dom 元素做任何事情。 dom 元素只是将原始键条目存储到 jQuery 内部缓存中,不管*。因此,您创建的引用是从 JS 到 JS:

    var div = document.createElement("div");
    $(div).data("asd", "daa")
    console.log(div[jQuery.expando]);
    //41
    console.log(jQuery.cache[41].data.asd);
    //"daa"
    

    (显然以上是 jQuery 内部结构,不应在生产代码中依赖)

    也就是说,如果你不通过 jQuery 进行所有操作,数据就会在那里泄漏,因为如果你背着它,jQuery 将不会收到通知。

    小部件类的最佳实践是提供一个destroy 方法,该方法删除它负责的所有元素,取消绑定它附加的任何事件并将其所有 DOM 引用设置为 null。

    *如果绝对没有关于元素的事件或数据,则条目不存在。但是如果一个条目由于某种原因而存在,那么使用 jQuery.data 将不会与元素本身进行交互。

    【讨论】:

      【解决方案3】:

      @Hoffmann 的回答很好,因为他提供了实用的建议和解释。我提供了不同的视角,灵感来自 Backbone.js 的工作原理。这是@vsr 在他们最初的评论中建议的扩展。

      基本要点:根本不要将 JS 对象存储在 $.data() 中。

      使用 $.data() 建议您使用 $(foo) 来查找 DOM 对象,然后深入了解它们的属性以获取您的自定义 JS 函数,这可能会在更多 DOM 元素上运行以获取它们的 自定义函数。这过于复杂且效率低下。

      更好的方法是创建一组“纯 JS”对象,使用适合您需要的任何语法和机制。然后,这些对象可以有一个属性指向它们的 DOM 元素,以便在少数需要它的情况下使用。 Backbone(再次)建议同时缓存 jQuery 选择的 DOM 元素是一种很好的做法。处理这些“纯 JS”对象将比通过 $.data() 更快、更容易维护。它还增加了 JS 和 HTML 之间的抽象,这是一件好事。

      此外,我发现一个很好的工作方式是不要嵌套你的对象,即使只有一对多的关系。不要使“组”的“项目”属性。相反,维护组和项目的数组,并让其中一个(或两者)通过引用而不是成员关系来管理关联。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-02-04
        • 2014-11-10
        • 1970-01-01
        • 2020-12-20
        • 2011-11-08
        • 1970-01-01
        • 2018-04-01
        相关资源
        最近更新 更多