dom-event

事件模型

事件指的是用户和浏览器之间的交互行为。比如:点击按钮、关闭窗口、鼠标移动。。。

事件是不用驼峰的

我们可以为事件来绑定回调函数来响应事件。

事件的绑定

标签属性

可以在标签的事件属性中设置相应的 JS 代码

<button onclick="js 代码。。。">按钮</button>

结构行为耦合,不推荐使用

DOM0

可以通过为对象的指定事件属性设置回调函数的形式来处理事件

只能绑定一个事件,后绑定的会覆盖前面的

  <button id="btn">按钮\</button>
  <script>
  var btn = document.getElementById("btn");
  btn.onclick = function(){};
  </script>

onxxx 又称为 事件句柄,相应的,addEventListener('xxx'),是事件监听

DOM2

addEventListener()

使用 addEventListener() 可以同时为一个元素的相同事件同时绑定多个响应函数,这样当事件被触发时,响应函数将会 按照函数的绑定顺序执行

Dom2 事件,Dom2 标准定义的绑定方式

该方法不支持 ie8

参数:

  1. 事件触发的字符串,不需要 on
  2. 回调函数,当事件触发时,该函数会被调用
  3. 是否在捕获阶段触发事件,需要一个布尔值,一般传 false
   btn01.addEventListener(“click”,function(){},false);

option

attachEvent()

参数:

  1. 事件的字符串,要 on
  2. 回调函数

DOM0 和 DOM2 两种绑定方式的区别

方法 冒泡 取消冒泡 cbCount 禁止默认行为的方法
DOM0 默认冒泡 event.calcelBubble = true 一个 return false
attachEvent()
addEventListener 通过 option 控制,默认冒泡 event.stopPropagation() 多个 event.preventDefault

给属性定义函数,无论如何都不会报错的,所以给事件绑定监听一定要等提示,不然 onload 拼错了,能 debugger 一个上午

通过 event.cancelable 查看默认行为是否可以被取消

this

解除事件的绑定

DOM0: 想解除事件就相当简单了,只需要再注册一次事件,把值设成 null

DOM2:removeEventListener

注意: 即使引用都相同, capture 的事件解绑时也必须使用 capture

DOM2:detachEvent(ie)

事件的冒泡(Bubble)

事件的冒泡指的是事件向上传导,当后代元素上的事件被触发时,将会导致其祖先元素上的同类事件(相同响应条件)也会触发。

亲了小姑娘一口,被小姑娘打了,被小姑娘爸爸打了,被小姑娘爷爷打了

事件的冒泡大部分情况下都是有益的,如果需要取消冒泡,则需要使用事件对象来取消

DOM2 级

元素.事件 = addEventListener(function(event){
event = event || window.event;
event.stopPropagation();
});

DOM 0 级 可以通过取消事件的默认行为来阻止冒泡

元素.事件 = function(event){
	return false
}
// 好像并不可以取消

DOM 0 级 还可以可以将事件对象的 cancelBubble 设置为 true,即可取消冒泡,IE 特性,但是 chrome 也实现了

元素.事件 = function(event){
event = event || window.event;
event.cancelBubble = true;
};

事件的传播

微软公司认为事件应该是由内向外传播,也就是当事件触发时,应该先触发当前元素上的事件,然后再向当前元素的祖先元素上传播,也就说事件应该在冒泡阶段执行。所以 ie 的 attachEvent(),没有 true or false

网景公司认为事件应该是由外向内传播的,也就是当前事件触发时,应该先触发当前元素的最外层的祖先元素的事件,然后在向内传播给后代元素

W3C 综合了两个公司的方案,将事件传播分成了三个阶段

  1. 捕获阶段: 在捕获阶段时从最外层的祖先元素,向目标元素进行事件的捕获,但是默认此时不会触发事件
  2. 目标阶段: 事件捕获到目标元素,捕获结束开始在目标元素上触发事件
  3. 冒泡阶段: 事件从目标元素向他的祖先元素传递,依次触发祖先元素上的事件

1546564409847

如果希望在捕获阶段就触发事件,可以将 addEventListener() 的第三个参数设置为 true,一般情况下我们不会希望在捕获阶段触发事件,所以这个参数一般都是 false

事件流

