javascript进阶知识28 - 事件(超详细)

181 阅读11分钟

事件流(事件传播)

事件流:描述的是从页面中接收事件的顺序。

三个阶段:

  • 事件事件捕获
  • 处于目标阶段
  • 事件冒泡阶段

历史:

如果单击了某个按钮,他们认为单击事件不仅仅发生在按钮上,甚至单击整个页面。

在这里插入图片描述

  • 就像上面那个图片一样,我们点击在红色盒子身上的同时,也是点击在了粉色盒子上
  • 这个是既定事实,那么两个盒子的点击事件都会触发
  • 这个就叫做 事件的传播

IE事件流:事件冒泡

Netscape(网景):事件捕获

image.png

事件冒泡

事件开始时由最具体的元素接收,然后逐级向上传播到较为不具体的节点(文档)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width:200px;
            height:200px;
            background-color:red;
        }
    </style>
</head>
<body>
    <div id="box">
        
    </div>
    <script>
        var box = document.getElementById('box');
        box.onclick = function() {
            box.innerHTML += 'div\n';
        }
        document.body.onclick = function() {
            box.innerHTML +='body\n';
        }
        document.documentElement.onclick = function() {
            box.innerHTML += 'html\n';
        }
        document.onclick = function() {
            box.innerHTML += 'document\n';
        }
        window.onclick = function() {
            box.innerHTML += 'window\n';
        }
    </script>
</body>
</html>

image.png

当我们点击红盒子后:

image.png

这就说明,事件冒泡就是从目标元素逐层往上传递,直到window。

事件捕获

由不太具体的节点更早的接收事件,更具体的节点应该最后接收到事件。

直白说就是最先是window接收,最后是目标元素接收。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width:200px;
            height:200px;
            background-color:red;
        }
    </style>
</head>
<body>
    <div id="box">
        
    </div>
    <script>
        // 默认为false,冒泡阶段   true为捕获阶段
        var box = document.getElementById('box');
        box.addEventListener('click',function(){
             box.innerHTML += 'div\n';
        },true)
        
        document.body.addEventListener('click',function(){
            box.innerHTML +='body\n';
        }.true)
            
        document.documentElement.addEventListener('click',function(){
            box.innerHTML +='html\n';
        }.true)
        
        document.addEventListener('click',function(){
            box.innerHTML +='document\n';
        }.true)
        
        window.addEventListener('click',function(){
            box.innerHTML +='window\n';
        }.true)
    </script>
</body>
</html>

默认:(原始)

image.png

点击红盒子后:

image.png

结论:事件捕获是从window开始逐层往下传播的,直到目标元素。

传播顺序

先捕获,再目标,再冒泡。也就是说同一元素实际上会被触发两次。但是浏览器默认使用的都是“冒泡”阶段,也就是说,我们绑定事件后一般只会触发一次。

事件处理程序

事件处理程序

  • HTML
  • DOM0级
  • DOM2级
  • IE事件处理程序

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width:200px;
            height:200px;
            background-color:red;
        }
    </style>
</head>
<body>
    <!-- <div id="box" onclick="this.innerHTML += '1'"></div>  -->
    <div id="box" onclick ='test();'></div>
    <script>
        function test() {
            duocument.getElementById('box').innerHTML += '1';
        }
    </script>
</body>
</html>

缺点:html和js无分离,后期不易维护

DOM0

DOM0就是使用dom.onclick = function() {}

优势:简单 具有跨浏览器的优势

特点:只有冒泡阶段,没有捕获阶段

缺点:不能给同一个元素来绑定相同的事件处理程序,如果绑定了,会有覆盖现象

删除事件处理程序:dom.onclick = null;

DOM2

DOM2

  • 增加事件监听器dom.addEventListener('click',function(事件名,处理程序的函数,布尔值){})

    • 布尔值默认是false,处于冒泡
    • 如果为true,则处于捕获阶段
  • 移除事件监听器dom.removeEventListener()

如果移除事件,就必须在增加事件监听器时,将处理程序的函数写成具名的

let box = document.querySelector('#box');
const handler = function() {
    this.innerHTML += '1'
}
box.addEventListener('click',handler);
box.removeEventListener('click',handler);
​
// 以下是错误的,这样并不能移除掉
box.addEventListener('click',function() {
    this.innerHTML += '1'
});
box.removeEventListener('click',function() {
    this.innerHTML += '1'
});

IE

