{JS} Events 原生事件机制Mon Sep 11 2017

Javascript Events 是DOM编程中较为核心的一环,在编写Ax框架以及早期实现和Z(jQuery Like)DOM库的阶段,接触了事件的绑定,解绑,事件代理,事件缓存。 其实这些都还比较好写,比较复杂的是浏览器实现的Event对象以及其扩展对象,例如(MouseEvent,UIEvent,CompositionEvent)等等,兼容性不一, API数量庞大,写入机制不同,浏览器的各自实现也有不同,这就是真正蛋疼的地方。

现代浏览器(IE9+)的事件机制都存在

事件捕获 -> 目标元素 -> 事件冒泡

而IE9以前的IE就只有冒泡的过程

目标元素 -> 事件冒泡


事件捕获

一般都按照了W3C标准来实现,#1(addEventListener),#2(removeEventListener), #3(dispatchEvent)

在早期,#1和#2的第三个参数(Captrue, Boolean , 是否在捕获阶段就触发)是必填的,后来改为了选填,默认为false,也就是不在捕获阶段就触发事件。其实很多人在使用#1,#2的时候都没有注意到, 捕获阶段的触发到底有什么用?

如果你不写事件代理(delegater),也许你就接触不到第三个参数,先来讲讲什么是事件代理


事件代理

例如以下层级关系

    body
        - div
        - div
        - a

想使得body中的每一个div,都有click点击事件, 是否需要给每一个div都绑定click事件呢?那么当后续往body中添加多个div的后,也要手动绑定click点击事件吗?

由此问题才有了事件代理这个概念,事件的代理指的是将点击事件挂载在body上,点击body内的div元素,点击事件会自动冒泡到body上,从而触发body的点击事件,但是这个事件的event.target 是指向了 div 本身(触发元素), 由此而来事情就变得简单了不少. 同时也极大的提高了性能.


那么问题来了,事件捕获参数 Captrue 到底有什么用?

其实,某些事件行为上是不存在冒泡的,或者是需要在捕获阶段就触发事件. 于是我在MDN每个事件类型都点进去看了一边,总结:

    "blur"       , "invalid"     ,
    "focusin"    , "focusout"    , "focus",
    "abort"      , "afterprint"  , "beforeprint" ,
    "checking"   , "downloading" ,
    "load"       , "unload"      ,
    "loadend"    , "loadstart"   ,
    "mouseenter" , "mouseleave"  ,
    "resize"     , "show"        , "select"

这些事件, 就是在事件代理时需要注意到的地方.


主动触发事件

JavaScript 中主动触发某个事件是通过 elm.dispatchEvent 完成的,低版本的ie 是 elm.fireEvent ,参数是传入一个实例化的对象,但是W3C对于事件的标准还是不太全, 本身很多是Event的扩展类型是浏览器自身实现的,MDN给出了几乎所有的扩展类型

Events MDN

每当我们需要主动触发某个事件的时候,首先我们需要找到对应的elm元素,然后给它构造一个 Event, 然后 elm.dispatchEvent(customEvent), 由于兼容性和实现的问题,通过new Event生成的代理实例是最可靠的,因为ie6也支持.

也可以根据对应的事件类型(EventType) 来使用不同的扩展类型Event来构造,例如copy事件, 就是用ClipboardEvent构造一个对象,传入dispatchEvent作为参数, 又比如click事件,我们就生成一个MouseEvent 实例.


*事件对象不可写的问题

很多时候,在某些特定的Event扩展对象实例上添加属性,是会报错的。 所以jQuery的Event 对象是自己封装的,而不是用的原生的事件对象。思路其实很简单:

当触发一个点击事件的时候,会给绑定函数传入一个event对象,在这个event对象的基础上进行包装. 步骤:

  • copy all event property -> myevent (注意某些属性)
  • myevent add [data] (添加特性的data属性)
  • myevent add [Symbol] pointer to event (这里的Symbol指的是任意属性)
  • create method mapping for event (创建映射方法间接调用event的原生方法)


event上只有3种方法是需要被映射的,preventDefault() , stopImmediatePropagation() , stopPropagation(), 这几个关键的方法直接关系到了事件的冒泡,事件的默认行为, 其他方法鲜少需要被调用,或者是将被废弃的方法

通过自身构造的模拟Event事件,可以随意写入自定义属性. 而不用担心浏览器的兼容问题(某些事件类型本身属性不可写) , 这里需要注意一个问题是,某些事件类型的属性是不能够被访问. copy时需要忽略这个属性.


通过 CompositionEvent 解决虚拟DOM渲染导致输入法无效的问题.

CompositionEvent

在编写diff render的时候也遇到了这样的问题, 给一个绑定了input事件的dom伙伴,触发视图渲染操作, 会导致输入法失效的问题, Google的很久才看到一片比较靠谱的文章,介绍了CompositionEvent 。 这个事件类型有三个阶段, start, update, end , 只需要用到start, end 就可以监听到浏览器的输入法行为. 从而对input事件进一步包装。

这个API的兼容性还是很不错的,原始ie6 就已经支持. 代码就不show了.