原生js事件绑定和事件委托

本文介绍了一种在不引入大型库的情况下,通过在HTML构造函数上扩展支持,实现轻量级的事件绑定和事件委托的方法。该方法支持类似于jQuery的on/off/one用法,适用于小型项目的快速开发。

最近常在移动端开发,由于不是大型站点,不需要使用vue等库,也不需要引用jquery和zepto等。

写得最多得就是各种元素选择器和事件绑定,操作下来也是有点烦人。这时候怀念起jquery的美好来了,但是仅为了这个引用这么大个库,实在时下不了手,思考了一下,直接在html构造函数上拓展支持了。

按照习惯来走,一般我们会喜欢在选择的元素上直接 on + 事件 ,加上相应的逻辑函数完成一个事件绑定的。

这里要说明一下,js的事件绑定是特殊的,不同于老旧版本的事件绑定

element.onclick = function () {}

这种方式绑定的事件,后面有其他相同绑定事件,会覆盖前面的逻辑代码,因此我们会使用 addEventListener 实现绑定,按照绑定事件的顺序执行所有操作。

element.addEventListener('click', function() {
    console.log(1)
}, false)

element.addEventListener('click', function() {
    console.log(2)
}, false)

>> 1
>> 2

再深一步思考一下,什么是 事件委托 ,意义是什么。

事件委托,实际是把目标元素的事件绑定到其他元素上,借助触发事件。举个例子,我需要做个div容器展示文章,容器有个按钮,需要点击放大字体。这里你可以考虑把事件绑定到按钮上,也可以绑定到div上,两者实现效果并无不同。

但是要记得一点,所有的元素查找和事件绑定都是需要消耗性能和内存的,而复杂页面上的操作区域可能多达数十上百,每一个都独立绑定事件,页面在低端设备上也会出现执行卡顿的情况,影响体验。此外,常见需求里是有未来事件绑定的,你无法为尚未存在的元素绑定事件。

原生js实现委托一般是从event属性中获取当前点击位置的元素,并对元素进行逻辑代码。因此,可以考虑将两者做到一起。

/** 
 * 事件绑定和事件委托
 * 类似jQuery的on/off/one的用法
 * @author mo
 * @param eventName 事件名称
 * @param selector 委托的子元素,可以为空
 * @param callback 事件逻辑代码
 * @description 在选择的元素上直接用on/off/one就好了,同时存在自身绑定事件和委托事件的,如需解绑,要分别解绑,仅off选择元素的事件是不会解绑代理的事件的
 * @example
 *
 * var test = document.querySelector('.test')
 *
 * test.on('click', function () {
 *    // TODO 
 * })
 *
 * test.one('click', function () {
 *    // TODO 
 * })
 *
 * test.on('click', '.childElement', function () {
 *    // TODO 
 * })
 *
 * test.off('click')
 *
 * test.off('click', '.childElement')
 *
 */

var p_n_space = /(^\s*)|(\s*$)/g, // 前后空格正则
    bind_list = {} // 绑定事件列表

HTMLElement.prototype.on = function(eventName, selector, callback) {

    // 预处理参数
    if (!eventName || !selector) {
        console.log('Arguments is require!')
        return false
    } else {

        eventName = eventName.toLowerCase()

        if (typeof selector == 'function') {
            callback = selector
            selector = null
        }
    }

    // 事件绑定逻辑

    if (!bind_list.eventName) bind_list.eventName = []

    bind_list.eventName.push({
        selector: selector,
        fn: function(event) {

            var ev = event || window.event,
                target = ev.target || ev.srcElement,
                targets, sSets, i = j = 0

            if (!selector) {

                // 当前元素绑定
                callback.apply(this, [ev])

                if (this.once) {
                    delete this.once
                    this.off(eventName, callback)
                }

            } else {

                targets = selector.split(',')

                for (; i < targets.length; i++) {

                    // 删除前后空格
                    targets[i] = targets[i].replace(p_n_space, '')

                    // 遍历集合
                    sSets = this.querySelectorAll(targets[i])

                    // 如果集合为空则说明不存在这种委托元素,不做处理
                    if (sSets.length) {
                        // 关系拆分
                        // targets[i] = targets[i].split(/\s+/g).reverse()

                        // 事件委托
                        for (var j = 0; j < sSets.length; j++) {
                            if (target === sSets[j]) {

                                callback.apply(target, [ev])

                                if (this.once) {
                                    delete this.once
                                    this.off(eventName, selector, callback)
                                }

                                break;
                            }
                        }

                    } else {
                        return false
                    }

                }

            }
        }
    })

    // 所有事件,包括委托事件都绑定到目标元素本身
    if (this.addEventListener) {
        this.addEventListener(eventName, bind_list.eventName[bind_list.eventName.length - 1].fn, false);
    } else if (this.attachEvent) {
        this.attachEvent("on" + eventName, bind_list.eventName[bind_list.eventName.length - 1].fn);
    }

}

