事件流
javascript注册dom事件的手段很多:
① 直接写在dom标签上,onclick的做法
② 在js中这样写:el.onclick = function
上述做法事实上是不好的,因为他们无法多次定义,也无法注销,更加不用说使用事件委托机制了
上述两种做法的最终仍然是调用addEventListener方式进行注册冒泡级别的事件,于是这里又扯到了javascript事件的几个阶段
在DOM2级事件定义中规定事件包括三个阶段,这个是现有DOM事件的基础,这个一旦改变,前端DOM事件便需要重组
三个阶段是事件事件捕获阶段、处于目标阶段、冒泡阶段
事件捕获由最先接收到事件的元素往最里面传
事件冒泡由最具体元素往上传至document
一般而言是先捕获后冒泡,但是处于阶段的事件执行只与注册顺序有关,比如:
每次点击一个DOM时候我们会先判断是否处于事件阶段,若是到了处于阶段的话便不存在捕获阶段了
直接按照这个DOM的事件注册顺序执行,然后直接进入冒泡阶段逻辑,其判断的依旧是e.target与e.currentTarget是否相等
这个涉及到一个浏览器内建事件对象,我们注册事件方式多种多样
除了addEventListener可以注册捕获阶段事件外,其余方式皆是最后调用addEventListener接口注册冒泡级别事件
注册的事件队列会根据DOM树所处位置进行排列,最先的是body,到最具体的元素
每次我们点击页面一个区域便会先做判断,是否处于当前阶段,比如:
我当前就是点击的是一个div,如果e.target==e.currentTarget,这个时候便会按注册顺序执行其事件,不会理会事件是捕获还是冒泡,而跳过捕获流程,结束后会执行冒泡级别的事件,若是body上有冒泡点击事件(没有捕获)也会触发,以上便是DOM事件相关知识点
事件冒泡是事件委托实现的基石,我们在页面的每次点击最终都会冒泡到其父元素,所以我们在document处可以捕捉到所有的事件,事件委托实现的核心知识点是解决以下问题:
① 我们事件是绑定到document上面,那么我怎么知道我现在是点击的什么元素呢
② 就算我能根据e.target获取当前点击元素,但是我怎么知道是哪个元素具有事件呢
③ 就算我能根据selector确定当前点击的哪个元素需要执行事件,但是我怎么找得到是哪个事件呢
如果能解决以上问题的话,我们后面的流程就比较简单了
确定当前元素使用 e.target即可,所以我们问题以解决,其次便根据该节点搜索其父节点即可,发现父节点与传入的选择器有关便执行事件回调即可
这里还需要重新e.currentTarget,不重写全部会绑定至document,简单实现:
var arr = [];
var slice = arr.slice;
var extend = function (src, obj) {
var o = {};
for (var k in src) {
o[k] = src[k];
}
for (var k in obj) {
o[k] = obj[k];
}
return o;
};
function delegate(selector, type, fn) {
var callback = fn;
var handler = function (e) {
//选择器找到的元素
var selectorEl = document.querySelector(selector);
//当前点击元素
var el = e.target;
//确定选择器找到的元素是否包含当前点击元素,如果包含就应该触发事件
/*************
注意,此处只是简单实现,实际应用会有许多判断
*************/
if (selectorEl.contains(el)) {
var evt = extend(e, { currentTarget: selectorEl });
evt = [evt].concat(slice.call(arguments, 1));
callback.apply(selectorEl, evt);
var s = '';
}
var s = '';
};
document.addEventListener(type, handler, false);
}
事件委托由于全部事件是绑定到document上的,所以会导致阻止冒泡失效,很多初学的同学不知道,这里要注意
事件模拟
事件模拟是dom事件的一种高级应用,一般情况下用不到,但是一些极端情况下他是解决实际问题的杀手锏
事件模拟是javascript事件机制中相当有用的功能,理解事件模拟与善用事件模拟是判别一个前端的重要依据,所以各位一定要深入理解
事件一般是由用户操作触发,其实javascript也是可以触发的,比较重要的是,javascript模拟的触发遵循事件流机制!!!
意思就是,javascript触发的事件与浏览器本身触发其实是一样的,简单模拟事件点击:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<style type="text/css">
#p { width: 300px; height: 300px; padding: 10px; border: 1px solid black; }
#c { width: 100px; height: 100px; border: 1px solid red; }
</style>
</head>
<body>
<div id="p">
parent
<div id="c">
child
</div>
</div>
<script type="text/javascript">
alert = function (msg) {
console.log(msg);
}
var p = document.getElementById('p'),
c = document.getElementById('c');
c.addEventListener('click', function (e) {
console.log(e);
alert('子节点捕获')
}, true);
c.addEventListener('click', function (e) {
console.log(e);
alert('子节点冒泡')
}, false);
p.addEventListener('click', function (e) {
console.log(e);
alert('父节点捕获')
}, true);
p.addEventListener('click', function (e) {
console.log(e);
alert('父节点冒泡')
}, false);
document.addEventListener('keydown', function (e) {
if (e.keyCode == '32') {
var type = 'click'; //要触发的事件类型
var bubbles = true; //事件是否可以冒泡
var cancelable = true; //事件是否可以阻止浏览器默认事件
var view = document.defaultView; //与事件关联的视图,该属性默认即可,不管
var detail = 0;
var screenX = 0;
var screenY = 0;
var clientX = 0;
var clientY = 0;
var ctrlKey = false; //是否按下ctrl
var altKey = false; //是否按下alt
var shiftKey = false;
var metaKey = false;
var button = 0; //表示按下哪一个鼠标键
var relatedTarget = 0; //模拟mousemove或者out时候用到,与事件相关的对象
var event = document.createEvent('Events');
event.myFlag = '叶小钗';
event.initEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY,
ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
console.log(event);
c.dispatchEvent(event);
}
}, false);
</script>
</body>
</html>
模拟点击事件是解决移动端点击响应的基石。