单击蓝色框后,开始事件捕获阶段:

  1. 从最外层的 document 对象(浏览器其实是从 window 对象开始的)向内捕获事件,路过红色框时,查看到红色框有事件,
  2. ​但是红色框说:“我是在冒泡阶段执行,现在是捕获阶段,等你回来再说吧。”
  3. ​接下来是黄色框:“我在捕获阶段执行,就是现在执行!在控制台输“黄色框”吧~~”
  4. 接下来到达目标阶段:
  5. ​“DOM2 级事件”规范要求捕获阶段不会涉及事件目标即我们点击的那个最具体的元素,
  6. ​但 IE9、Chrome 等浏览器都会在捕获阶段触发事件对象上的事件。执行目标对象的事件函数,控制台输出“蓝色
  7. 框”。

最后是冒泡阶段:

  1. ​由目标对象向外传递,到达黄色框,黄色框说:“我在捕获阶段执行过了,你走吧...”
  2. ​然后到达红色框,红色框说:“你终于回来了,现在就执行我的事件!”控制台输出“红色框”。
  3. ​然后继续向外传播,直到到达 document 对象后停止。

其他:更改了元素绑定事件代码的顺序,执行顺序也和上面表现的一致。

事件的委派

希望只绑定一次事件,即可以应用到多个事件上,即使事件是后添加的

绑定给共同的祖先元素,点击后代可以冒泡至祖先,触发响应函数

event.target 返回触发事件的对象
if(event.target.className == );

this 返回绑定事件的对象: 事件监听绑定在父元素上,事件也是发生在父元素身上,只是点击子元素也可以触发, 因为有冒泡

浏览器实现

前端杂谈: DOM event 原理 - 知乎

可以参考这个逻辑, 在 canvas 上实现对齐浏览器的事件模型

EventTarget 接口

事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。DOM 支持大量的事件,本章开始介绍 DOM 的事件编程。

概述

DOM 的事件操作(监听和触发),都定义在 EventTarget 接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequestAudioNodeAudioContext)也部署了这个接口。

该接口主要提供三个实例方法。

EventTarget.dispatchEvent()

EventTarget.dispatchEvent 方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了 Event.preventDefault(),则返回值为 false,否则为 true

target.dispatchEvent(event)

dispatchEvent 方法的参数是一个 Event 对象的实例 (详见 事件对象 (event) 章节)

  para.addEventListener('click', hello, false);
  var event = new Event('click');
  para.dispatchEvent(event);

上面代码在当前节点触发了 click 事件。

如果 dispatchEvent 方法的参数为空,或者不是一个有效的事件对象,将报错。

下面代码根据 dispatchEvent 方法的返回值,判断事件是否被取消了。

  var canceled = !cb.dispatchEvent(event);
  if (canceled) {
    console.log('事件取消');
  } else {
    console.log('事件未取消');
  }

事件对象 (event)

事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个 Event 对象,所有的事件都是这个对象的实例,或者说继承了 Event.prototype 对象。

这个事件对象中封装了当前事件的相关信息,比如:鼠标的坐标,键盘的按键,鼠标的按键,滚轮的方向。

可以在响应函数中定义一个形参,来使用 事件对象,但是在 IE8 以下浏览器中事件对象没有做完实参传递,而是作为 window 对象的属性保存 (不兼容火狐)

  元素.事件 = function(event){ event = event || window.event; };
  元素.事件 = function(e){ e = e || event; };

Event 对象本身就是一个构造函数,可以用来生成新的实例。

  event = new Event(type, options);

参数

  var ev = new Event(
    'look',
    {
      'bubbles': true,
      'cancelable': false
    }
  );
  document.dispatchEvent(ev);

实例属性

Event.bubbles,Event.eventPhase

Event.bubbles 属性返回一个布尔值,表示当前事件是否会冒泡。该属性为只读属性,一般用来了解 Event 实例是否可以冒泡。前面说过,除非显式声明,Event 构造函数生成的事件,默认是不冒泡的。

Event.eventPhase 属性返回一个整数常量,表示事件目前所处的阶段。该属性只读。

Event.cancelable

Event.cancelable 属性返回一个布尔值,表示事件是否可以取消。该属性为只读属性,一般用来了解 Event 实例的特性。

大多数浏览器的原生事件是可以取消的。比如,取消 click 事件,点击链接将无效。但是除非显式声明,Event 构造函数生成的事件,默认是不可以取消的。

var evt = new Event('foo');
evt.cancelable  // false

Event.cancelable 属性为 true 时,调用 Event.preventDefault() 就可以取消这个事件,阻止浏览器对该事件的默认行为。