仅仅有冒泡流:绑定attachEvent() 移除detachEvent()

let box = document.querySelector('#box');
box.attachEvent('onclick',function(){
    this.innerHTML += '1'
})

算了不写了,这个只能在IE浏览器中使用,有锤子用,IE都挂了...

如何获取事件对象

window.onload = function() {
    var box = ducument.querySelector('#box');
    box.onclick = function(e) {
        console.log(e);
    }
}

event对象是事件处理程序的第一个参数 ie8浏览器不支持

2/直接使用event变量

box.onclick = function() {
    console.log(event)
}

事件目标对象

currentTarget target和srcElement

  • currentTarget 属性返回事件当前所在的节点,正在执行的监听函数所绑定的节点 (就是事件绑定的节点)
  • target返回的是事件的实际触发目标对象
  • this对象和e.currentTarget属性是一致的,指向的都是绑定这个事件的节点
  • srcElement是IE里的,IE8以下不兼容target,就需要使用secElement来获取,但是现在不重要了...
<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
​
        #box {
            width: 200px;
            height: 200px;
            background-color: red;
        }
​
        li {
            list-style: none;
            width: 100%;
            height: 20%;
            background-color: black;
            color: #fff;
            margin-bottom: 10px;
        }
        #box li:nth-child(odd) {
            background-color: blue;
        }
    </style>
</head><body>
    <ul id="box">
        <li class="item">1</li>
        <li class="item">2</li>
    </ul>
    <script>
        let box = document.querySelector('#box');
        box.onclick = function (e) {
            e = e || window.event;  // window.event的为了兼容IE
            console.log('e.target:', e.target)
            console.log('e.currentTarget:', e.currentTarget)
​
        }
    </script>
</body></html>

image.png

当我们点击1时:

image.png

当我们点击2时:

image.png

当我们点击红色区域时:

image.png

通过这个例子我们就应该能很好的理解e.targete.currentTarget的区别了吧!我们绑定事件绑定的就是在ul上面,所以不管我们点击ul里面的哪里,只要这个事件触发了,e.currentTarget指向的就是ul;而e.target就是指的是这个事件真正的触发者,因为我们点击的是<li class="item"></li>这个元素。其实这就是事件委托或者事件代理

事件委托(事件代理)

  • 就是把我要做的事情委托给别人来做
  • 因为我们的冒泡机制,点击子元素的时候,也会同步触发父元素的相同事件
  • 所以我们就可以把子元素的事件委托给父元素来做

上面的例子其实已经很好的解释了什么是事件委托以及事件代理。

事件委托的好处:

  • 点击子元素的时候,不管子元素有没有点击事件,只要父元素有点击事件,那么就可以触发父元素的点击事件。

  • 为什么要用事件委托:

    • 我页面上本身没有<li>, 我通过代码添加了一些<li>,添加进来的<li>是没有点击事件的
    • 我每次动态的操作完<li>以后都要从新给<li>绑定一次点击事件,比较麻烦
    • 这个时候只要委托给<ul>就可以了
    • 因为新加进来的<li>也是<ul>的子元素,点击的时候也可以触发<ul>的点击事件
  • 事件委托的书写:

    • 元素的事件只能委托给 结构父级 或者 再结构父级相同事件
      • <li>的点击事件,就不能委托给<ul>的鼠标 移入事件
      • <li>的点击事件,只能委托给<ul>或者 在高父级的点击事件上

阻止事件

阻止默认行为

我们知道我们的<a></a>标签默认有跳转行为,我们使用鼠标右键的时候,会默认出来一个弹窗等等,这些不需要我们注册就能实现的事情,我们叫做 默认事件

我们现在假设需要点击a标签的时候,弹出一个窗口,就需要将a标签的默认行为阻止掉。

  • e.preventDefault() : 非 IE 使用
  • e.returnValue = false :IE 使用
<a href="https://www.baidu.com">点击我试试</a>
<script>
      var oA = document.querySelector('a')
  
      a.addEventListener('click', function (e) {
        e = e || window.event
        console.log(this.href)
        e.preventDefault ? e.preventDefault() : e.returnValue = false
      })
</script>
  • 这样写完以后,你点击 a 标签的时候,就不会跳转链接了
  • 而是会在控制台打印出 a 标签的 href 属性的值

阻止事件传播

  • 如果希望事件到某个节点为止,不再传播,可以使用事件对象的stopPropagation方法
