媒介
在寻常开辟历程当中,就算不运用如今主流的框架也至少得运用个Jquery,这些东西帮我们一致差别阅读器平台之间的差别和细节,能够将注重力集合到开辟上来.
不过有意思的一点是,在看完高程的N年后我竟然连event对象中的target和currentTarget属性的辨别都忘记了.
先提几个引子:
- 你能说出event.currentTarget和event.target的辨别吗?
- 假如能够那末event.srcElement和事宜监听函数中的this呢
- 怎样运用编程的体式格局来触发事宜,而不借助阅读器默许触发体式格局?
- 怎样竖立一个我们自身的Event对象,然后自定义我们的事宜?
- 完成上方的内容的同时该怎样兼容IE阅读器?
假如这几个内容你都熟习了,那末这篇文章不会给你带来太多的协助.
在正文最先之前先来阅读一个表格,来看一下差别阅读器之间Event对象的属性有何差别:
<button id="button">click me then change word</button>
var button = document.getElementById('button');
button.addEventListener('click',function (event) {
console.log(event);
});
在下方的表格中我们记录了差别阅读器之间click点击后event可用的属性列表(删除了掌握台输出的原型和函数援用):
firefox67 | chrome72 | edge44.17763.1.0 | ie11 | ie9 |
---|---|---|---|---|
altKey | altKey | altKey | altKey | altKey |
bubbles | bubbles | bubbles | AT_TARGET | AT_TARGET |
button | button | button | bubbles | bubbles |
buttons | buttons | buttons | BUBBLING_PHASE | BUBBLING_PHASE |
cancelBubble | cancelBubble | cancelable | button | button |
cancelable | cancelable | cancelBubble | buttons | buttons |
clientX | clientX | clientX | cancelable | cancelable |
clientY | clientY | clientY | cancelBubble | cancelBubble |
composed | composed | ctrlKey | CAPTURING_PHASE | CAPTURING_PHASE |
ctrlKey | ctrlKey | currentTarget | clientX | clientX |
currentTarget | currentTarget | defaultPrevented | clientY | clientY |
defaultPrevented | defaultPrevented | detail | constructor | constructor |
detail | detail | eventPhase | ctrlKey | ctrlKey |
eventPhase | eventPhase | fromElement | currentTarget | currentTarget |
explicitOriginalTarget | fromElement | height | defaultPrevented | defaultPrevented |
isTrusted | isTrusted | isPrimary | detail | detail |
layerX | layerX | isTrusted | deviceSessionId | eventPhase |
layerY | layerY | layerX | eventPhase | fromElement |
metaKey | metaKey | layerY | fromElement | isTrusted |
movementX | movementX | metaKey | height | layerX |
movementY | movementY | movementX | hwTimestamp | layerY |
mozInputSource | offsetX | movementY | isPrimary | metaKey |
mozPressure | offsetY | offsetX | isTrusted | offsetX |
offsetX | pageX | offsetY | layerX | offsetY |
offsetY | pageY | pageX | layerY | pageX |
originalTarget | path | pageY | metaKey | pageY |
pageX | relatedTarget | pointerId | offsetX | relatedTarget |
pageY | returnValue | pointerType | offsetY | screenX |
rangeOffset | screenX | pressure | pageX | screenY |
rangeParent | screenY | relatedTarget | pageY | shiftKey |
region | shiftKey | returnValue | pointerId | srcElement |
relatedTarget | sourceCapabilities | screenX | pointerType | target |
returnValue | srcElement | screenY | pressure | timeStamp |
screenX | target | shiftKey | relatedTarget | toElement |
screenY | timeStamp | srcElement | rotation | type |
shiftKey | toElement | target | screenX | view |
srcElement | type | tiltX | screenY | which |
target | view | tiltY | shiftKey | x |
timeStamp | which | timeStamp | srcElement | y |
type | x | toElement | target | |
view | y | twist | tiltX | |
which | type | tiltY | ||
x | view | timeStamp | ||
y | which | toElement | ||
width | type | |||
x | view | |||
y | which | |||
width | ||||
x | ||||
y |
经由历程这个表格我们能够视察Event对象在差别阅读器之间组织是差别的,出人意表的是纵然是在当代阅读器中事宜对象也存在着差别.
固然这篇文章可不是将一切的Event属性都将一遍,要知道差别的事宜的Event对象组织是差别的.
吐槽:本来是盘算供应IE8的然则ie8不能运用addEventListener来监听事宜懒得去搞ie那套数据了.
currentTarget,target,srcElement,this
currentTarget
一句话:
哪一个元素上监听的事宜,event.currentTarget
返回的就是这个对象的自身的援用.
假如你的一个事宜监听函数被注册到了多个DOM元素上,应用这个属性你就能够推断是谁触发的事宜.
this
回调函数中的this === event.currentTarget
.
button.addEventListener('click',function (event) {
console.log(event.currentTarget === this); // true
});
target
event.target和上面的三者差别,这内里触及到了DOM中的一个基本知识事宜冒泡和事宜阻拦.
关于这两点我置信人人都已相识了,纵然不相识网上引见的文章也有一大堆.
我们用事宜冒泡来举例,而且改写我们之前的谁人例子:
<div id="wrap">
<button id="button">test click</button>
</div>
var
wrap = document.getElementById('wrap'),
button = document.getElementById('button');
// 注重我们监听的是wrap的click事宜,而不是button的click事宜
wrap.addEventListener('click',function (event) {
// event.target指向的是按钮,因为我们点击的是按钮
console.log(event.target === button && event.target === event.srcElement); // true
// 当我们点击按钮触发的事宜冒泡到了wrap,所以触发了wrap的click事宜,
// 此时currentTarget指向的是wrap
console.log(wrap===this && wrap === event.currentTarget); // true
// 直接打印event然后掌握台中检察currentTaget会返回null
// 你能够将他赋值到一个变量在打印输出这个变量
// see https://github.com/vuejs/vue/issues/6867#issuecomment-338195468
})
在这个例子中,我们点击页面中的按钮,然后再按钮的包裹div中吸收到了button冒泡上来的事宜,这个中:
- this 和 currentTarget指向的都是增加了监听器的对象这里就是wrap
- target 和 srcElement指向的是触发了事宜的元素
事宜托付也是event.target最罕见的用处之一:
// Make a list
var ul = document.createElement('ul');
document.body.appendChild(ul);
var li1 = document.createElement('li');
var li2 = document.createElement('li');
ul.appendChild(li1);
ul.appendChild(li2);
function hide(e){
// e.target 援用着 <li> 元素
// 不像 e.currentTarget 援用着其父级的 <ul> 元素.
e.target.style.visibility = 'hidden';
}
// 增加监听事宜到列表,当每一个 <li> 被点击的时刻都邑触发。
ul.addEventListener('click', hide, false);
srcElement
简朴明白event.srcElement === event.target
.
Event.srcElement 是规范的
Event.target
属性的一个别号。它只对老版本的IE阅读器有用。
参考之前的表格后看来这个属性还没有被干掉,在现在最新的阅读器上它依旧存在,不过已不发起运用,除非你须要向下兼容.
完全的事宜编程
EventTarget接口
当我们在运用以下的要领的时刻:
- elem.addEventListener
- elem.removeEventListener
- elem.dispatchEvent
实际上是在运用EventTarget接口上的功用.
比方我们能够竖立一个新的EventTarget对象来增加事宜监听:
const a = new EventTarget;
a.addEventListener('click',()=>{
})
然则这没有任何意义,因为这里没有事宜被触发.我们仅仅是增加了事宜监听器罢了.
为了到达我们目标经由历程编程的体式格局来实行完全的事宜流程我们还须要完成以下的几步:
- 继续EventTarget而不是直接运用EventTarget的实例,
- 在事宜监听函数中通报Event对象
- 找个处所来触发这个事宜
起首我们来继续EventTarget对象:
继续EventTarget
在阅读器中大部份能够增加删除事宜的对象都继续了EventTarget对象.
你能够在掌握台挑选一个HTML元素一起查找原型链获得.
然则他们进过重重继续,都有自身的奇特属性和事宜范例以至是差别的组织函数.
为了和已有的事宜举行辨别我们这里须要对EventTarget举行继续:
// --- 包装EventTarget最先
function MyEventTarget() {
var target = document.createTextNode(null);
this.addEventListener = target.addEventListener.bind(target);
this.removeEventListener = target.removeEventListener.bind(target);
this.dispatchEvent = target.dispatchEvent.bind(target);
}
MyEventTarget.prototype = EventTarget.prototype;
// --- 包装EventTarget完毕
// --- 竖立我们继续EventTarget的组织函数
function myElem() {
}
myElem.prototype = new MyEventTarget;
myElem.prototype.constructor = myElem;
// 竖立实例
const instance = new myElem();
instance.addEventListener('click',()=>{
// 如今我们实例能够监听事宜了
});
console.log(instance);
继续的历程看似异常复杂,尤其是包装EventTarget显得节外生枝.然则搞定EventTarget的继续确切花了我大批的时候去寻觅解决方案.
你完全能够编写自身的继续体式格局往来来往继续EventTarget,不过你会发明这个中的坑异常深.
简朴来讲,EventTarget在JavaScript中真的就是一个接口,虽然是以函数的情势存在,然则它不是组织函数(这点在Chrome64 和firefox59后举行了修正).
总之经由历程原型链继续的EventTarget一切没法事情,假如运用ES6的类式继续在当代阅读器中(Chrome64和firefox59后)能够运用class来举行继续.
细致参考:
竖立我们的Event对象
猎取一个event:
document.getElementById('button').addEventListener('click',(event)=>{
// event
console.log(event);
})
假如你在阅读器中运转这段代码而且在掌握台中检察,你会发明变量event的称号MouseEvent,假如你沿着原型链向上你会发明继续的是UIEvent再次向上检察则是真正的Event.
事宜触发中通报的第一个参数我们一般叫它event,一切的event对象都基于Event,然则这不意味着这类关联的窗户纸就只有一层,click事宜中的event和Event之间就隔着一个UIEvent.
一般跟着event继续的层数越多,event对象身上的属性也会越来越多.
如今我们来竖立一个规范的Event对象:
// 运用全局的Event
new Event('test',{ // 事宜范例
bubbles:false, // 是不是冒泡 默许false
cancelable:false,// 是不是能够被作废 默许false
});
假如你在阅读器中视察这个对象,你会发明事宜上罕见的属性诸如:
- event.target
- event.currentTarget
- event.preventDefault()
都在这个new Event()返回的对象中,因为其他范例的事宜都继续自Event这也诠释了为何事宜对象中总是有这些属性.
和继续EventTarget一样,运用Event的历程也一样困难,总的来讲运用Event的难点在于它有两套API:
- 第一套比较新的API供应了当代的接口,也就是之前例子中的体式格局.
在竖立一个已有的事宜的时刻,你只须要运用全局的组织函数就能够,
比方:new MouseEvent('test',/*对应MouseEvent的参数选项*/)
,
然则瑕玷就是不支撑IE阅读器. - 第二套API支撑IE阅读器,然则运用历程比较烦琐
运用Event.createEvent(/*事宜范例*/)
竖立对应事宜范例的Event对象,
运用Event.initEvent()
来初始化事宜,而且供应对应事宜范例的参数,
假如你竖立一个MouseEvent范例的事宜InitEvent要领最多须要15个参数.
这类情况下运用new MouseEvent()
传入对象设置的情势就简朴多了.
一篇值得参考的文章,运用createEvent api
别的差别品种的事宜,都有自身的全局组织函数,差别范例的组织函数的第二个参数中的选项也是差别的.
其他的组织函数请参考这里.
触发我们的事宜
触发事宜就显得简朴多了,我们须要运用EventTarget.dispatchEvent
要领.
在我们之前竖立的实例上举行事宜的触发:
function MyEventTarget() {
var target = document.createTextNode(null);
this.addEventListener = target.addEventListener.bind(target);
this.removeEventListener = target.removeEventListener.bind(target);
this.dispatchEvent = target.dispatchEvent.bind(target);
}
MyEventTarget.prototype = EventTarget.prototype;
function myElem() {
}
myElem.prototype = new MyEventTarget;
myElem.prototype.constructor = myElem;
const instance = new myElem();
instance.addEventListener('test', (event) => {
console.log(event); // 监听事宜而且打印实例
});
const myEvent = new Event('test'); // 竖立Event实例
instance.dispatchEvent(myEvent); // 触发事宜
当你挪用dispatchEvent的时刻,EventTarget会根据对应事宜注册的递次来同步实行这些事宜监听器.
假如在事宜监听器中挪用了event.preventDefault
,那末dispatchEvent就返回false反之返回true(条件是cancleable为true).
细致参考
编程式的事宜触发
我们在页面中来一次详细的实战,起首竖立以下的HTML组织:
<div id="wrap">
<button id="button">test click</button>
</div>
我们在#wrap中监听click事宜,然后在#button触发click事宜.
如许我们能够演习一下Event中bubbles(许可冒泡)参数的运用,
别的还能够测试click事宜中的Event对象假如不是MouseEvent的实例那末监听器是不是会被触发.
const
button = document.getElementById('button'),
wrap = document.getElementById('wrap');
wrap.addEventListener('click', (event) => {
console.log(event); // 打印event对象
});
const myEvent1 = new Event('click', {
bubbles: false, // 不能够冒泡
});
const myEvent2 = new Event('click', {
bubbles: true, // 能够冒泡
});
button.dispatchEvent(myEvent1); // 此次没有打印出内容
button.dispatchEvent(myEvent2); // 此次打印出了内容
结论很明白:
- dispatchEvent实行的时刻只如果Event的实例且范例雷同那末监听器就会被触发.
- bubbles参数能够掌握该事宜是不是许可冒泡