【问题标题】:How can I avoid traversing the same DOM route multiple times?如何避免多次遍历同一个 DOM 路由?
【发布时间】:2015-02-05 00:46:31
【问题描述】:

我有一个on 函数,其中有两个鼠标事件mouseentermouseleave。当这些事件被触发时,它们会运行不同的函数,一个添加一个类,另一个删除它。

$(this).siblings('.testimonial').find('p').addClass('unseen');

$(this).siblings('.testimonial').find('p').removeClass('unseen');

问题是,我正在执行以下 DOM 遍历两次:

$(this).siblings('.testimonial').find('p')

但我不知道如何将这种遍历保存为一个函数中的变量并将其用作另一个函数。这是我的完整 JS 代码:

JavaScript

(function ($) {

    var testimonial = $('.testimonial');
    var testimonialHeight = testimonial.outerHeight();
    var testimonialWidth = testimonial.outerWidth();

    testimonial.find('p').addClass('unseen');

    testimonial.css({
        height: testimonialHeight,
        width: testimonialWidth
    });

    $('.client').on({
        mouseenter: function() {
            $(this).siblings('.testimonial').find('p').removeClass('unseen');
        },
        mouseleave: function() {
            $(this).siblings('.testimonial').find('p').addClass('unseen');
        }
    });

})(jQuery);

HTML

<ul class="list-unstyled list-inline">
  <li>
    <div class="testimonial"><p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.<p></div>
      <img class="client" src="https://s3.amazonaws.com/uifaces/faces/twitter/jsa/128.jpg" alt="" />
  </li>
  <li>
    <div class="testimonial"><p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.</p></div>
    <img class="client" src="https://s3.amazonaws.com/uifaces/faces/twitter/gerrenlamson/128.jpg" alt="" />
  </li>
  <li>
    <div class="testimonial"><p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</p></div>
    <img class="client" src="https://s3.amazonaws.com/uifaces/faces/twitter/jadlimcaco/128.jpg" alt="" />
  </li>
</ul>

有人可以提出更好的方法吗?

谢谢。

【问题讨论】:

  • 请向我们展示相关的 HTML,以便我们能够理解您所询问的 DOM 遍历以及您询问的哪一段代码?另外,您要解决的确切问题是什么?代码不起作用吗?还是您认为它太慢了?
  • 当你可以做.testimonial p时,你为什么要找到p?另外,您是否尝试过使用hover?为什么不在一个变量中进行遍历,然后向该变量添加/隐藏类...有点像您对testimonial 所做的那样。
  • @jfriend00 我已经添加了上面的 HTML。代码有效,但问题是我在重复自己。我做了两次相同的 DOM 遍历。我希望代码能够工作,但我也希望它是 DRY。
  • @slime 因为当我使用testimonial.find('p') 时,我不会再次遍历整个 DOM。如果我使用$('.testimonial p'),那么我会的。我认为我在上面添加的 HTML 可能会让事情变得更清晰。
  • 你说的是这段代码的两个副本:$(this).siblings('.testimonial').find('p')?如果是这样,请在您的问题中这么说。您的许多代码都在进行 DOM 遍历,因此该短语本身并不能告诉我们您要询问的是哪一行代码。请努力使您的问题更加清楚。如果您要询问特定的代码行,请准确告诉我们是哪一行代码。

标签: javascript jquery dom traversal jquery-traversing


【解决方案1】:

您可以更改为对这两个事件使用一个公共事件处理程序,并根据它是哪个事件来设置操作:

$('.client').on("mouseenter mouseleave", function(e) {
    var method = e.type === "mouseenter" ? "removeClass" : "addClass";
    $(this).siblings('.testimonial').find('p')[method]('unseen');
});

下面是对发生的事情的解释:

  • .on("mouseenter mouseleave", function(e) {...}) 将多个事件连接到同一个事件处理程序。
  • e.type 是当前事件的事件名称,因此当您有多个事件触发同一个事件处理程序时,您可以查看触发的是哪个事件。
  • var method = e.type === "mouseenter" ? "removeClass" : "addClass" 类似于 if/else 语句,它根据e.type 的值将"removeClass""addClass" 分配给method 变量。它被称为ternary operator
  • obj[method] 是使用变量作为属性名称而不是字符串文字的属性引用。所以当method"addClass" 时,obj.addClassobj[method] 相同。在末尾添加() 使其成为函数调用,然后method"addClass"obj.addClass('unseen')obj[method]('unseen') 相同。

所以,再次打破最后一行:

// find the right paragraphs
$(this).siblings('.testimonial').find('p')

// get the property whose name is in the method variable
$(this).siblings('.testimonial').find('p')[method]

// call that property as a function and pass it 'unseen'
$(this).siblings('.testimonial').find('p')[method]('unseen');

一个可能对 DRY 有用的工具是 .hover(),因为它是 mouseenter 和 mouseleave 的快捷方式。如果您知道相关段落在悬停之前总是被标记为未见,并且页面中没有其他代码会与未见类混淆(您在问题中没有说什么),那么您可以使用 @987654342 的快捷方式@

