dom 事件机制

事件流

这一概念源自于对事件触发对象的思考。例如常见的点击事件,鼠标移动事件。这些事件发生之时,往往不只是点击或者移动到某一特定元素上。

比如点击某一个按钮,而它是由上一层的父标签,或许在上一层还有父标签甚至是整个页面。因此点击一个元素可以看成是同时点击了父标签或者整个页面。那么此时事件应该怎么响应到指定标签呢?

事件冒泡

即事件从指定元素开始传播到最外层的元素,并且该事件不仅会在指定元素上发生,还会在传播过过程中的每一个元素上发生。

1
2
3
4
5
6
7
<html>
<body>
<div>
<button>click</button>
</div>
</body>
</html>
  • 事件冒泡: button -> div -> body -> html -> document

如上,再点击click之后,事件从 button 开始传播至 html ,再到 documet。这一个过程也称为事件冒泡

事件捕获

与事件冒泡刚好相反,事件从最外层的 documet 开始一直往里面,直到点击的元素才停止

1
2
3
4
5
6
7
<html>
<body>
<div>
<button>click</button>
</div>
</body>
</html>
  • 事件冒泡: documet -> html -> body -> div -> button

如上,再点击click之后,事件从 documet 开始传播至 button。这一个过程也称为事件捕获

DOM 事件流

在上述两种事件确定的方式下,规定了事件处理的三个阶段。事件捕获阶段、处于目标阶段、事件冒泡阶段。同时 DOM明确规定

  • 事件捕获阶段不会处理事件

  • 处于目标阶段属于冒泡阶段的一部分,并且会触发事件。

  • 然而实际上,几乎所有主流浏览器都支持在事件捕获阶段触发事件,它们并没有遵守规定

事件处理程序

主要是指 DOM 如何处理各种 HTML 上的程序。

对于所有浏览器来说,有两种标准用来操作事件的添加与删除,一种是 DOM2 事件处理程序,一种是 IE 事件处理程序。

DOM2 级事件处理

直接调用该 dom 对象的事件属性,并将相应的执行函数赋予它

addEventListener() 和 dom.on(事件) = 函数

利用 dom 对象的事件属性直接赋予一个执行函数;利用 addEventListener 添加,并且该方法可以添加多个

addEventListener() 有三个参数,参数 1 为事件、参数 2 为执行函数。参数 3 为一个布尔值,false 代表在冒泡阶段执行,true 表示在捕获阶段执行

1
2
3
4
5
6
7
8
9
10
var doc = document.querySelector('div');
doc.onclick = function() {
alert(1);
}
doc.addEventListener('click', function() {
alert(2);
}, false)
doc.addEventListener('click', function() {
alert(3);
}, false)

removeEventListener() 和 dom.on(事件) = null

利用直接设置事件属性为 null 来移出执行函数。利用 removeEventListener() 来移出,但前提是必须给定函数名

removeEventListener() 有三个参数,参数 1 为事件、参数 2 为执行函数。参数 3 为一个布尔值,false 代表在冒泡阶段执行,true 表示在捕获阶段执行

如下使用匿名函数定义的执行函数无法删除

1
2
3
4
5
6
7
8
9
10
11
12
13
var doc = document.querySelector('div');
doc.addEventListener('click', function() {
alert(2);
}, false)//通过匿名函数定义的
doc.removeEventListener('click',function() {
alert(2);
}, false);//无效
doc.onclick = null;//有效
function Click() {
alert(2);
}
doc.addEventListener('click', Click, false)//
doc.removeEventListener('click', Click, false);//有效

IE 级

attachEvent()

和 DOM 2 级一样用来添加事件,只有两个参数 “事件” 和 “处理函数”,不能设置冒泡或者捕获。因为 IE8 之前那只支持冒泡,所以就只能冒泡

同样可以添加多个执行函数

detachEvent()

和 DOM2 级一样,要删除必须给定函数名作为参数。无法删除通过匿名定义添加的执行函数。

封装跨浏览器的事件处理函数

