事件是JavaScript和浏览器交互的主要途径。事件是一种叫做观察者的设计模式,这是一种创建松散耦合代码的技术。 观察者模式:由两类对象组成,主体和观察者,主体负责发布事件,观察者通过订阅这些事件来观察主体。该模式的一个重要概念是主体并不知道观察者的任何事情,也就是说即使观察者不存在,主体也可以独自存在并正常运作。而观察者知道主体,并能注册事件的回调函数。
其实,“订阅”就是把处理程序交给“主体”;“发布事件”就是主体“调用”处理程序(上文的观察者)。
比如,主体 class A中有一个function fireEvent(){},处理程序是function handle1(){},则“发布事件”就是:
class A {
fireEvent() {
handle1()
}
}
“订阅”就是通过addHandles将处理程序handle1交给主体:
class A {
constructor() {
this.handles = {}
}
addHandles(handle) {
if (this.handles && this.handles.length) {
this.handles.push(handle)
}
}
fireEvent() {
// 循环调用,所以DOM.addEventListener对于一个click可以添加多个处理程序
for(let handle of this.handles) {
handle()
}
}
}
还需要有个解除处理程序function removeHandle(handle) {//略}
所以一个主体(自定义事件),至少要有如上三个function。
当然,事件是有不同类型的,所以“订阅”addHandles可以改为:
addHandles(type, handle) {
if (!(this.handles[type] && this.handles[type].length)) {
this.handles[type] = []
}
this.handles[type].push(handle)
}
而“发布事件”为:
fireEvent(type) {
if (this.handles && this.handles[type] && this.handles[type].length) {
for(let handle of this.handles[type]) {
handle()
}
}
}
通过继承来使用它:
class MyDom extends A {}
let fakeDom = new MyDom()
fakeDom.addHandles('myclick', function() {
console.log('watching myclick')
})
当某种情况下触发了“myclick”时,比如在class B的某个函数中:
function responseToEvent_MYCLICK() {
fakeDom.fireEvent('myclick')
}
自定义事件没有什么实用意义,但对理解浏览器运行机制有很大帮助。
所以额外的,想从底层机器运行到上层Javascript编程的视角描述整个过程:
- 我们改下名字:A=>EventTarget(浏览器事件对象),B=>Window(浏览器窗口对象)
然后是对MyDom的修改:(以下代码仅示意,并非真实实现)class MyDom extends EventTarget { constructor() { super() // 当创建DOM元素时,该元素便被注册进Window中了 // 触发事件时window就可以找到该实例,调用fireEvent了 Window.addTarget(this) } }
- 首先,鼠标单击行为产生信号脉冲,USB口接收信号后,将发送中断信号给CPU,CPU将信号通知给相应进程,此时是浏览器的进程。
- 其中Browser进程负责监听和计算各个事件的触发和触发相关的元素,它将接收CPU的信号,通知Render进程对相关元素进行修改,Render进程执行一系列程序,其中便运行到Window的responseToEvent_MYCLICK。
- 于是,之前通过window.addTarget注册的各元素的fireEvent就可以被调用了,即:
function responseToEvent_MYCLICK() { this.doms.map(dom => { // fireEvent内部判空了,不用在乎dom有没有监听myclick dom.fireEvent('myclick') }) }注意,这里使用的是“通知”而非“调用”,是因为进程间是通过共有一内存来互相“调用”的。