重探浏览器事件(浅析事件编程化)

时间: 2019-01-29阅读: 214标签: 浏览器

前言

在平常开发过程中,就算不使用现在主流的框架也至少得使用个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可用的属性列表(删除了控制台输出的原型和函数引用):

firefox67chrome72edge44.17763.1.0ie11ie9
altKeyaltKeyaltKeyaltKeyaltKey
bubblesbubblesbubblesAT_TARGETAT_TARGET
buttonbuttonbuttonbubblesbubbles
buttonsbuttonsbuttonsBUBBLING_PHASEBUBBLING_PHASE
cancelBubblecancelBubblecancelablebuttonbutton
cancelablecancelablecancelBubblebuttonsbuttons
clientXclientXclientXcancelablecancelable
clientYclientYclientYcancelBubblecancelBubble
composedcomposedctrlKeyCAPTURING_PHASECAPTURING_PHASE
ctrlKeyctrlKeycurrentTargetclientXclientX
currentTargetcurrentTargetdefaultPreventedclientYclientY
defaultPreventeddefaultPreventeddetailconstructorconstructor
detaildetaileventPhasectrlKeyctrlKey
eventPhaseeventPhasefromElementcurrentTargetcurrentTarget
explicitOriginalTargetfromElementheightdefaultPreventeddefaultPrevented
isTrustedisTrustedisPrimarydetaildetail
layerXlayerXisTrusteddeviceSessionIdeventPhase
layerYlayerYlayerXeventPhasefromElement
metaKeymetaKeylayerYfromElementisTrusted
movementXmovementXmetaKeyheightlayerX
movementYmovementYmovementXhwTimestamplayerY
mozInputSourceoffsetXmovementYisPrimarymetaKey
mozPressureoffsetYoffsetXisTrustedoffsetX
offsetXpageXoffsetYlayerXoffsetY
offsetYpageYpageXlayerYpageX
originalTargetpathpageYmetaKeypageY
pageXrelatedTargetpointerIdoffsetXrelatedTarget
pageYreturnValuepointerTypeoffsetYscreenX
rangeOffsetscreenXpressurepageXscreenY
rangeParentscreenYrelatedTargetpageYshiftKey
regionshiftKeyreturnValuepointerIdsrcElement
relatedTargetsourceCapabilitiesscreenXpointerTypetarget
returnValuesrcElementscreenYpressuretimeStamp
screenXtargetshiftKeyrelatedTargettoElement
screenYtimeStampsrcElementrotationtype
shiftKeytoElementtargetscreenXview
srcElementtypetiltXscreenYwhich
targetviewtiltYshiftKeyx
timeStampwhichtimeStampsrcElementy
typextoElementtarget
viewytwisttiltX
whichtypetiltY
xviewtimeStamp
ywhichtoElement
widthtype
xview
ywhich
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);
https://developer.mozilla.org...

srcElement

简单理解event.srcElement === event.target.

Event.srcElement 是标准的 Event.target 属性的一个别名。它只对老版本的IE浏览器有效。

https://developer.mozilla.org...

参考之前的表格后看来这个属性还没有被干掉,在目前最新的浏览器上它依然存在,不过已经不建议使用,除非你需要向下兼容.


完整的事件编程

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来进行继承.

详细参考:https://stackoverflow.com/que...


创建我们的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
    });
https://developer.mozilla.org...

如果你在浏览器中观察这个对象,你会发现事件上常见的属性诸如:

  • event.target
  • event.currentTarget
  • event.preventDefault()

都在这个new Event()返回的对象中,由于其他类型的事件都继承自Event这也解释了为什么事件对象中总是有这些属性.