因为 IE 独树一帜,又因为 IE 属于 windows 用户标配;所以兼容需要考虑。否则代码在 IE 上可能出问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const EventHandle = {
addEvent: (element, type, fn) => {
if(element.addEventListener) {
element.addEventListener(type, fn, false);
} else if(element.attachEvent) {
element.attachEvent(type, fn);
} else {
element[`on${type}`] = fn;
}
},
removeEvent: (element, type, fn) => {
if(element.removeEventListener) {
element.removeEventListener(type, fn, false);
} else if(element.detachEvent) {
element.detachEvent(type, fn);
} else {
element[`on${type}`] = null;
}
},
}

let doc = document.querySelector('div');
function T() {
alert(13);
}
EventHandle.addEvent(doc, 'click', T);
EventHandle.removeEvent(doc, 'click', T);

事件对象

事件对象作为事件发生给予 js 获得相关信息的机会非常重要。程序要根据这些信息作出相应的响应。

DOM 事件对象

必定会传一个 event 对象给执行函数。可以直接作为参数使用。但有两种情况需要注意

  • 通过直接在 html 元素上添加的事件,必须写明参数为 event,响应执行函数也要写明该参数
  • 通过 addEventListener() 添加的事件,只需要在执行函数上写明参数就行。
1
2
3
4
5
6
<div onclick="Test(event)"></div>
<script>
function Test(event) {
console.log(event.type);
}
</script>
1
2
3
4
let doc = document.querySelector('header');
doc.addEventListener('click', function(event) {
console.log(event.target.tagName,event.type);
}, false);

常用属性

  • bubbles Boolean 表明事件能否冒泡
  • cancelable Boolean 是否能取消事件的默认行为
  • currentTarget Element 当前事件处理程序正在处理的元素
  • defaultPrevented Boolean 为 true 表示调用了 preventDefault()
  • eventPhase Integer 1 为捕获阶段 - 2 - 为处于目标阶段 - 3 为冒泡阶段
  • preventDefault() 取消事件的默认行为,前提是 cancleable 为 true
  • target 事件的目标,就是触发事件的对象
  • type 事件类型

target 与 currentTarget

这里的 currentTarget 是指发生事件时,该事件所绑定的那个元素

而 target 从始至终就都是你点击或者移动或者其他触发事件行为的元素

1
2
3
4
5
6
7
8
9
10
11
12
<div onclick="father(event)">
<button>1</button>
<button onclick="son(event)">2</button>
</div>
<script>
function father(event) {
console.log(event.target, event.currentTarget);
}
function son(event) {
console.log(event.target, event.currentTarget);
}
</script>

看上述这个例子。

  • 情况一:点击 button 2;按照事件冒泡那么两个执行函数都会触发

    • target: 两个函数的输出值都为 button 2;因为点击的是该元素
    • currentTarge: son() 输出的为 button 1;father() 输出为 div
  • 情况二:点击 button 1;只会触发 father()

    • target: 输出为 button 1;因为就是点击在 button 1 上
    • currentTarget:输出为 div;因为该执行函数就绑定在该元素上
  • 情况三:点击 div;只触发 father()

  • target 和 currentTarget 都为 div

另外,执行函数中的 this 值指向 currentTarget;但是有个前提,this值要等于 currentTarget,那么必须是在target上或者事件是通过 sddEventListener 添加的。否则通过html元素直接绑定的方式this指向了 window 对象

eventPhase 和 stopPropagation

eventPhase 可以知道事件执行时处于哪个阶段

stopPropagation 可以阻值事件继续冒泡传播。我们知道一般事件是在处于目标阶段到冒泡阶段执行的。倘若不阻止冒泡,那么点击一个小按钮,一直回溯到 document。那么整个页面许多地方的点击事件都会触发,很显然我们不想这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
<div onclick="father(event)">
<button>1</button>
<button onclick="son(event)">2</button>
</div>
<script>
function father(event) {
console.log(event.target, event.currentTarget);
}
function son(event) {
console.log(event.target, event.currentTarget, event.eventPhase);//button button 2
event.stopPropagation();//阻止继续冒泡。这样就不会触发 father 了
}
</script>

当然该方法同样可以阻止捕获,不过前提是绑定事件时指定他在捕获阶段触发。这样一来就不会继续捕获下去了

IE 事件对象

为什么不能统一呢,非要学两套

IE 事件对象与 DOM 级有一定差异