如果事件不能取消,调用 Event.preventDefault() 会没有任何效果。所以使用这个方法之前,最好用 Event.cancelable 属性判断一下是否可以取消。

Event.cancelBubble

Event.cancelBubble 属性是一个布尔值,如果设为 true,相当于执行 Event.stopPropagation(),可以阻止事件的传播。

Event.defaultPrevented

Event.defaultPrevented 属性返回一个布尔值,表示该事件是否调用过 Event.preventDefault 方法。该属性只读。

Event.currentTarget,Event.target

事件发生以后,会经过捕获和冒泡两个阶段,依次通过多个 DOM 节点。因此,任意时点都有两个与事件相关的节点,一个是事件的原始触发节点(Event.target),另一个是事件当前正在通过的节点(Event.currentTarget)。前者通常是后者的后代节点。

Event.currentTarget 属性返回事件当前所在的节点,即事件当前正在通过的节点,也就是当前正在执行的监听函数所在的那个节点。随着事件的传播,这个属性的值会变。

Event.target 属性返回原始触发事件的那个节点,即事件最初发生的节点。这个属性不会随着事件的传播而改变。

事件传播过程中,不同节点的监听函数内部的 Event.targetEvent.currentTarget 属性的值是不一样的。

Event.type

Event.type 属性返回一个字符串,表示事件类型。事件的类型是在生成事件的时候指定的。该属性只读。

var evt = new Event('foo');
evt.type // "foo"

Event.timeStamp

Event.timeStamp 属性返回一个毫秒时间戳,表示事件发生的时间。它是相对于网页加载成功开始计算的。

  var evt = new Event('foo');
  evt.timeStamp // 3683.6999999995896

它的返回值有可能是整数,也有可能是小数(高精度时间戳),取决于浏览器的设置。

下面是一个计算鼠标移动速度的例子,显示每秒移动的像素数量。

  var previousX;

Event.isTrusted

Event.isTrusted 属性返回一个布尔值,表示该事件是否由真实的用户行为产生。比如,用户点击链接会产生一个 click 事件,该事件是用户产生的;Event 构造函数生成的事件,则是脚本产生的。

  var evt = new Event('foo');
  evt.isTrusted // false

上面代码中,evt 对象是脚本产生的,所以 isTrusted 属性返回 false

Event.detail

Event.detail 属性只有浏览器的 UI (用户界面)事件才具有。该属性返回一个数值,表示事件的某种信息。具体含义与事件类型相关。比如:

实例方法

Event.preventDefault()

Event.preventDefault 方法取消浏览器对当前事件的默认行为。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了;再比如,按一下空格键,页面向下滚动一段距离,使用这个方法以后也不会滚动了。该方法生效的前提是,事件对象的 cancelable 属性为 true,如果为 false,调用该方法没有任何效果。

注意,该方法只是取消事件对当前元素的默认影响,不会阻止事件的传播。如果要阻止传播,可以使用 stopPropagation()stopImmediatePropagation() 方法。

Event.stopPropagation()

stopPropagation 方法阻止事件在 DOM 中继续传播,防止再触发定义在别的节点上的监听函数,但是不包括在当前节点上其他的事件监听函数。

Event.stopImmediatePropagation()

Event.stopImmediatePropagation 方法阻止同一个事件的其他监听函数被调用,不管监听函数定义在当前节点还是其他节点。也就是说,该方法阻止事件的传播,比 Event.stopPropagation() 更彻底。

如果同一个节点对于同一个事件指定了多个监听函数,这些函数会根据添加的顺序依次调用。只要其中有一个监听函数调用了 Event.stopImmediatePropagation 方法,其他的监听函数就不会再执行了。

  function l1(e){
    e.stopImmediatePropagation();
  }
  
  function l2(e){
    console.log('hello world');
  }
  
  el.addEventListener('click', l1, false);
  el.addEventListener('click', l2, false);

上面代码在 el 节点上,为 click 事件添加了两个监听函数 l1l2。由于 l1 调用了 event.stopImmediatePropagation 方法,所以 l2 不会被调用。

Event.composedPath()

Event.composedPath() 返回一个数组,成员是事件的最底层节点和依次冒泡经过的所有上层节点。

MouseEvent

鼠标事件的种类

鼠标事件指与鼠标相关的事件,继承了 MouseEvent 接口。具体的事件主要有以下一些。

click 事件指的是,用户在同一个位置先完成 mousedown 动作,再完成 mouseup 动作。因此,触发顺序是,mousedown 首先触发,mouseup 接着触发,click 最后触发。

dblclick 事件则会在 mousedownmouseupclick 之后触发。

mouseover 事件和 mouseenter 事件,都是鼠标进入一个节点时触发。两者的区别是:

<div id="over">
  <div class="inner"></div>
  <div class="outer" id='stopped'></div>
</div>
<div id="enter">
  <div class="inner"></div>
  <div class="outer" id='stopEnter'></div>
</div>

<style>
  #over{
    position: absolute;
    left:200px;
    width: 300px;
    height: 300px;
    background-color: pink;
  }
  #enter{
    position: absolute;
    top:300px;
    width: 300px;
    height: 300px;
    background-color: green;
  }
  .inner{
    position: absolute;
    top:200px;
    width: 50px;
    height: 50px;
    background-color: orange;
  }
  .outer{
    position: absolute;
    left:350px;top:200px;
    width: 50px;
    height: 50px;
    background-color: red;
  }
</style>

<script>
  enter.onmouseenter = function(){
    console.log('enter')
  }
  enter.onmouseleave = function(){
    console.log('leave')
  }
  over.onmouseover = function(){
    console.log('over')
  }
  over.onmouseout = function(){
    console.log('out')
  }
  stopped.onmouseover = function(ev){
    ev.cancelBubble = true
    return false
  }
  stopEnter.addEventListener('mouseenter',function(ev){
    console.log('enter outer')
    ev.stopPropagation()
  })
</script>

MouseEvent 接口概述

MouseEvent 接口代表了鼠标相关的事件,单击(click)、双击(dblclick)、松开鼠标键(mouseup)、按下鼠标键(mousedown)等动作,所产生的事件对象都是 MouseEvent 实例。此外,滚轮事件和拖拉事件也是 MouseEvent 实例。

MouseEvent 接口继承了 Event 接口,所以拥有 Event 的所有属性和方法。它还有自己的属性和方法。

MouseEvent()

浏览器原生提供一个 MouseEvent 构造函数,用于新建一个 MouseEvent 实例。

var event = new MouseEvent(type, options);

MouseEvent 构造函数接受两个参数。第一个参数是字符串,表示事件名称;第二个参数是一个事件配置对象,该参数可选。除了 Event 接口的实例配置属性,该对象可以配置以下属性,所有属性都是可选的。

下面是一个例子。

function simulateClick() {
  var event = new MouseEvent('click', {
    'bubbles': true,
    'cancelable': true
  });
  var cb = document.getElementById('checkbox');
  cb.dispatchEvent(event);
}

上面代码生成一个鼠标点击事件,并触发该事件。

MouseEvent 接口的实例属性

altKey,ctrlKey,metaKey,shiftKey

MouseEvent.altKeyMouseEvent.ctrlKeyMouseEvent.metaKeyMouseEvent.shiftKey 这四个属性都返回一个布尔值,表示事件发生时,是否按下对应的键。它们都是只读属性。

button,buttons

MouseEvent.button 属性返回一个数值,表示事件发生时按下了鼠标的哪个键。该属性只读。

MouseEvent.buttons 属性返回一个三个比特位的值,表示同时按下了哪些键。它用来处理同时按下多个鼠标键的情况。该属性只读。

同时按下多个键的时候,每个按下的键对应的比特位都会有值。比如,同时按下左键和右键,会返回 3(二进制为 011)。

screenX,screenY

MouseEvent.screenX 属性返回鼠标位置相对于屏幕左上角的水平坐标(单位像素)

MouseEvent.screenY 属性返回垂直坐标。这两个属性都是只读属性。

movementX,movementY

MouseEvent.movementX 属性返回当前位置与上一个 mousemove 事件之间的水平距离(单位像素)。数值上,它等于下面的计算公式。

currentEvent.movementX = currentEvent.screenX - previousEvent.screenX

MouseEvent.movementY 属性返回当前位置与上一个 mousemove 事件之间的垂直距离(单位像素)。数值上,它等于下面的计算公式。

currentEvent.movementY = currentEvent.screenY - previousEvent.screenY。

这两个属性都是只读属性。

clientX,clientY

MouseEvent.clientX 只读

MouseEvent.clientY 只读

pageX,pageY

MouseEvent.pageX 只读(IE9)

MouseEvent.pageY 只读(IE9)

/* HTML 代码如下
  <style>
    body {
      height: 2000px;
    }
  </style>
*/
document.body.addEventListener(
  'click',
  function (e) {
    console.log(e.pageX);
    console.log(e.pageY);
  },
  false
);

上面代码中,页面高度为 2000 像素,会产生垂直滚动条。滚动到页面底部,点击鼠标输出的 pageY 值会接近 2000。

offsetX,offsetY

MouseEvent.offsetX 属性返回鼠标位置与目标节点左侧的 padding 边缘的水平距离(单位像素)

MouseEvent.offsetY 属性返回与目标节点上方的 padding 边缘的垂直距离。这两个属性都是只读属性。

MouseEvent.relatedTarget 属性返回事件的相关节点。对于那些没有相关节点的事件,该属性返回 null。该属性只读。

下表列出不同事件的 target 属性值和 relatedTarget 属性值义。

事件名称 target 属性 relatedTarget 属性
focusin 接受焦点的节点 丧失焦点的节点
focusout 丧失焦点的节点 接受焦点的节点
mouseenter 将要进入的节点 将要离开的节点
mouseleave 将要离开的节点 将要进入的节点
mouseout 将要离开的节点 将要进入的节点
mouseover 将要进入的节点 将要离开的节点
dragenter 将要进入的节点 将要离开的节点
dragexit 将要离开的节点 将要进入的节点
/*
  HTML 代码如下
  <div id="outer" style="height:50px;width:50px;border-width:1px solid black;">
    <div id="inner" style="height:25px;width:25px;border:1px solid black;"></div>
  </div>
*/

var inner = document.getElementById('inner');
inner.addEventListener('mouseover', function (event) {
  console.log('进入' + event.target.id + ' 离开' + event.relatedTarget.id);
}, false);
inner.addEventListener('mouseenter', function (event) {
  console.log('进入' + event.target.id + ' 离开' + event.relatedTarget.id);
});
inner.addEventListener('mouseout', function () {
  console.log('离开' + event.target.id + ' 进入' + event.relatedTarget.id);
});
inner.addEventListener("mouseleave", function (){
  console.log('离开' + event.target.id + ' 进入' + event.relatedTarget.id);
});

// 鼠标从 outer 进入inner,输出
// 进入inner 离开outer
// 进入inner 离开outer

// 鼠标从 inner进入 outer,输出
// 离开inner 进入outer
// 离开inner 进入outer

MouseEvent 接口的实例方法

getModifierState()

MouseEvent.getModifierState 方法返回一个布尔值,表示有没有按下特定的功能键。它的参数是一个表示 功能键 的字符串。

document.addEventListener('click', function (e) {
  console.log(e.getModifierState('CapsLock'));
}, false);

上面的代码可以了解用户是否按下了大写键。

WheelEvent 接口

非标准滚轮事件

onmousewheel(DOM0)

chrome、ie 都支持,火狐不兼容该特性

火狐中可以使用:DOMMouseScroll,该事件需要通过 addEventListenner() 方法(DOM2)

bind(box1,"DOMMouseScroll",box1.onmousewheel);
//把其他浏览器的函数作为回调函数传入火狐的函数中,视作普通函数对象进行调用

event.wheelDelta

概述

WheelEvent 接口继承了 MouseEvent 实例,代表鼠标滚轮事件的实例对象。目前,鼠标滚轮相关的事件只有一个 wheel 事件,用户滚动鼠标的滚轮,就生成这个事件的实例。

**该事件为标准规定的事件接口。**早期的浏览器实现过 MouseWheelEventMouseScrollEvent 两种滚轮事件接口,但这两种接口皆非标准,加之各浏览器间对其兼容性极差。因而开发者应用该标准事件接口取代这两个非标准接口。

注意事项: 请勿想当然依据滚轮方向(即该事件的各 delta 属性值)来推断文档内容的滚动方向,因标准未定义滚轮事件具体会引发什么样的行为,引发的行为实际上是各浏览器自行定义的。即便滚轮事件引发了文档内容的滚动行为,也不表示滚轮方向和文档内容的滚动方向一定相同。因而通过该滚轮事件获知文档内容滚动方向的方法并不可靠。要获取文档内容的滚动方向,可在文档内容滚动事件(scroll)中监视 scrollLeftscrollTop 二值变化情况,即可推断出滚动方向了。

WheelEvent()

浏览器原生提供 WheelEvent() 构造函数,用来生成 WheelEvent 实例。

var wheelEvent = new WheelEvent(type, options);

