【JavaScript】事件

事件的使用

绑定事件

  1. DOM0 写法:dom.on事件类型 = listener
  2. DOM2 写法:dom.addEventListener('事件类型', listener, 配置对象)

listener 可以被指定为一个回调函数 / 对象(其 handleEvent() 方法用作回调函数)。

例如,一个可同时处理 fullscreenchangefullscreenerror 事件的函数如下:

function eventHandler(event) {
    if (event.type === 'fullscreenchange') {
        /* 处理 fullscreenchange 事件 */
    } else {
        /* 处理 fullscreenerror 事件 */
    }
}

配置对象 的可用属性:

  • capture 可选:默认为 false-在冒泡阶段触发回调;true-在捕获阶段触发回调。
  • once 可选:默认为 falsetrue-触发回调之后自动移除事件。
  • passive 可选:默认为 falsetrue-表示告诉浏览器,回调中不会调用 preventDefault 方法。如果回调中仍然调用了 preventDefault 方法,客户端会忽略它并抛出一个控制台警告。
  • signal 可选:AbortSignal,该 AbortSignal 的 abort 方法被调用时,监听器会被移除。
const controller = new AbortController();

const ele = document.querySelector('#box');
ele.addEventListener(
    'click',
    () => {
        console.log('I am clicked');
    },
    { signal: controller.signal }
);

controller.abort(); // 移除监听器

推荐使用 addEventListener 来注册事件监听器,理由如下:

  1. addEventListener 允许为一个事件添加多个监听器,所有的 listener 会按绑定顺序触发回调。
  2. addEventListener 可以控制 listener 的触发阶段,即可以选择捕获/冒泡阶段触发。
  3. addEventListener 的事件目标可以是一个文档上的元素 Element、Document 和 Window,也可以是任何支持事件的对象(比如 XMLHttpRequest),而不仅仅是 HTML / SVG 元素。

解除绑定

  1. DOM0 写法:dom.on事件类型 = null
  2. DOM2 写法:dom.removeEventListener('事件类型', listener)

唯一需要 removeEventListener 检测的是 capture / useCapture 标志。这个标志必须与 addEventListener 的对应标志匹配,其他标志则不需要。


passive

将 passive 设为 true 可以启用性能优化,它可以让开发者告诉浏览器,事件监听器中不会调用 event.preventDefault() 方法来阻止事件的默认行为。这样,浏览器就可以在两个线程里同时执行监听器中的 JS 代码和浏览器的默认行为,避免了等待和阻塞,提高了页面滑动的流畅度。

根据规范,addEventListener 的 passive 默认值始终为 false。但这可能会大大降低浏览器处理页面滚动的性能,因为触摸事件和滚轮事件的事件监听器在浏览器尝试滚动页面时,有可能会阻塞浏览器的主线程。

为了避免这一问题,大部分浏览器(Safari 除外)将文档级节点 Window、Document 和 Document.body 上的 wheel、mousewheel、touchstart 和 touchmove 事件的 passive 默认值更改为 true。如此,事件监听器便不能取消事件,也不会在用户滚动页面时阻止页面呈现。



批量绑定事件

li {
    list-style: none;
    width: 100px;
    height: 100px;
    background: lightcoral;
}
<ul id="box">
    <li></li>
    <li></li>
    <li></li>
</ul>
let li = document.getElementsByTagName('li');

for (let i = 0; i < li.length; i++) {
    li[i].addEventListener('click', () => {
        // 排他, 性能消耗较大
        for (let j = 0; j < li.length; j++) {
            li[j].style.background = 'lightcoral';
        }
        li[i].style.background = 'lightblue';
    });
}

改进:针对性排他

let li = document.getElementsByTagName('li');
let cur = -1; // 默认序号

for (let i = 0; i < li.length; i++) {
    li[i].addEventListener('click', () => {
        if (cur == i) {
            return; // 节流
        } else if (cur !== -1) {
            li[cur].style.background = 'lightcoral';
        }
        cur = i; // 更新序号
        li[cur].style.background = 'lightblue';
    });
}



事件类型