// 移除全部事件
HTMLElement.prototype.off = function(eventName, selector) {

    if (!selector) selector = null

    // 预处理参数
    if (!eventName) {
        console.log('Arguments is require!')
        return false
    } else {

        eventName = eventName.toLowerCase()

    }


    // 遍历已添加列表
    for (var k = 0; k < bind_list.eventName.length; k++) {
        // 仅移除相关的事件,分目标元素和委托元素绑定的事件
        if (bind_list.eventName[k] && selector == bind_list.eventName[k].selector) {
            if (this.removeEventListener) {
                this.removeEventListener(eventName, bind_list.eventName[k].fn, false);
            } else if (this.detachEvent) {
                this.detachEvent("on" + eventName, bind_list.eventName[k].fn);
            }
        }

        // 移除
        bind_list.eventName[k] = null
    }


}

// 一次性事件
HTMLElement.prototype.one = function(eventName, selector, callback) {
    this.once = true
    this.on(eventName, selector, callback)
}

需要注意,匿名function,即使写得一模一样,但是实际也是两个不同的对象。而原生js解绑事件时是需要传入相同的函数对象的,因此要具名声明函数,为了方便管理,我全放在了一个统一变量中保存,不需要时指向于null,让浏览器回收内存。

此外,一次性事件的实现很简单,就是执行一次后解绑事件即可。

整理代码后,之后的绑定操作就不用干重复活咯~