WheelEvent() 构造函数可以接受两个参数,第一个是字符串,表示事件类型,对于滚轮事件来说,这个值目前只能是 wheel。第二个参数是事件的配置对象。该对象的属性除了 EventUIEvent 的配置属性以外,还可以接受以下几个属性,所有属性都是可选的。

实例属性

WheelEvent 事件实例除了具有 EventMouseEvent 的实例属性和实例方法,还有一些自己的实例属性,但是没有自己的实例方法。

下面的属性都是只读属性。

默认行为

当滚轮滚动时,如果浏览器有滚动条,滚动条会随之滚动,

这是浏览器的默认行为,如果不希望发生,则可以取消默认行为

return false;

使用 addEventListener() 方法绑定的函数,不能使用 return false 取消默认行为,需要使用 event.preventDefault();

实践

pointer-events:none, 代表<a>标签上的事件都取消了, cursor 属性自然也没有了

pointer-events 这个 CSS3 属性,对浏览器的兼容并不好 IE11

滚轮事件

bind(box1,"DOMMouseScroll",box1.onmousewheel);
//把其他浏览器的函数作为回调函数传入火狐的函数中,视作普通函数对象进行调用

PubSub.subscribe("deleComment",(msg,index)=>{
  this.deleteComment(index)
})

deleteComment(index){}

//可以简写,因为第二个参数接受一个回调函数,这个回调函数有两个参数,第一个是msg,第二个是index,
//如果deleteComment也设计成这样(msg,index),其实就是和上面那种麻烦的形式是一样的
PubSub.subscribe("deleComment",deleteComment)

deleteComment(msg,index){}

KeyboardEvent

键盘事件的种类 @@@

键盘事件由用户击打键盘触发,主要有 keydownkeypresskeyup 三个事件,它们都继承了 KeyboardEvent 接口。

如果用户一直按键不松开,就会连续触发键盘事件,触发的顺序如下。

  1. keydown
  2. keypress
  3. keydown
  4. keypress
  5. ...(重复以上过程)
  6. keyup

KeyboardEvent 接口概述

KeyboardEvent 接口用来描述用户与键盘的互动。这个接口继承了 Event 接口,并且定义了自己的实例属性和实例方法。

KeyboardEvent()

浏览器原生提供 KeyboardEvent 构造函数,用来新建键盘事件的实例。

new KeyboardEvent(type, options)

KeyboardEvent 构造函数接受两个参数。第一个参数是字符串,表示事件类型;第二个参数是一个事件配置对象,该参数可选。除了 Event 接口提供的属性,还可以配置以下字段,它们都是可选。

KeyboardEvent 的实例属性

altKey,ctrlKey,metaKey,shiftKey

以下属性都是只读属性,返回一个布尔值,表示是否按下对应的键。

下面是一个示例

function showChar(e) {
  console.log('ALT: ' + e.altKey);
  console.log('CTRL: ' + e.ctrlKey);
  console.log('Meta: ' + e.metaKey);
  console.log('Shift: ' + e.shiftKey);
}

document.body.addEventListener('keydown', showChar, false);

KeyboardEvent.code

KeyboardEvent.code 属性返回一个字符串,表示当前按下的键的字符串形式。该属性只读。

下面是一些常用键的字符串形式,其他键请查 文档

KeyboardEvent.key

KeyboardEvent.key 属性返回一个字符串,表示按下的键名。该属性只读。

KeyboardEvent.location

KeyboardEvent.location 属性返回一个整数,表示按下的键处在键盘的哪一个区域。它可能取以下值。

KeyboardEvent.repeat

KeyboardEvent.repeat 返回一个布尔值,代表该键是否被按着不放,以便判断是否重复这个键,即浏览器会持续触发 keydownkeypress 事件,直到用户松开手为止。

KeyboardEvent.keyCode *

被废弃了

KeyboardEvent 的实例方法

KeyboardEvent.getModifierState()

KeyboardEvent.getModifierState() 方法返回一个布尔值,表示是否按下或激活指定的功能键。它的常用参数如下。

if (
  event.getModifierState('Control') +
  event.getModifierState('Alt') +
  event.getModifierState('Meta') > 1
) {
  return;
}

上面代码表示,只要 ControlAltMeta 里面,同时按下任意两个或两个以上的键就返回。

ProgressEvent 进度事件

进度事件的种类

进度事件用来描述资源加载的进度,主要由 AJAX 请求、<img><audio><video><style><link> 等外部资源的加载触发,继承了 ProgressEvent 接口。它主要包含以下几种事件。

注意,除了资源下载,文件上传也存在这些事件。

