8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

原生 JavaScript 事件委托

YAS_Bangamuwage 2月前

132 0

在 vanilla js 中进行事件委托的最佳方式(最快/正确)是什么?例如,如果我在 jQuery 中有这个:$('#main').on('click', '.focused', function(){ settingsPanel();})...

在 vanilla js 中进行事件委托的最佳方式(最快/最合适)是什么?

例如如果我在 jQuery 中有这个:

$('#main').on('click', '.focused', function(){
    settingsPanel();
});

我该如何将其转换为原生 js?或许可以用 .addEventListener()

我能想到的方法是:

document.getElementById('main').addEventListener('click', dothis);
function dothis(){
    // now in jQuery
    $(this).children().each(function(){
         if($(this).is('.focused') settingsPanel();
    }); 
 }

但这似乎效率低下,特别是在 #main 有很多孩子的情况下。

那么这是正确的做法吗?

document.getElementById('main').addEventListener('click', doThis);
function doThis(event){
    if($(event.target).is('.focused') || $(event.target).parents().is('.focused') settingsPanel();
}
帖子版权声明 1、本帖标题:原生 JavaScript 事件委托
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由YAS_Bangamuwage在本站《loops》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 我有一个这样的数组:var arr1 = [\'a\', \'b\', \'c\', \'d\']; 我怎样才能随机化/打乱它?

    我有一个这样的数组:

    var arr1 = ["a", "b", "c", "d"];
    

    我怎样才能随机化/打乱它?

  • 只需在这里提出这一点,您就可以使用 Mike Bostock 制作的这个可视化工具来直观地看到 shuffle 函数实际上有多么随机:

  • @MrGuru:因为这就是事件委托的工作方式。您必须查看元素层次结构中是否有任何节点与选择器匹配,从事件起源的元素开始,到绑定处理程序的元素停止。

  • 事实上的无偏洗牌算法是 Fisher–Yates(又名 Knuth)洗牌 .

    您可以 在此处看到出色的可视化效果 .

    function shuffle(array) {
      let currentIndex = array.length;
    
      // While there remain elements to shuffle...
      while (currentIndex != 0) {
    
        // Pick a remaining element...
        let randomIndex = Math.floor(Math.random() * currentIndex);
        currentIndex--;
    
        // And swap it with the current element.
        [array[currentIndex], array[randomIndex]] = [
          array[randomIndex], array[currentIndex]];
      }
    }
    
    // Used like so
    let arr = [2, 11, 37, 42];
    shuffle(arr);
    console.log(arr);
  • 我想出了一个简单的解决方案 ,它似乎效果很好(尽管支持旧版 IE)。在这里,我们扩展了 EventTarget 的原型,以提供一种 delegateEventListener 使用以下语法的方法:

    EventTarget.delegateEventListener(string event, string toFind, function fn)
    

    我创建了一个相当复杂的 fiddle 来演示它的实际操作,其中我们委托绿色元素的所有事件。停止传播继续起作用,您可以访问应该 event.currentTarget 通过的 this (与 jQuery 一样)。

    以下是完整的解决方案:

    (function(document, EventTarget) {
      var elementProto = window.Element.prototype,
          matchesFn = elementProto.matches;
    
      /* Check various vendor-prefixed versions of Element.matches */
      if(!matchesFn) {
        ['webkit', 'ms', 'moz'].some(function(prefix) {
          var prefixedFn = prefix + 'MatchesSelector';
          if(elementProto.hasOwnProperty(prefixedFn)) {
            matchesFn = elementProto[prefixedFn];
            return true;
          }
        });
      }
    
      /* Traverse DOM from event target up to parent, searching for selector */
      function passedThrough(event, selector, stopAt) {
        var currentNode = event.target;
    
        while(true) {
          if(matchesFn.call(currentNode, selector)) {
            return currentNode;
          }
          else if(currentNode != stopAt && currentNode != document.body) {
            currentNode = currentNode.parentNode;
          }
          else {
            return false;
          }
        }
      }
    
      /* Extend the EventTarget prototype to add a delegateEventListener() event */
      EventTarget.prototype.delegateEventListener = function(eName, toFind, fn) {
        this.addEventListener(eName, function(event) {
          var found = passedThrough(event, toFind, event.currentTarget);
    
          if(found) {
            // Execute the callback with the context set to the found element
            // jQuery goes way further, it even has it's own event object
            fn.call(found, event);
          }
        });
      };
    
    }(window.document, window.EventTarget || window.Element));
    
  • Kazz 2月前 0 只看Ta
    引用 7

    这很好地展示了理论,但实际实现时应该检查输入是否实际上是一个数组。事实上,如果你不小心传入了不同类型的值,while 循环将永远不会完成,程序将挂起。

  • Shaq 2月前 0 只看Ta
    引用 8

    当存在 lodah 之类的库时,不要将此类内容添加到代码中。代码膨胀是一个真正的危险,因此尽可能使用实用程序库。geeksforgeeks.org/lodash-_-shuffle-method

  • Wil 2月前 0 只看Ta
    引用 9

    的 JavaScript 实现 Durstenfeld shuffle ,它是 Fisher-Yates 的优化版本:

    /* Randomize array in-place using Durstenfeld shuffle algorithm */
    function shuffleArray(array) {
        for (var i = array.length - 1; i > 0; i--) {
            var j = Math.floor(Math.random() * (i + 1));
            var temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }
    

    它为每个原始数组元素挑选一个随机元素,并将其从下一次抽取中排除,就像从一副牌中随机挑选一样。

    这种巧妙的排除方法将选定的元素与当前元素交换,然后从余数中选择下一个随机元素,向后循环以获得最佳效率,确保随机选择被简化(它总是可以从 0 开始),从而跳过最后一个元素。

    算法运行时间为: O(n) . 请注意 ,shuffle 是就地完成的,因此如果您不想修改原始数组,请先使用 .slice(0) .


    编辑: 更新至 ES6 / ECMAScript 2015

    新的 ES6 允许我们一次分配两个变量。当我们想要交换两个变量的值时,这尤其方便,因为我们可以在一行代码中完成此操作。以下是使用此功能的同一函数的较短形式。

    function shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [array[i], array[j]] = [array[j], array[i]];
        }
    }
    
  • 我建议使用 window.Node 而不是 window.Element。文档本身继承自 Node,从而使其功能可以在 IE(没有 EventTarget)中运行。

  • 此答案中的实现有利于数组的下端。 发现困难的方法。 Math.random() 不应与循环计数器 + 1 相乘,而应与 array.lengt() 相乘。 请参阅在特定范围内生成 JavaScript 中的随机整数? 以获得非常全面的解释。

  • 无需改变内置原型(这会导致代码脆弱,并且经常会破坏某些功能),只需检查所点击的元素是否有与 .closest 所需选择器匹配的元素即可。如果有,则调用要调用的函数。例如,要翻译

    $('#main').on('click', '.focused', function(){
        settingsPanel();
    });
    

    在 jQuery 中,使用:

    document.querySelector('#main').addEventListener('click', (e) => {
      if (e.target.closest('#main .focused')) {
        settingsPanel();
      }
    });
    

    除非内部选择器也可能作为父元素存在(这可能非常不常见),否则将内部选择器单独传递给 .closest (例如 .closest('.focused') )就足够了。

    当使用这种模式时,为了保持紧凑,我经常将代码的主要部分放在早期返回的下面,例如:

    document.querySelector('#main').addEventListener('click', (e) => {
      if (!e.target.closest('.focused')) {
        return;
      }
      // code of settingsPanel here, if it isn't too long
    });
    

    现场演示:

    document.querySelector('#outer').addEventListener('click', (e) => {
      if (!e.target.closest('#inner')) {
        return;
      }
      console.log('vanilla');
    });
    
    $('#outer').on('click', '#inner', () => {
      console.log('jQuery');
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="outer">
      <div id="inner">
        inner
        <div id="nested">
          nested
        </div>
      </div>
    </div>
  • @MarjanVenema 不确定您是否还在关注这个空间,但这个答案是正确的,而您建议的更改实际上引入了偏见。请参阅 blog.codinghorror.com/the-danger-of-naivete 了解关于此错误的精彩文章。

  • 如果 DOM 尚未加载,document.querySelector 将无法运行。这不完全是委托。

  • 引用 15

    当然是 - 就像 OP 的 jQuery 代码 $('#main').on('click', '.focused', function(){ 将 #main 内部的点击委托给 #main 的点击监听器一样,我的代码也在做同样的事情。如果 #main 的子项是动态的(例如,如果 #main 是待办事项列表容器),则可以使用这种技术。如果 #main 本身可能不存在,那就是另一种情况,当然需要将事件委托给外部父级。

  • 引用 16

    重复 user94559 的评论并引用 en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 要交换的元素 (j) 应位于 0 和当前数组索引 (i) 之间

  • 我的错……楼主的案例确实可行。我应该详细说明一下,因为我正在寻找与 Turbolinks 兼容的解决方案。它删除了除文档和窗口之外的所有内容。我最终只使用了带有完整选择器的 .matches,而没有像上一个示例那样在文档上使用 querySelector。

  • 第二个代码片段使用了一个简单的 target.matches(),它与 ​​.closest() 不同,因为它要求事件直接在该元素上触发,而不是在其后代之一上触发。我猜这不是有意的。

  • @IRvanFauziE 该函数适用于您传递给它的数组。添加返回数组只会允许更轻松地与其他数组进行链接。

  • 我有一个类似的解决方案来实现事件委托。它利用数组函数 slice , reverse , filter forEach .

    • slice 将查询中的 NodeList 转换为数组,这必须在允许反转列表之前完成。
    • reverse 反转数组(使得最终的遍历尽可能接近事件目标。
    • filter 检查哪些元素包含 event.target .
    • forEach 只要处理程序不返回,就会为过滤结果中的每个元素调用提供的处理程序 false .

    函数返回创建的委托函数,这使得稍后可以移除监听器。注意,本机 event.stopPropagation() 不会停止遍历 validElements ,因为冒泡阶段已经遍历到委托元素。

    function delegateEventListener(element, eventType, childSelector, handler) {
        function delegate(event){
            var bubble;
            var validElements=[].slice.call(this.querySelectorAll(childSelector)).reverse().filter(function(matchedElement){
                return matchedElement.contains(event.target);
            });
            validElements.forEach(function(validElement){
                if(bubble===undefined||bubble!==false)bubble=handler.call(validElement,event);
            });
        }
        element.addEventListener(eventType,delegate);
        return delegate;
    }
    

    虽然不建议扩展原生原型,但可以将该函数添加到原型中 EventTarget (或 Node 在 IE 中)。执行此操作时, element 在函数中 this EventTarget.prototype.delegateEventListener = function(eventType, childSelector, handler){...} )。

返回
作者最近主题: