【JavaScript 事件】站在巨人身上俯看_js事件(详细笔记)

182 阅读12分钟

事件

JavaScript与HTML之间的交互是通过事件实现的

一、事件流

事件流描述的是从页面接收事件的顺序()

  • IE提出的是:事件冒泡流
  • Netscape Communicator提出的是:事件捕获流

1、事件冒泡

事件开始从最具体的元素接收,然后逐级向上传播到较为不具体的节点

比如:单击了一个div,事件传播顺序:

  1. div
  2. body
  3. html
  4. document

IE5.5 以前事件冒泡会跳过html元素,从body直接跳到document。 IE9/Firefox/Chrome/Safari则将事件一直冒泡到window对象

2、事件捕获

最具体的元素最后接收到事件

比如:单击了一个div,事件传播顺序:

  1. document
  2. html
  3. body
  4. div

3、事件流

DOM2级事件 规定事件流包括三个阶段:

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

IE8以前的版本不支持DOM事件流

二、事件处理程序

事件就是用户或浏览器自身执行的某种动作,而响应某个事件的函数就叫做事件处理程序

1、HTML事件处理程序

<div onclick="alert('click me!')">Click Me</div>

2、DOM0级事件处理程序

let app = document.querySelector('#app')
app.onclick = function() {
  alert(this.id); // app
}

将事件处理函数赋值为null,即删除这种方式指定的事件处理程序

3、DOM2级事件处理程序

DOM2级事件 定义了两个方法:

  • addEventListener():处理指定的事件处理程序
  • removeEventListener():删除事件处理程序
    • 参数:
      • 第一个参数:事件名
      • 第二个参数:事件处理程序函数
      • 第三个参数:布尔值;true表示在捕获阶段调用事件处理程序,false表示在冒泡阶段调用时机处理程序
let app = document.querySelector('#app')
app.addEventListener('click', function() {
  alert(this.id); // app
}, false)

为了最大限度的兼容各种浏览器,我们不推荐在事件捕获阶段注册事件处理程序。通常都是事件处理程序添加到事件流的冒泡阶段

4、IE事件处理程序

IE实现了与DOM类型的两个方法:

  • attachEvent()
  • detachEvent()
    • 参数:
      • 第一个参数:事件名
      • 第二个参数:事件处理程序函数

因为IE8以前只支持冒泡,那么他添加的事件处理程序默认都是添加到冒泡阶段

let app = document.querySelector('#app')
app.attachEvent('onclick', function() {
  alert(this === window); // true
}, false)

注意:这里是onclick,而不是DOM中的click

在IE中使用attachEvent方法与使用DOM0级方法主要区别在于事件处理程序的作用域。

  • 在使用DOM0级方法,事件处理程序会在其所属的元素作用域中运行
  • 使用attachEvent方法,事件处理程序会在全局作用域中运行,this等于window

并且attachEvent可以绑定多个不同的事件处理程序,但是执行顺序是反过来的,而不是添加他们的顺序执行。

支持IE事件处理程序的浏览器有IE和Opera

5、 跨浏览器的事件处理程序

没有考虑到所有浏览器问题。例如在IE中的作用域问题。不过,是用他们添加移除事件处理程序足够了

let app = document.querySelector('#app')

let EventUtil = {
  // 分别使用DOM0级方法、DOM2级方法、IE方法来添加事件
  addHandler: function(el, type, handler) {
    if(el.addEventListener) {
      el.addEventListener(type, handler, false)
    }else if(el.attachEvent) {
      el.attachEvent('on' + type, handler)
    }else { // DOM0级方法
      el['on' + type] = handler
    }
  },
  removeHandle: function(el, type, handler) {
    if(el.removeEventListener) {
      el.removeEventListener(type, handler, false)
    }else if(el.detachEvent) {
      el.detachEvent('on' + type, handler)
    }else {
      el['on' + type] = null
    }
  }
}