<think>我们使用JavaScript原生给元素绑定点击事件,主要有几种方式: 1. 直接在HTML元素标签中使用onclick属性(不推荐,因为混合了HTMLJavaScript) 2. 使用DOM元素的onclick属性(只能绑定一个处理函数) 3. 使用addEventListener方法(推荐,可以绑定多个事件处理函数,且更灵活) 下面分别给出示例: 方法一:直接在HTML中(不推荐) ```html <button onclick="handleClick()">Click Me</button> <script> function handleClick() { alert('Button clicked!'); } </script> ``` 方法二:使用元素的onclick属性(只能绑定一个函数) 假设我们有一个按钮,其id为"myButton"。 ```html <button id="myButton">Click Me</button> <script> document.getElementById('myButton').onclick = function() { alert('Button clicked!'); }; // 如果再次绑定,会覆盖上一个 document.getElementById('myButton').onclick = function() { alert('Another click handler!'); // 这个会覆盖上面的 }; </script> ``` 方法三:使用addEventListener(推荐) 使用addEventListener可以绑定多个事件处理函数,并且不会覆盖。 同时,它提供了更强大的功能,比如捕获阶段冒泡阶段的设置。 ```html <button id="myButton">Click Me</button> <script> var button = document.getElementById('myButton'); button.addEventListener('click', function() { alert('First handler!'); }); button.addEventListener('click', function() { alert('Second handler!'); // 两个都会执行 }); </script> ``` 另外,我们也可以使用removeEventListener来移除事件,但需要注意,移除的函数必须绑定的函数是同一个(所以通常使用函数表达式时,需要保留函数的引用)。 示例:绑定命名函数,以便移除 ```javascript function handleClick() { alert('Handler that will be removed!'); } button.addEventListener('click', handleClick); // 之后可以移除 button.removeEventListener('click', handleClick); ``` 此外,addEventListener的第三个参数是一个可选对象或布尔值,用于指定事件处理函数是在捕获阶段执行还是在冒泡阶段执行。默认是冒泡阶段(false)。 例如,设置捕获阶段: ```javascript button.addEventListener('click', function() { alert('This happens during capture phase'); }, true); // 第三个参数为true,表示在捕获阶段处理 ``` 在现代浏览器中,第三个参数也可以是一个选项对象: ```javascript button.addEventListener('click', function() { alert('This handler will only be triggered once'); }, { once: true }); // 只触发一次 ``` 注意:addEventListener在IE8及以下版本不支持,需要使用attachEvent(但IE8以下已经很少需要考虑了)。 因此,在一般情况下,我们推荐使用addEventListener来绑定事件。 下面给出一个完整的示例,包括如何兼容旧版IE(如果需要的话): ```html <button id="myButton">Click Me</button> <script> var button = document.getElementById('myButton'); if (button.addEventListener) { // 标准浏览器 button.addEventListener('click', handler, false); } else if (button.attachEvent) { // IE8及以下 button.attachEvent('onclick', handler); } function handler() { alert('Button clicked!'); } </script> ``` 但是,由于现在IE8及以下的使用率已经非常低,所以通常我们只需使用addEventListener即可。 综上所述,给元素绑定点击事件最推荐的方法是使用addEventListener。 解释: - addEventListener方法允许我们为同一个事件添加多个监听器,而不会覆盖已有的事件处理函数。 - 它提供了更精确的控制(捕获阶段/冒泡阶段)。 - 支持更现代的选项,如once(只执行一次)、passive(告诉浏览器事件处理函数不会调用preventDefault)等。 因此,在开发中应当优先使用addEventListener。 §§相关问题§§: 1. JavaScript事件处理模型中的捕获阶段冒泡阶段有什么区别? 2. 如何阻止事件冒泡?如何阻止事件的默认行为? 3. 事件委托是什么?有什么好处?如何实现? 4. 在JavaScript中,有哪些常见的事件类型(除了click以外)? </think>### 在 JavaScript 中为元素绑定点击事件 以下是 3 种原生绑定点击事件的方法(推荐使用 `addEventListener`): #### 1. 使用 `addEventListener`(推荐) ```html <button id="btn1">按钮1</button> <script> const button = document.getElementById('btn1'); // 绑定点击事件 button.addEventListener('click', function(event) { console.log('按钮被点击了', event.target); alert('Hello from addEventListener!'); }); // 可绑定多个不冲突的事件 button.addEventListener('click', () => { console.log('第二个处理函数'); }); </script> ``` **解释**: - 使用 `addEventListener` 可以绑定多个事件处理函数 - `event` 参数包含事件信息(如触发元素 `event.target`) - 不会覆盖元素已有的其他点击事件 - 可使用 `removeEventListener` 精准移除事件 #### 2. 使用 `onclick` 属性 ```html <button id="btn2">按钮2</button> <script> document.getElementById('btn2').onclick = function() { alert('Hello from onclick!'); // 注意:再次赋值会覆盖此处理函数 }; </script> ``` **解释**: - 直接赋值给元素的 `onclick` 属性 - 简单但只能绑定一个处理函数 - 新赋值会覆盖旧的处理函数 #### 3. HTML 内联事件(不推荐) ```html <button onclick="handleClick()">按钮3</button> <script> function handleClick() { alert('Hello from HTML handler!'); } </script> ``` **解释**: - 在 HTML 标签中直接添加 `onclick` 属性 - 违反关注点分离原则(混合 HTML JS) - 不方便维护调试 #### 完整示例 ```html <!DOCTYPE html> <body> <button id="method1">方法1</button> <button id="method2">方法2</button> <button onclick="inlineHandler()">方法3</button> <script> // 方法1: addEventListener(推荐) document.getElementById('method1').addEventListener('click', function(e) { console.log('Method1 - 事件类型:', e.type); alert('addEventListener 方式'); }); // 方法2: onclick 属性 document.getElementById('method2').onclick = function() { alert('onclick 属性方式'); }; // 方法3: HTML内联事件 function inlineHandler() { alert('HTML内联事件方式'); } </script> </body> </html> ``` #### 最佳实践建议: 1. **优先使用 `addEventListener`** - 支持多个事件监听器 - 可使用捕获/冒泡阶段控制 - 支持事件委托 2. 事件委托示例(高效处理动态元素): ```javascript // 父元素代理子元素点击事件 document.getElementById('parent').addEventListener('click', (e) => { if(e.target.matches('.child')) { console.log('点击的子元素:', e.target); } }); ``` #### 注意事项: - 使用 `addEventListener` 时避免匿名函数(如需移除事件) - 事件处理函数中的 `this` 指向触发事件的元素 - IE8 及以下需使用 `attachEvent`(已淘汰)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值