示例

下面是一个例子。

image.addEventListener('load', function (event) {
  image.classList.add('finished');
});

image.addEventListener('error', function (event) {
  image.style.display = 'none';
});

上面代码在图片元素加载完成后,为图片元素添加一个 finished 的 Class。如果加载失败,就把图片元素的样式设置为不显示。

有时候,图片加载会在脚本运行之前就完成,尤其是当脚本放置在网页底部的时候,因此有可能 loaderror 事件的监听函数根本不会执行。所以,比较可靠的方式,是用 complete 属性先判断一下是否加载完成。

function loaded() {
  // ...
}

if (image.complete) {
  loaded();
} else {
  image.addEventListener('load', loaded);
}

由于 DOM 的元素节点没有提供是否加载错误的属性,所以 error 事件的监听函数最好放在 <img> 元素的 HTML 代码中,这样才能保证发生加载错误时百分之百会执行。

<img src="/wrong/url" onerror="this.style.display='none';" />

loadend 事件的监听函数,可以用来取代 abort 事件、load 事件、error 事件的监听函数,因为它总是在这些事件之后发生。

req.addEventListener('loadend', loadEnd, false);

function loadEnd(e) {
  console.log('传输结束,成功失败未知');
}

loadend 事件本身不提供关于进度结束的原因,但可以用它来做所有加载结束场景都需要做的一些操作。

另外,error 事件有一个特殊的性质,就是不会冒泡。所以,子元素的 error 事件,不会触发父元素的 error 事件监听函数。

ProgressEvent 接口

ProgressEvent 接口主要用来描述外部资源加载的进度,比如 AJAX 加载、<img><video><style><link> 等外部资源加载。进度相关的事件都继承了这个接口。

ProgressEvent()

浏览器原生提供了 ProgressEvent() 构造函数,用来生成事件实例。

new ProgressEvent(type, options)

ProgressEvent() 构造函数接受两个参数。第一个参数是字符串,表示事件的类型,这个参数是必须的。第二个参数是一个配置对象,表示事件的属性,该参数可选。配置对象除了可以使用 Event 接口的配置属性,还可以使用下面的属性,所有这些属性都是可选的。

ProgressEvent 具有对应的实例属性。

如果 ProgressEvent.lengthComputablefalseProgressEvent.total 实际上是没有意义的。

示例

下面是一个例子。

var p = new ProgressEvent('load', {
  lengthComputable: true,
  loaded: 30,
  total: 100,
});

document.body.addEventListener('load', function (e) {
  console.log('已经加载:' + (e.loaded / e.total) * 100 + '%');
});

document.body.dispatchEvent(p);
// 已经加载:30%

上面代码先构造一个 load 事件,抛出后被监听函数捕捉到。

下面是一个实际的例子。

var xhr = new XMLHttpRequest();

xhr.addEventListener('progress', updateProgress, false);
xhr.addEventListener('load', transferComplete, false);
xhr.addEventListener('error', transferFailed, false);
xhr.addEventListener('abort', transferCanceled, false);

xhr.open();

function updateProgress(e) {
  if (e.lengthComputable) {
    var percentComplete = e.loaded / e.total;
  } else {
    console.log('不能计算进度');
  }
}

function transferComplete(e) {
  console.log('传输结束');
}

function transferFailed(evt) {
  console.log('传输过程中发生错误');
}

function transferCanceled(evt) {
  console.log('用户取消了传输');
}

上面是下载过程的进度事件,还存在上传过程的进度事件。这时所有监听函数都要放在 XMLHttpRequest.upload 对象上面。

var xhr = new XMLHttpRequest();

xhr.upload.addEventListener('progress', updateProgress, false);
xhr.upload.addEventListener('load', transferComplete, false);
xhr.upload.addEventListener('error', transferFailed, false);
xhr.upload.addEventListener('abort', transferCanceled, false);

xhr.open();

compositionstart

文本合成系统如 input method editor(即输入法编辑器)开始新的输入合成时会触发 compositionstart 事件。

例如,当用户使用拼音输入法开始输入汉字时,这个事件就会被触发。

此外还有 compositionupdate, 和 compositionend 事件

compositionstart - Web API 接口参考 | MDN

这几个事件, 兼容性非常好, 可以放心使用

和 Change Input 的区别是什么?

beforeInput

DOM 事件 beforeinput 在 <input><select> 或 <textarea> 的值即将被修改前触发。这个事件也可以在 contenteditable 被设置为 true 的元素和打开 designMode 后的任何元素上被触发。