常用属性

  • cancelable 默认值为 false,true 为取消冒泡。与 DOM 中 stopPropagation 相似
  • returnValue 默认为 true,false 为取消事件默认行为,与 DOM 中 preventDefalut() 相似
  • srcElement 事件目标,与 DOM target 相似
  • type 事件类型

event 对象的获取

IE 中的 event 对象时作为 window 对象的一部分存在,可以通过 window.event 来获取

  • 通过文档对象赋值的方法,必须要指定 window.event ,直接使用 event 会报错 undefined
1
2
3
4
5
6
var doc = document.querySelector('#id');
doc.onclick = function (event) {
console.log(event.type);//event undefined
var event = window.event;
console.log(event.type);//click
}
  • 通过 attachEvent() 添加的可以像 DOM 那样作为参数直接使用
1
2
3
doc.attachEvent('click', function(event) {
console.log(event.type);//click
});

总结

执行函数中关于事件元素的信息都可以通过 event 获取,虽然 this 值有时也会等于 event 的部分属性。但是建议用 event,因为 this 的指向取决于外部执行环境,不能保证得到想要的值。

另外,IE 要没了。取而代之的是微软新浏览器 Edge ,这个浏览器好像已经统一了 DOM级规定的事件处理。原来 IE 的那些特有事件处理已经没有了

事件Type

常见的事件类型

UI事件

界面发生的事件

load 事件

当页面完全加载,包括所有图像、js 文件、产生式文件等外部资源。之后就会触发该事件。添加事件的方法?建议使用之前写的跨浏览器事件处理方法。当然也可以通过获取 dom 对象,并对其属性赋值,也可以直接在 html 元素上绑定。但是这两个方法下的 event 对象的使用有区别,特别是在 IE 浏览器上

  • 在 window对象下触发整个页面的加载;
1
2
3
window.addEventListener('load', function(event) {
console.log(event.target, '加载完毕');
})
  • 用来加载图片

绑定事件后,设置 img 的 src 即刻加载。可以用来做图片的预加载。

1
2
3
4
5
6
7
8
let image = new Image();
image.addEventListener('load', function(event) {
console.log(event.target, '图像加载完毕');
})
window.addEventListener('load', function(event) {
console.log(event.target, '页面加载完毕,开始加载图像');
image.src = `https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599126757461&di=2d244dfca72c7e4c96e533c37d520b43&imgtype=0&src=http%3A%2F%2Fattach.bbs.miui.com%2Fforum%2F201307%2F25%2F201849gw2fgom557bg25jz.jpg`;
})
  • 用来加载 js 外部文件
1
2
3
4
5
6
7
8
let js = document.createElement('script');
js.addEventListener('load', function(event) {
console.log(event.target, 'js加载完毕');
})
window.addEventListener('load', function(event) {
console.log(event.target, '页面加载完毕,开始加载js');
js.src = `./xxx.js`;
})

unload 事件

与 load 事件相反,一般页面切换后触发,可用来强制的引用清除,防止内存泄漏

1
2
3
window.addEventListener('unload', function(event) {
console.log(event.target, '跳转了');
})

resize 事件

当页面大小发生改变时触发,可以用此来获取一些窗口属性,用来做响应式开发。但是大小改变检测很灵敏,所以需要做防抖

1
2
3
4
window.addEventListener('resize', function(event) {
console.log('大小改变');
console.log(window.innerWidth, window.innerHeight);
})

scroll 事件

scrollLeft 和 scrollTop

这是存在于 document 上的两个属性,分别代表滚动条已经滚动的高度和宽度。他们与 clientWidth 、clienHeight一样都是页面视口的属性,并非整个浏览器创口属性。

该事件是在 window 对象上发生的,与 scrollTop,scrollLeft 有关。监听该事件可以用来做导航栏的变化,同样要做防抖,否则容易卡顿

1
2
3
4
window.addEventListener('scroll', function(event) {
console.log('滚动');
console.log(document.documentElement.scrollTop, document.documentElement.scrollLeft);
})

焦点事件

焦点一般只鼠标的焦点,虽然可以检测鼠标事件来监控,但是焦点可以通过键盘移动所以有专门的事件监控。焦点事件标准不一,但是 DOM3 统一了一个标准并且规定了它的发生顺序