let handler = function() {
  alert('clicked')
}
EventUtil.addHandler(app, 'click', handler);
// EventUtil.removeHandle(app, 'click', handler);

三、跨浏览器的事件对象

let EventUtil = {
  addHandler: function() {},
  removeHandle: function() {},

  // 在IE中,event对象在window下
  getEvent: function(event) {
    return event ? event : window.event;
  },

  // 返回事件的目标
  getTarget: function(event) {
    return event.target || event.srcElement;
  },

  // 取消事件的默认行为
  preventDefault: function(event) {
    if(event.preventDefault) {
      event.preventDefault();
    }else {
      event.returnValue = false;
    }
  },

  // 阻止事件流
  stopPropagation: function(event) {
    if(event.stopPropagation) {
      event.stopPropagation();
    }else {
      event.cancelBubble = true;
    }
  }
}

四、事件类型

  • UI事件
  • 焦点事件
  • 鼠标事件
  • 滚轮事件
  • 文本事件
  • 键盘事件
  • 合成事件
  • 变动事件

1、UI事件

UI事件事件指的是那些不一定与用户操作有关的事件

现有的UI事件如下:

  1. load:当页面完全加载后在window上触发
  2. unload:当页面完全卸载后在window上触发
  3. abort:在用户停止下载过程时,如果嵌入的内容没有加载完,则在object元素上触发
  4. error:当发生在JavaScript错误时在window上触发...
  5. select:当用户选择文本框中的一或多个字段时触发
  6. resize:当窗口或框架的大小变化时在window上触发
  7. scroll:当用户滚动到滚动条的元素中的内容时触发

这些事件在DOM2级事件中都归为:HTML事件。

要确定浏览器是否支持DOM2级事件,可以使用:document.implementation.hasFeature('HTMLEvents', '2.0')

要确定浏览器是否支持DOM3级事件,可以使用:document.implementation.hasFeature('UIEvent', '3.0')

load事件

有两定义onload事件处理程序的方式:

  1. 使用JS代码
window.addEventListener('load', function(e){})
  1. 为body元素添加onlaod属性
<body onload='alert("loaded!")'>

unload事件

与load事件对应的是unload事件,在文档被完全卸载时触发。比如用户从一个页面切换到另一个页面,就会发生unload事件 最多的情况是清除引用,以避免内存泄漏

  1. 使用JS代码
window.addEventListener('unload', function(e){})
  1. 为body元素添加onlaod属性
<body onunload='alert("loaded!")'>

resize事件

浏览器窗口变化时触发

  1. 使用JS代码
window.addEventListener('resize', function(e){})

scroll事件

虽然scroll事件是在window对象上发生的,但他实际表示的则是页面中相应元素的变化

可以通过body元素的scrollLeft和scrollTop来监控

window.addEventListener('scroll', function(e){
  if(document.compatMode === 'CSS1Compat') {
    console.log(document.documentElement.scrollTop)
  }else {
    console.log(document.body.scrollTop)
  }
})

2、焦点事件

会在页面获得或失去焦点时触发

利用这些事件与document.hasFocus()方法及document.activeElement属性配合。

  • blur:在元素失去焦点时触发,不会冒泡
  • DOMFocusIn:元素获得焦点时触发,不会冒泡
  • DOMFocusOut:元素失去焦点时触发(DOM3级事件中废弃,选择了focusout)
  • focus:在元素获得焦点时触发,不会冒泡
  • focusin:在元素获得焦点时触发
  • focusout:在元素失去焦点时触发

3、鼠标与滚轮事件

鼠标事件:

  • click

  • dblclick:双击鼠标

  • mousedown:用户按下鼠标触发

  • mouseup:用户释放鼠标时触发

  • mouseenter:在用户把鼠标光标从元素外部移到元素内部时触发。(不冒泡)

  • mouseleave:在用户把鼠标光标从元素内部移到元素外部时触发。(不冒泡)

  • mousemove:在鼠标光标在元素上移动时反复触发

  • mouseout:在用户把鼠标光标从一个元素移到另一个元素上触发

  • mouseover:在用户把鼠标光标从元素外部移动到元素内部时触发