和继承EventTarget一样,使用Event的过程也同样艰难,总的来说使用Event的难点在于它有两套API:

  1. 第一套比较新的API提供了现代的接口,也就是之前例子中的方式.
    在创建一个已有的事件的时候,你只需要使用全局的构造函数就可以,
    例如:new MouseEvent('test',/*对应MouseEvent的参数选项*/),
    但是缺点就是不支持IE浏览器.
  2. 第二套API支持IE浏览器,但是使用过程比较繁琐
    使用Event.createEvent(/*事件类型*/)创建对应事件类型的Event对象,
    使用Event.initEvent()来初始化事件,并且提供对应事件类型的参数,
    如果你创建一个MouseEvent类型的事件InitEvent方法最多需要15个参数.
    这种情况下使用new MouseEvent()传入对象配置的形式就简单多了.
一篇值得参考的文章,使用createEvent api

https://www.cnblogs.com/ggz19...

此外不同种类的事件,都有自己的全局构造函数,不同类型的构造函数的第二个参数中的选项也是不同的.

其他的构造函数请参考这里.


触发我们的事件

触发事件就显得简单多了,我们需要使用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).

详细参考https://developer.mozilla.org...


编程式的事件触发

我们在页面中来一次具体的实战,首先建立如下的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参数可以控制该事件是否允许冒泡


链接: http://www.fly63.com/article/detial/2638

不同浏览器的内核

网页上所用到的语言有:html, css, JavaScript等,其中,前两者通常决定了该页面长什么样,它们是可以说都是约定的规范。不同的浏览器在获取到某页面的代码文件后,负责根据这套规范将代码渲染出来呈现给用户

Js实现阻止浏览器返回的功能

无论pc端还是移动端,浏览器都会带有后退按钮或后退键.主要方便我们能返回以前访问过的页面,但有时候我们不得不关闭这个功能.尤其是对于一些推广落地页,用户进入后不希望它返回

完美解决安卓端百度浏览器屏蔽fixed悬浮元素的问题

h5活动页面底部有个悬浮图片按钮,使用fixed悬浮定位在底部,但是在安卓端的百度浏览器下打开,却发现该图片一闪而过,在百度浏览器中消失不见。

浏览器的三种Js弹窗方式

在做网页时,常常使用弹窗,以上就是浏览器的三种弹窗方式, alert 在测试时常用; confirm 可以套用if...else 来用 ,比如 :confirm点击了确定做什么事情,点击了取消又做什么事情;prompt 弹窗输入 ; 可以给网页设置密码。

火狐浏览器 访问所有HTTPS网站显示连接不安全解决办法

当 Firefox 连接到一个安全的网站时(网址最开始为“https://”),它必须确认该网站出具的证书有效且使用足够高的加密强度。如果证书无法通过验证,或加密强度过低,Firefox 会中止连接到这个网站,并显示“连接不安全”的错误信息页面。

GeckoView:Mozilla面向移动浏览器打造的渲染引擎

Mozilla 现已推出面向 Android 的全新移动浏览器 Firefox Preview。与大多数浏览器所采用的 Bink 渲染引擎不同,Firefox Preview 的渲染引擎 GeckoView 由 Mozilla 自行开发

常用浏览器内核及分类

浏览器是网页运行的平台,常用的浏览器有 IE、火狐(Firefox)、谷歌(Chrome)、Safari和Opera等。我们平时称为五大浏览器。 浏览器内核分成两部分:渲染引擎和 JS 引擎

理解Js中的Repaint和Reflow

最近,在研究React的虚拟DOM如此之快的原因时,我意识到我们对javascript性能的了解甚少。所以我写这篇文章是为了帮助提高对Repaint和Reflow以及JavaScript性能的认识。

用Vue.js在浏览器中裁剪图像

你是否写了一个需要接受用户上传图片的 Web 应用,后来才意识到用户总是提供各种形状和大小的图像来破坏你的网站主题?在网络上处理图像很容易成为一种痛苦 —— 当然,除非你使用了正确的工具。

防止用户在浏览器下调试查看js代码

浏览器可以通过F12或者鼠标右键的形式打开html页面,然后就可以看到页面的的信息,如dom结构,加载文件、请求信息等。那有没有办法禁止掉这一行为呢?

fly63.com版权所有,内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权,请与小编联系!情况属实本人将予以删除!

广告赞助文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全