In the case of contenteditable and designMode, the event target is the editing host. If these properties apply to multiple elements, the editing host is the nearest ancestor element whose parent isn't editable.

兼容性其实一般, 还是一个在实验状态的 api

相比于 keydown, 最主要的优势就是不需要判断是否是功能按键, 能在用户真正输入时才触发

Beforeunload

unload deprecated

beforeunload ok

unload 事件即将废弃

自定义事件

自定义事件有三种方法,一种是使用 new Event(), 另一种是 createEvent('CustomEvent') , 另一种是 new customEvent()

New Event()

let btn = document.querySelector('#btn');
let ev = new Event('alert', {
    bubbles: true,    //事件是否冒泡;默认值false
    cancelable: true, //事件能否被取消;默认值false
    composed: false
});
btn.addEventListener('alert', function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable); //true
    console.log(event.detail); //undefined
}, false);
btn.dispatchEvent(ev);

以及各个 Event 接口的构造函数

createEvent('CustomEvent')

要创建自定义事件,可以调用 createEvent('CustomEvent'),返回的对象有 initCustomEvent 方法,接受以下四个参数:

let btn = document.querySelector('#btn');
let ev = btn.createEvent('CustomEvent');
ev.initCustomEvent('alert', true, true, 'button');
btn.addEventListener('alert', function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable);//true
    console.log(event.detail); //button
}, false);
btn.dispatchEvent(ev);

customEvent()(DOM4)

使用起来比 createEvent('CustomEvent') 更加方便

var btn = document.querySelector('#btn');
/*
 * 第一个参数是事件类型
 * 第二个参数是一个对象
 */
var ev = new CustomEvent('alert', {
    bubbles: 'true',
    cancelable: 'true',
    detail: 'button'
});
btn.addEventListener('alert', function (event) {
    console.log(event.bubbles); //true
    console.log(event.cancelable);//true
    console.log(event.detail); //button
}, false);
btn.dispatchEvent(ev);

FAQ

#faq/js

Addeventlistener 多次绑定,只执行一次

方案一:每次执行完,再 handler 解绑自己

方案二:使用 once 属性

addEventListener This

事件监听, this 一定是 dom 本身, 如果是在 class 里面想要指定 this, 有如下实践:

        this.el.addEventListener('mouseup', this.handleMouseUp = () => {
            if (!this.dragging) {
                return;
            }

            this.dragging = false;
            this.activeSelection = null as any;
        });

		this.el.removeEventListener('mouseup', this.handleMouseUp);

本质上就是那个经典的 react 问题

监听与解除绑定

class 内部的事件监听与解除绑定, 因为箭头函数 this, 以及 eventListener 的 this 问题, 需要形成一个最佳实践.

export const EditorContent = san.defineComponent<EditorContentProps>({

    template: `
        <div class="editor-san" on-click="handleClick">
            <slot></slot>
        </div>`,

    attached() {
        this.handleEditor(this.data.get('editor'));

        this.watch('editor', this.handleEditor);
    },

    handleEditor(editor: Editor) {
        if (editor && editor.options.element) {
            this.handleValidate(editor);
        }
    },

    /**
     * 处理 santd form validate, 外部直接传入 decorator 即可
     * 仅在失焦时做校验, 避免频繁获取 html
     */
    handleValidate(editor: Editor) {

        this.validateListener = () => {
            this.dispatch('UI:form-item-interact', {
                fieldValue: editor.getHTML(),
                type: 'change'
            });
            console.log('editor.getHTML(): ', editor.getHTML());
        };

        editor.on('blur', this.validateListener);
    },

    detached() {
        editor.off('blur', this.validateListener);
    },
});

动态的改 this 的做法挺好的

什么情况下需要解?

取决于 dom 节点是否还被引用

If a DOM element which is removed is reference-free (no references pointing to it) then yes - the element itself is picked up by the garbage collector as well as any event handlers/listeners associated with it.

var a = document.createElement('div');
var b = document.createElement('p');
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b);
b = null; 
// A reference to 'b' no longer exists 
// Therefore the element and any event listeners attached to it are removed.

However; if there are references that still point to said element, the element and its event listeners are retained in memory.

var a = document.createElement('div');
var b = document.createElement('p'); 
// Add event listeners to b etc...
a.appendChild(b);
a.removeChild(b); 
// A reference to 'b' still exists 
// Therefore the element and any associated event listeners are still retained.