|

JavaScript不同绑定事件方式的异同

在 DOM 节点上绑定用户事件大致有三种方法,以绑定用户单击事件为例,分别为:

  • 在 HTML 节点标签的 onclick 属性上绑定;
  • 在节点对象的 onclick 属性上绑定;
  • 通过 addEventListener 添加 click 绑定;

本文主要整理介绍这三种常见方法的异同与利弊。

HTML标签内联绑定

标签内联绑定是早期比较常用的方法,以绑定单击事件为例:

<div onclick="alert('hello')">
    click me
</div>

与之对应的,是将事件函数写在 script 标签内,或者从外部引入,然后在 onclick 属性上绑定事件函数的调用。

如下:

<div onclick="userClick()">
    click me
</div>
<script>
    function userClick (e) {
        let event = e || window.event;
        console.log("hello");
    }
</script>

节点对象绑定

与内联绑定类似,但不同点是这种绑定方式全部在 JavaScript 中完成,也就是需要在 script 标签下完成。

如下:

<div id="div">
    click me
</div>

<script>
    // 直接使用匿名函数的方式绑定
    document.getElementById("div").onclick = function (e) {
        let event = e || window.event;
        console.log("hello");
    }

    // 或者引用一个具名函数的引用
    function userClick (e) {
        let event = e || window.event;
        console.log("hello");
    }
    document.getElementById("div").onclick = userClick;
</script>

无论使用上述那种方式绑定,onclick 属性都是保存事件函数的引用,所以这种方式只能实现单一绑定,即一个节点只能绑定一种事件函数,如果用户多次绑定,则最新绑定的函数会覆盖之前的函数,当用户触发事件时,只执行最新绑定的事件函数。

同时,这两种方式绑定的函数默认是不处理事件冒泡的。如果子元素和父元素都绑定了同名事件,那么在子元素上触发的事件会默认冒泡到父节点上。如果需要阻止事件冒泡,则可以通过 stopPropagation 来实现。

例如,组织子节点的 click 事件冒泡到父节点上:

<div id="father" onclick="userClick(event, 'father')">
    father
    <div id="child" onclick="userClick(event, 'child')">
        child
    </div>
</div>
<script>
    function userClick(e, name) {
        var event = e || window.event;
        console.log(event, name);
        // 使用 stopPropagation 阻止事件冒泡
        event.stopPropagation();
        // IE 浏览器阻止事件冒泡
        // window.event.cancelBubble = true;
    }
</script>

使用 addEventListener 绑定

使用 addEventListener 方式绑定的好处是它可以默认处理事件冒泡,该方法原型如下:

element.addEventListener(event, function, useCapture)

第三个参数 useCapture 表示事件获取的阶段,可取值:

  • true : 在事件捕获阶段获取事件并执行;
  • false: 在事件冒泡阶段获取事件并执行,默认为 false.

这里多出了一个概念,即 事件冒泡事件捕获 ,这其实是 DOM2 级事件中定义的两个事件阶段,用户从点击一个按钮,到完成响应这个过程其实有三个阶段:

  • 事件捕获阶段,事件从最外层的元素开始捕获,依次向内传递;
  • 事件获取阶段,如果事件传递阶段有某个元素定义了事件回调,则事件被获取并执行;
  • 事件冒泡阶段,当事件传递到最内层元素后,再有最内层元素一次向外层传递,直到根节点结束。

事件冒泡和事件捕获是两种不同的事件传递模型,DOM2 级事件将他们整合并规范化,默认情况下,使用 addEventListener 创建的事件回调会在冒泡阶段触发,但是我们可以通过第三个参数来修改事件的获取阶段。

如下:

<div id="father">
    father
    <div id="child">
        child
    </div>
</div>
<script>
    function userClick(e) {
        var event = e || window.event;
        console.log(event);
    }

    document.getElementById('father').addEventListener('click', userClick, true);
    document.getElementById('child').addEventListener('click', userClick, true);
</script>

上面的事件只会在捕获阶段获取,不会向上传递,所以点击子元素不会应该父元素。

以这种方式绑定的事件还有一个特点,即事件不是唯一的,对于一个节点对象可以调用 addEventListener 进行多次绑定,绑定的函数会按照绑定的顺序在用户触发时按顺序执行。

优化:事件代理

说到事件绑定就不得不说事件代理,事件代理是为了提高事件的性能。试想,加入有一个列表,列表的每一个元素都需要响应单击事件,加入有有 10000 个列表元素,我们是不太可能去绑定 10000 次的,那样只会带来性能损耗和资源浪费。一般这种情况可以在列表的父元素上绑定事件,通过父元素代理子元素的事件,这就是事件代理。

如下:

<ul id="father">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    ...
    <li>10000</li>
</ul>
<script>
    document.getElementById('father').addEventListener('click', function (e) {
        let event = e || window.event;
        console.log(event.target.innerText);
    });
</script>

上面代码在父元素 ul 上绑定了 click 事件代理,以代理其下所有 li 元素的 click 事件,由于事件冒泡的存在,当点击子元素 li 时,事件被冒泡到了 ul 元素上,所以父元素边能收到 click 事件,这便实现了代理。

引申:阻止默认行为

试想页面中有一个 a 标签,我们为他绑定了 click 事件函数,在用户点击该元素的时候,我们需要判断是否可以跳转,如果可以跳转,则通过 a 标签的 href 跳转,否则禁止跳转。

由于 a 标签本就是被设计为跳转功能的,所以禁止跳转即禁止了 a 的默认事件。

阻止默认事件的方式如下:

  • 直接在获取的事件函数中返回 false ;
  • 使用 event.preventDefault() 进行阻止;
  • IE浏览器需要使用 window.event.returnValue = false 来阻止。

不过,使用 return false 的方式有一定局限性,且只适用于传统方式绑定的情况下。

a 标签为例:

<a href="www.baidu.com" onclick="userClick()">click me</a>

可以在 userClick 函数中定义单击处理函数,并阻止页面跳转:

function userClick(e) {
    let event = e || window.event;
    // do something...

    // 阻止默认事件,页面将不会跳转
    // event.preventDefault();
    // IE 下阻止页面跳转
    // window.event.returnValue = false;
    // 返回 false 也可以阻止默认事件
    return false;
}

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注