鼠标事件在DOM3 Events中对应的类型是MouseEvent

滚轮事件:

  • mousewheel

1、客户端坐标

  • clientX
  • clientY

2、页面坐标

  • pageX
  • pageY

在页面没有滚动时,页面坐标和客户端坐标相同

3、屏幕坐标

  • screenX
  • screenY

4、修饰键

  • shiftKye
  • ctrlKey
  • altKey
  • metaKey

5、鼠标按键

event对象上有button属性:

  • 0:鼠标主键
  • 1:鼠标滚轮
  • 2:鼠标右键

IE8及更早版本提出了button属性,与前面说的完全不同

6、额外事件信息

event对象上提供了detail属性: 包含一个数值,单击一次增加一。

IE还为每个鼠标事件提供了以下额外信息:

  • altLeft:左alt键是否按下
  • ctrlLeft
  • offsetX:光标相对于目标元素边界的X坐标
  • offsetY
  • shiftLeft

7、mousewheel事件

每次都是滚动120

window.addEventListener('mousewheel', (e) => {
  console.log(e.wheelDelta);
})

8、触摸屏设备

iOS和Android不支持鼠标操作

  • 不支持dblclick
  • 单指点触屏幕的可点击元素(有默认动作的元素)会触发mousemove
    • 如果操作会引起内容变化,则不在触发其他事件
    • 如果没有引起内容变化,则会相继触发mousedown、mouseup和click事件
  • mousemove事件也会触发mouseover和mouseout
  • 双指触点屏幕并滑动导致页面滚动时会触发mousewheel和scroll事件

4、键盘与输入事件

  • keydown:用户按下键盘上的某一个键触发,而且持续按住会重复触发
  • keypress:当用户按下键盘上的字符键时触发。DOM3废弃,而推荐textInput事件
  • keyup:用户释放键盘的键时触发

1、键码

keyup事件中的event对象的keyCode属性对应键盘的键。

例如数字键7的keyCode值为55

2、字符编码

要想以跨浏览器的方式取得字符编码,必须首先检测charCode属性是否可用,不可用则使用keyCode

getCharCode: function(event) {
  if(typeof event.charCode == 'number') {
    return event.charCode
  }else {
    return event.keyCode
  }
}

取得了字符编码后,就可以使用String.fromCharCode()将其转换成实际的字符

3、DOM3变化

键盘事件中,不在包含charCode属性,而是包含两个新属性:keychar

等等等还有一些变化...

4、textInput事件

只有在编辑区域中输入字符,才触发这个事件(删除字符不会触发)

他的event对象中有data属性,他的值就是输入的字符

let input = document.querySelector('input')
input.addEventListener('textInput', (e) => {
  console.log(e.data);
})

5、复合事件(合成事件)

用于处理IME输入时的复杂输入序列

  • compositionstart
  • compositionupdate
  • compositionend

6、变动事件(变化事件)

DOM2的变化事件,为了在DOM变化时提供通知。 注意:这些事件已经被废弃~

7、HTML5事件

1、contextmenu事件

自定义上下文菜单事件

2、beforeunload事件

在页面卸载前阻止这一操作(这个事件会在浏览器卸载页面之前触发)

3、DOMContentLoaded事件

用于添加事件处理程序或执行其他DOM操作(在load事件之前触发)

DOMContentLoaded事件会在DOM树构建完毕后立即触发,不用等待图片、JS文件、CSS文件或其他文件加载完成。

对于不支持DOMContentLoaded事件的,可以使用超时为0的setTimeout函数来设置。

4、readystatechange事件

提供文档或元素加载状态的信息

支持该事件的每个对象身上都有一个readyState属性,该属性具有以下可能出行的字符串值:

  • uninitialized:对象存在并尚未初始化
  • loading:对象正在加载数据
  • loaded:对象已经加载完数据
  • interactive:对象可以交互,但尚未加载完成
  • complete:对象加载完成