当一个元素移动到另一个元素会依次触发以下事件;一般 blur 与 focus 常见

  • focusout 在失去焦点元素上触发,会冒泡
  • focusin 在获取焦点元素上触发,会冒泡
  • blur 在失去元素上触发,不会冒泡
  • DOMFocusOut 在失去焦点元素上触发,会冒泡; Opera 专有
  • focus 在获取焦点元素上触发,不会冒泡
  • DOMFocusIn 在获取焦点元素上触发,会冒泡; Opera 专有

鼠标与滚轮事件

鼠标点击与移动

主要是点击、双击、光标移入、移出、暗下、放开的操作;

  • mouseenter 首次移入元素内部触发,不冒泡
  • mouseleave ,移出元素触发,不冒泡
  • mousemove 在元素内重复移动触发
  • mouseout 移入另一个元素触发
  • mousedown 按下鼠标触发
  • mouseup 释放鼠标按键触发
  • click 点击,只有按下事件和放开事件发生后才会触发,只是按下不会触发
  • dblclick 双击,当且仅当连续两次 click 时触发

触发顺序:

  • mousedown
  • mouseup
  • click //一次点击
  • mousedown
  • mouseup
  • click //二次点击
  • dblclick //触发双击

一般可以用于轮播图或者自动播放,当鼠标移入将其停止,移出又自动播放

1
2
3
var doc = document.querySelector('input');
doc.addEventListener('mouseenter', function() {console.log('暂停')} )
doc.addEventListener('mouseout', function() {console.log('开始')} )

滚轮

  • mousewheel

可用于获取该事件发送时鼠标属性,在任何元素上通过鼠标滚动即可触发。可以检测页面滚动是否来自鼠标。该事件冒泡。

触摸屏

上述事件在移动端上又有所不同

  • 并不支持 dblclick ,双击只会放大
  • 轻击不可单击或者没有绑定 click 的元素什么事件也不会发生
  • 在可点击或者绑定 click 的前提下点击会触发 mousemove 。若该事件改变了内容将不会在发生其他事件,否则可以发生 down、up、click 事件
  • mousemove 也会触发 mouseenter 和 mouseout
  • 手指滚动页面时会触发 mousewheel 和 scroll 事件

键盘和文本事件

键盘

  • keydown 敲击任意键时触发,若按住不放则不断触发
  • keypress 敲击字符健时触发,若按住不放则不断触发
  • keyup 释放键盘触发

按下字符键时依次触发 keydown -> keypress -> keyup
按下非字符键时依次触发 keydown -> keyup

另外发生 keydown 和 keyup 时,也可以通过 event 对象获取相应的键值(ASCII码值)。keyCode

1
2
3
4
let doc = document.querySelector('input');
doc.addEventListener('keyup',function(event) {
console.log(event.keyCode);
})

textInput 事件

触发条件

  • 必须在可编辑区编辑
  • 输入实际字符的键,不会包括删除、退格键等等

可以通过 event.data 获得键盘实际输入值而非 ASCII 码值

1
2
3
4
let doc = document.querySelector('input');
doc.addEventListener('textInput',function(event) {
console.log(event.data);
})

设备事件

设备事件不是值页面内的事件,而是移动设备本身事件,比如翻转、是否走动。目前的草案中有四种类型事件

  • orientationchange ,苹果给 safari 添加的事件用来检测设备从横向观察模式到纵向观察模式。

  • 它的值存在于 window.orientation 中。0 为纵向、90 为向左旋转、-90 为向右旋转

  • MozOrientation ,firefox 未检测设备而引入,依靠 event 的 x,y,z 来确定方向。该事件可能被替代

  • deviceorientation

  • devicemotion

触摸与手势事件

这一类事件是移动设备的事件核心

触摸事件

  • touchstart 手指触摸屏幕触发
  • touchmove 手指在屏幕上连续滑动触发。可以调用 preventDefault() 来阻值滑动
  • touchend 手指离开屏幕
  • touchcancel 系统停止跟踪触发
文章作者: 努力向前
文章链接: https://greatiga.cn/2020/09/06/浏览器/domEvent/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 努力向前