// 事件传播到 p 元素后,就不再向下传播了
p.addEventListener('click', function (event) {
  event.stopPropagation();
}, true);
​
// 事件冒泡到 p 元素后,就不再向上冒泡了
p.addEventListener('click', function (event) {
  event.stopPropagation();
}, false);

例子:

<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            width: 200px;
            height: 200px;
            background-color: red;
        }
    </style>
</head><body>
    <div id="box">
​
    </div>
    <script>
        // 默认为false,冒泡阶段   true为捕获阶段
        var box = document.getElementById('box');
        box.addEventListener('click', function (event) {
            box.innerHTML += 'div\n';
        }, true)
​
        document.body.addEventListener('click', function (event) {
            event.stopPropagation();
            box.innerHTML += 'body\n';
        }, true)
​
        document.documentElement.addEventListener('click', function () {
            box.innerHTML += 'html\n';
        }, true)
    </script>
</body></html>

当我们给body添加stopPropagation方法后,那么body就停止了事件传播行为,所以当我们点击div时,由于是捕获行为,就从window往下传播到body就停止传播了,而这里只有html、body、div绑定了事件,所有红盒子里输出了html body.

image.png

  • stopPropagation方法只会阻止事件的传播,不会阻止该事件触发<p>节点的其他click事件的监听函数
p.addEventListener('click', function (event) {
  event.stopPropagation();
  console.log(1);   // 能正常打印
});
// 另一个click事件的监听函数也会触发
p.addEventListener('click', function(event) {
  // 会触发
  console.log(2);
});

阻止事件触发

  • 如果想要彻底取消该事件,不再触发后面所有click的监听函数,可以使用stopImmediatePropagation方法
  • stopImmediatePropagation方法可以彻底取消这个事件,使得后面绑定的所有click监听函数都不再触发
p.addEventListener('click', function (event) {
  event.stopImmediatePropagation();
  console.log(1);   // 能正常打印
});
// 另一个click事件的监听函数不会触发,因为在上一个监听函数中阻止了
p.addEventListener('click', function(event) {
  // 不会被触发
  console.log(2);
});

但是该方法不会阻止事件的传播,只会阻止事件的触发。

常见事件

  • 大致分为几类,浏览器事件、鼠标事件、键盘事件、表单事件、触摸事件

浏览器事件

用的最多的就是window.onload了吧...就是等所有资源都加载完毕后再执行。包括图片资源、css资源等。

说到这里,还有一个document.ready(Jquery提供的事件),它们两者之间有什么区别吗??

  • 这就要说一说浏览器的渲染机制了,浏览器在加载HTML时,会先将HTML解析成为DOM树,在和css解析成的CSSOM一起行为渲染树,再对渲染树进行layout布局生成布局树Layout tree,最后再通过显卡将Layout Tree进行绘制,显示在屏幕上。

  • 而当HTML成功解析为DOM树时,浏览器自带了一个事件DOMContentLoadeddocument.ready触发的时候就是调用的DOMContentLoaded,所以document.ready触发的时候就是DOM树生成的时候。

  • window.onload就是等所有资源加载完成后才执行。

  • 所以window.onload执行事件比document.ready晚。

    在这里插入图片描述

我在网上找的一张图。document.ready是时间点1,window.onload是时间点2。

鼠标事件

鼠标点击事件:

事件类型说明
click单击鼠标左键时发生,如果右键也按下则不会发生。当用户的焦点在按钮上并按了 Enter 键时,同样会触发这个事件
dblclick双击鼠标左键时发生,如果右键也按下则不会发生
mousedown单击任意一个鼠标按钮时发生
mouseup松开任意一个鼠标按钮时发生

鼠标移动事件:

事件类型说明
mousemove当鼠标在一个节点内部移动时触发
mouseout鼠标离开一个节点时触发,离开父节点也会触发这个事件
mouseover鼠标进入一个节点时触发,进入子节点会再一次触发这个事件
mouseleave鼠标离开一个节点时触发,离开父节点不会触发这个事件
mouseenter鼠标进入一个节点时触发,进入子节点不会触发这个事件

注意: mouseleavemouseenter是不会有事件传播的。

键盘事件

事件类型描述
keydown键盘按下事件
keypress按下有值的键时触发,即按下 Ctrl、Alt、Shift、Meta 这样无值的键,这个事件不会触发
keyup键盘抬起事件

鼠标点击事件触发顺序是:

  1. keydown 首先触发 2. keypress 接着触发 3. keyup 最后触发