5、pageshow和pagehide事件

firefox 和 opera开发了一个名为往返缓存功能

往返缓存:在使用浏览器前进和后退时加快页面之间的切换。不仅存储页面数据,页存储DOM和JS状态。如果页面存在缓存,那么就不会触发load事件

  • pageshow
    • event.persisted:、
      • 默认为false
      • true:表示页面在往返缓存中加载的
  • pagehide
    • event.persisted
      • 默认为true:页面在卸载之后被保存在往返缓存中
      • true

6、hashchange事件

用于在URL散列值(URL最后#后边的部分)发生变化时,通知开发者

该事件必须添加给window,他有两个属性:

  • oldURL:变化前的URL
  • newURL:变化后的URL

想确定当前的散列值,最好用location对象:location.hash

8、设备事件

针对手机、平板计算机

1、orientationchange事件

苹果公司在移动Safari浏览器上创建了orientationchange事件

判断用户的设备是处于垂直模式还是水平模式

window上暴露了window.orientation属性,他有三个值:

  • 0:垂直模式
  • 90:左转水平模式
  • -90:右转水平模式

2、deviceorientation事件

是DeviceOrientationEvent规范定义的事件

获得设置的加速计信息,只反映了设备在空间中的朝向,不涉及移动相关的信息

event有五个属性:

  • alpha
  • beta
  • gamma
  • absolute
  • compassCalibrated

3、devicemotion事件

是DeviceOrientationEvent规范定义的事件

用于提示设备实际上在移动,而不是仅仅改变了朝向

event有以下属性:

  • acceleration:对象,包含xyz属性,在不考虑重力情况下各个维度的加速信息
  • accelerationIncludingGravity:对象,包含xyz属性,反映各个维度的加速信息,包含z轴自然重力加速度
  • interval:毫秒,距离下次触发devicemotion事件的时间。
  • rotationRate:对象包含alpha、beta、gamma属性,表示设备朝向

注意:如果无法提供该属性信息,则他们为null

9、触摸及手势事件

1、触摸事件

触摸事件有以下几种:

  1. touchstart:手指放到屏幕上时触发
  2. touchmove:手指在屏幕上滑动时连续触发
  3. touchend:手指离开屏幕时触发
  4. touchcancel:系统停止跟踪触摸时触发

以上事件都会冒泡

触摸事件还提供了以下属性用于跟踪触点:

  • touches:Touch对象的数组,表示当前屏幕上的每个触点
  • targetTouches:Touch对象的数组,表示特定于事件目标的触点
  • changedTouches:Touch对象数组,表示自上次用户动作之后变化的触点

每个Touch对象都包含以下属性:

  • clientX
  • clientY
  • identifier:触点ID
  • pageX
  • pageY
  • screenX
  • screenY
  • target:触摸事件的事件目标

2、手势事件

iOS2.0中的Safari还增加了一种手势事件。

  • gesturestart
  • gesturechange
  • gestureend

五、内存与性能

1、事件委托

原本在元素自身上绑定事件,利用事件冒泡特性可以给他的父元素绑定事件,最终都会由这个函数来处理。

优点:

  1. document对象随时可用,任何时候都可以给他添加事件处理程序
  2. 节省花在设置页面事件处理程序上的时间。只指定一个时间处理程序可以节省DOM引用,节省时间
  3. 减少整个页面所需的内存,提示整体性能

最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown、keypress、mouseover、mouseout

2、删除事件处理程序

事件处理程序越多,页面性能就越差。

btn.onclick = null // 删除事件处理程序

注意:onload事件处理程序中做了什么,最好在unonload事件处理程序中恢复

六、总结

优化内存与性能问题的建议:

  • 利用事件冒泡和事件委托来限制事件处理程序数量的问题
  • 最好在页面卸载之前删除所有的事件处理程序
  • 最好限制一个页面中事件处理程序的数量,他们会占用过多内存,导致页面响应缓慢