鼠标点击事件

  1. click - - - 点击左键 / 按回车键(click = mousedown + mouseup
  2. contextmenu - - - 点击右键(不常用:浏览器有默认的右键行为)
  3. dblclick - - - 左键双击(ondblclick = 2 * onclick
  4. mousedown - - - 按下鼠标任意键
  5. mouseup - - - 抬起鼠标任意键

鼠标移动事件

  1. mouseenter - - - 光标进入(不冒泡)
  2. mouseleave - - - 光标离开(不冒泡)
  3. mouseover - - - 光标进入
  4. mouseout - - - 光标离开
  5. mousemove - - - 光标移动(持续触发)

鼠标滚轮事件

wheel - - - 鼠标在元素上滚动时触发(IE + Chrome)

document.onwheel = (event) => {
    console.log(event.wheelDelta);
};

event.wheelDelta:滚动方向,120 → 上、-120 → 下;滚动得较快时,可能会出现 120 的整数倍


火狐使用 DOMMouseScroll,且只能通过 DOM2 的事件绑定方式使用

Div.addEventListener(
    'DOMMouseScroll',
    (event) => {
        Div.innerHTML = event.detail;
    },
    false
);

火狐使用 event.detail 作为滚动方向:-3 → 上;3 → 下


兼容函数封装:

if (document.onwheel === null) {
    // 给 Chrome 添加事件
    document.onwheel = mouseWheelHandler;
} else {
    // 给火狐添加事件
    document.addEventListener('DOMMouseScroll', mouseWheelHandler, false);
}

function mouseWheelHandler(event) {
    let direction;
    if (event.wheelDelta) {
        // Chrome 用的是 event.wheelDelta
        direction = event.wheelDelta / 120;
    } else if (event.detail) {
        // 火狐用的是 event.detail
        direction = -event.detail / 3;
    }
    console.log(direction);
}

键盘事件

keydown 键盘按下(所有键 = 输入键 + 功能键):

input.onkeydown = () => {
    console.log(111);
};

如果一直按着不动,会持续触发该事件


keyup 键盘抬起:

input.onkeyup = function () {
    console.log(111);
};

keypress 键盘按下(输入键,功能键不触发):

input.onkeypress = function () {
    console.log(111);
};

常见的 keyCode

  • shiftctrlalt 对应的 event.keyCode161718
  • enter 对应的 event.keyCode13
  • 左上右下 → 37383940

注意:keyCode 已被弃用,推荐使用 key 来判断用户按下的键值。


demo - 实现留言功能:

input.onkeyup = function (e) {
    if (this.value.trim()) {
        // 如果写了非空内容
        if (e.keyCode == 13 && e.ctrlKey) {
            // 同时按下 ctrl enter 键
            let oLi = document.createElement('li');
            oLi.innerHTML = this.value.trim();
            if (oUl.children[0]) {
                oUl.insertBefore(oLi, oUl.children[0]);
            } else {
                oUl.appendChild(oLi);
            }
        }
    }
};

可以通过查看 ctrlKeyaltKeyshiftKey 的值是否为 true,来判断是否有按下 ctrlaltshift


demo - 实现方向键移动:

let oBox = document.getElementById('box');

document.onkeydown = function (e) {
    // 给整个页面绑定事件
    switch (e.keyCode) {
        case 37:
            oBox.style.left = oBox.offsetLeft - 10 + 'px';
            break;
        case 38:
            oBox.style.top = oBox.offsetTop - 10 + 'px';
            break;
        case 39:
            oBox.style.left = oBox.offsetLeft + 10 + 'px';
            break;
        case 40:
            oBox.style.top = oBox.offsetTop + 10 + 'px';
            break;
    }
};

焦点事件

  1. focus - - - 得到焦点时触发(不冒泡)
  2. blur - - - 失去焦点时触发(不冒泡)
  3. focusinfocus 的冒泡版
  4. focusoutblur 的冒泡版

用户界面事件

  1. load:在 window 上,当页面加载完成后触发;在 img 上,当图片加载完成后触发;在 object 元素上,当对象加载完成后触发
  2. select:在文本框上,当用户选择了字符时触发
  3. resize:文档视图调整大小时触发;在 window 上,当窗口被缩放时触发
  4. scroll:文档视图或者一个元素在滚动时触发(区别于滚轮事件 wheel
  5. input:当一个 inputselecttextarea 元素的 value 被修改时,会触发该事件
  6. change:当一个 inputselecttextarea 元素的 value 被修改并失焦时,会触发该事件



事件处理模型

  • 单击某个子元素时,该元素称为事件源。
  • 单击事件不仅发生在事件源上,也发生在其所有的父级上。

事件流:捕获阶段 + 冒泡阶段;事件流对应着事件的触发顺序(先捕获、后冒泡)

  1. 捕获阶段:父级 → 子级
  2. 冒泡阶段:子级 → 父级(浏览器会冒泡到 window 为止)

在这里插入图片描述


练一练:

#box1 {
    width: 100px;
    height: 100px;
    background: lightgoldenrodyellow;
}

#box2 {
    width: 50px;
    height: 50px;
    background: lightgreen;
}
<div id="box1">
    <div id="box2"></div>
</div>
box1.addEventListener(
    'click',
    function () {
        console.log('父级捕获');
    },
    true
);

box2.onclick = function () {
    console.log('子级冒泡');
};

box2.addEventListener(
    'click',
    function () {
        console.log('子级捕获');
    },
    true
);

box1.onclick = function () {
    console.log('父级冒泡');
};



事件对象

JS 引擎会传入一个实参给事件处理函数,这个实参就叫做 “事件对象”。通常用形参 event / e 接收:

<div id="box">1</div>
<script>
    box.onclick = function (event) {
        console.log(event);
    };
</script>

输出:PointerEvent { isTrusted: true, pointerId: 1, width: 1, height: 1, pressure: 0, … }


事件对象 event 中,封装了这次事件的很多细节。比如点击事件发生时,鼠标的位置:

document.addEventListener('mousemove', (event) => {
    console.log('当前鼠标坐标为' + event.clientX + ',' + event.clientY);
});

常用方法

  1. stopPropagation - 阻止事件流。( propagation 有 “传播” 的意思 )

调用该方法后,事件就不会再沿着事件流继续往后触发:

box1.addEventListener(
    'click',
    function (event) {
        console.log('父级捕获');
        event.stopPropagation(); // 从这里开始阻止事件流
    },
    true
);

box2.addEventListener(
    'click',
    function () {
        console.log('子级捕获');
    },
    true
);

box2.onclick = function () {
    console.log('子级冒泡');
};

box1.onclick = function () {
    console.log('父级冒泡');
};

此时点击子级也不会触发子级的点击事件。


  1. preventDefault - 阻止默认事件。

在浏览器中,某些元素的某些事件,会有自己的默认行为。比如:

  1. a 元素的 click 事件,会跳转页面
  2. form 元素的 submit 事件,会提交表单,最终导致页面刷新

调用 preventDefault 即可阻止这些默认行为:

<a id="link" href="http://www.baidu.com">百度</a>
<script>
    link.onclick = function (event) {
        event.preventDefault(); // 超链接的默认点击事件是跳转
        console.log(111);
    };
</script>

此时点击超链接也不会跳转页面了。


常用属性

  1. event.type - 事件的类型。eg:click
  2. event.target - 事件源。即使该元素身上没有监听,也是返回它。
  3. event.currentTarget - 当前元素,即 this 的指向。
  4. event.button - 区分鼠标左、中、右键(0、1、2)。

常用信息

  1. offsetX / offsetY - - - 鼠标距离 [事件对象] 的左上角

  2. layerX / layerY - - - 鼠标距离 [最近的定位元素] 的左上角(会一直找到 body 标签;自身有定位属性的话就是相对于自身)

  3. clientX / clientY - - - 鼠标距离 [显示区域] 的左上角(与页面是否有滚动无关)

  4. pageX / pageY - - - 鼠标距离 [页面] 的左上角(会加上滚动的距离)

  5. screenX / screenY - - - 鼠标距离 [计算机屏幕] 的左上角(在多屏显示的环境下,范围将增加到屏幕的组合宽高)

  6. x / y - - - clientX / clientY 的别名

  • 页面没有滚动时,pageXY 和 clientXY 的值相同



事件委托

通过事件对象 event 可以得到事件源 target。所以我们可以将子级的事件委托给父级处理。

<ul id="box">
    <li>li1</li>
    <li>li2</li>
    <li>li3</li>
</ul>
<script>
    box.onclick = (event) => {
        console.log(event.target.innerHTML);
    };
</script>

优点:① 不需要循环绑定每个子元素,可以优化性能;② 添加新元素时,不需要给新元素绑定事件。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值