表单事件

事件类型描述
input每次输入数据都会触发事件
selectelect事件当在< input>、< textarea>里面选中文本时触发
change该事件在表单元素的内容改变失去焦点时触发
invalid用户提交表单时,如果表单元素的值不满足校验条件,就会触发invalid事件
resetreset事件当表单重置(所有表单成员变回默认值)时触发
submit提交表单时触发
focus获取焦点时触发
blur失去焦点时触发

input

  • 对于复选框或单选框,用户改变选项时,也会触发这个事件;
  • input事件的一个特点,就是会连续触发,比如用户每按下一次按键,就会触发一次input事件;
/* HTML 代码如下
<select id="mySelect">
  <option value="1">1</option>
  <option value="2">2</option>
  <option value="3">3</option>
</select>
*/
function inputHandler(e) {
  console.log(e.target.value)
}
var mySelect = document.querySelector('#mySelect');
mySelect.addEventListener('input', inputHandler);

上面代码中,改变下拉框选项时,会触发input事件,从而执行回调函数inputHandler。

selected

// HTML 代码如下
// <input id="test" type="text" value="Select me!" />var elem = document.getElementById('test');
elem.addEventListener('select', function (e) {
  console.log(e.type); // "select"
}, false);

选中的文本可以通过event.target元素的selectionDirection、selectionEnd、selectionStart和value属性拿到。

change

  • 激活单选框(radio)或复选框(checkbox)时触发。
  • 用户提交时触发。比如,从下列列表(select)完成选择,在日期或文件输入框完成选择。
  • 当文本框或元素的值发生改变,并且丧失焦点时触发。

与input的区别:

不同之处在于input事件在元素的值发生变化后立即发生,而change在元素失去焦点时发生,而内容此时可能已经变化多次。也就是说,如果有连续变化,input事件会触发多次,而change事件只在失去焦点时触发一次。(可以看作两者之间的区别只有是input时才有)

// HTML 代码如下
// <select size="1" οnchange="changeEventHandler(event);">
//   <option>chocolate</option>
//   <option>strawberry</option>
//   <option>vanilla</option>
// </select>function changeEventHandler(event) {
  console.log(event.target.value);
}

如果比较一下上面input事件的例子,你会发现对于元素来说,input和change事件基本是等价的。

invalid

<form>
  <input type="text" required oninvalid="console.log('invalid input')" />
  <button type="submit">提交</button>
</form>

上面代码中,输入框是必填的。如果不填,用户点击按钮提交时,就会触发输入框的invalid事件,导致提交被取消。

如果使用过Element UI或者Element Plus的表单,就会对这个很熟悉啦~

blur和focus

blur:某元素失去活动焦点时产生该事件。例如鼠标在文本框中点击后又在文本框外点击时就会产生。

focus:网页上的元素获得焦点时产生该事件。

blur和focus不能事件冒泡

触摸事件

主要用于移动端,这个就不写啦,主要是没咋用过,等后面用了再说~

Event获取鼠标位置

offsetX与offsetY

  • 是相对于你点击的元素的边框内侧开始计算。
<!DOCTYPE html>
<html lang="en"><head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #box {
            margin: 100px;
            width: 200px;
            height: 200px;
            background-color: red;
            border:5px solid #000;
        }
        .sm {
            width: 50px;
            height: 50px;
            background-color: #000;
            margin: 30px;
            box-sizing: border-box;
            border: 5px solid green;
        }
    </style>
</head><body>
    <div id="box">
        <div class="sm"></div>
    </div>
    <script>
        let box = document.getElementById('box');
        box.addEventListener('click',(e)=>{
            console.log('X:',e.offsetX,'Y:',e.offsetY);
        })
    </script>
</body></html>

image.png

我们发现它的X与Y是基于事件真正触发的元素的border内侧开始计算的(也就是e.target),而不是基于绑定事件的元素的内边框。

clientX与clientY

  • 是相当于浏览器窗口来计算的,不管你页面滚动到什么情况,都是根据可视窗口来计算坐标

就是从电脑屏幕的左上角开始计算。

pageX与pageY

  • 是相对于整个页面的坐标点,不管有没有滚动,都是相对于页面拿到的坐标点

就是从页面的左上角开始计算,如果滚动条没有滚动,那么等于clientX与clientY。(应该可以理解吧?这个上一节js写过的offset、client、scroll和这个差不多。)

马上js就写完了,哈哈...😉😁