$('.client').hover(function() {
    $(this).siblings('.testimonial').find('p').toggleClass('unseen');
});

将重复的代码移动到可以在两个地方使用的通用函数中的更常见方法如下所示:

function getSiblingParagraphs(parent) {
    return $(parent).siblings('.testimonial').find('p');
}

$('.client').on({
    mouseenter: function() {
        getSiblingParagraphs(this).removeClass('unseen');
    },
    mouseleave: function() {
        getSiblingParagraphs(this).addClass('unseen');
    }
 });

【讨论】:

  • @IeriOggiDomani - 使用一个 DRY 且紧凑的通用事件处理程序为我的答案添加了新方法。
  • 第一种方式有一些我不明白的概念,所以我还没有尝试过,但第二种方式正是我想要的。谢谢!
  • 顺便说一句,在第一个解决方案中,在线$(this).siblings('.testimonial').find('p')[method]('unseen');[method] 引用数组吗?为什么不关注.?正如你可能知道的那样,我远非中级:-P
  • @IeriOggiDomani - 我在回答中添加了一些解释。简短的回答是obj[someVariable] 是一种访问obj 的属性的方法,其属性名称在someVariable 中。当名称在变量中而不是在字符串文字中时,这是您访问属性的方式。所以,如果someVariable"removeClass",那么obj[someVariable]()obj.removeClass() 相同。当代码行中的所有内容都相同时,除了方法名称之外,这使得 DRY 变得更容易。您可以将方法名称放在变量中,然后使用obj[method] 语法执行语句。
  • 如果所有问题都与 DRYing 代码有关,最好使用相关方法作为悬停处理程序和 toggleClass() 方法
【解决方案2】:

如果确实需要缓存特定的同级子元素而不是所有.testimonial p,则可以使用以下逻辑:

$('.client').each(function () {
    this._testimonialP = $(this).siblings('.testimonial').find('p').addClass('unseen');// But .unseen should be set in HTML markup by default
}).hover(function () {
    this._testimonialP.toggleClass('unseen');
});

【讨论】:

  • 这将创建包含对其他 DOM 对象的引用的自定义 DOM 属性。过去,已知这会导致某些浏览器中的内存泄漏。如果任何同级或同级内容是动态创建或更改的,这也不起作用。
  • 这很好用。这甚至更好,因为如果 JavaScript 不工作,那么.testimonial p 默认会出现。如果你在JS中添加它,为什么会有评论说But .unseen should be set in HTML markup by default?还有一个问题(出于好​​奇)——为什么要在_testimonialP 变量前加上下划线?谢谢你的回答。
【解决方案3】:

如果您正在寻找一种 DRY 方法来执行此操作,您可以编写一个可重用的函数来找到您想要的兄弟姐妹。

function findTestimonialParagraph($root) {
    return $root.siblings('.testimonial').find('p');
}

$('.client').on({
    mouseenter: function() {
        findTestimonialParagraph($(this)).removeClass('unseen');
    },
    mouseleave: function() {
        findTestimonialParagraph($(this)).addClass('unseen');
    }
});

这样,如果您需要更改推荐段落的访问方式,您只需在一个地方进行。

【讨论】:

    【解决方案4】:

    您可以简单地执行以下操作:

    var $client = $('.client');
    var $clientTestimonialParagraphs = $client.siblings('.testimonial').find('p');
    
    $client.on({
            mouseenter: function() {
                $clientTestimonialParagraphs.removeClass('unseen');
            },
            mouseleave: function() {
                $clientTestimonialParagraphs.addClass('unseen');
            }
        });
    

    我希望这会有所帮助。

    【讨论】:

    • 也许是全局范围的污染?还有在使用之前保存的浪费?
    • 它不会污染全局范围......代码(来自作者问题)它在一个函数内(所以函数范围)。至于在使用之前保存一些东西......没有错,替代方案是惰性实例化;各有利弊。
    • 这可能行不通。假设可能有多个.client 对象,那么找到的段落必须与收到鼠标事件的段落相关,而不是全部。
    • 我已经编辑了这个问题并添加了 HTML 以显示 JS 正在处理什么。使用此解决方案,这将选择页面上.client所有 个实例。正如您在我的 HTML 中看到的,实际上有 3 个 divs 应用了 .client 类。这意味着当我悬停一个.client 时,每个div.testimonial 中的p 标记将应用unseen 类。不过感谢您的回答。
    【解决方案5】:

    尝试存储所选项目

    var p = testimonial.find('p').addClass('unseen');
    

    然后对存储的进行操作

    $('.clienet').hover(
        function(){ p.removeClass('unseen') }, 
        function(){ p.addClass('unseen') }
    )
    

    【讨论】:

    • 可能是全局范围的污染?
    • p 与特定的悬停客户端元素无关
    • 请编辑您的答案,并解释为什么这是一个好的解决方案。
    猜你喜欢
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 2012-02-19
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多