事件与事件流

1713 12 0 技术 2020-12-29

关于JS事件的起源可以追溯至现代浏览器最初形成,当时用于分担服务器运算的负载,将部分的计算和交互下放到用户浏览器端。

事件流

假设我们点击了两个嵌套元素的子元素,表面上你是点击了这个子元素,但实际上子元素、父元素、整个页面都被点击了,那浏览器是怎么知道是需要响应子元素的,还是父元素的,甚至是整个页面文档的点击事件?所以事件流的概念就被提出来了。

IE和Netscape一开始提出相反的事件流概念,IE是冒泡流,Netscape是捕获流。

现在普遍说的事件流就是“DOM2级事件”规范,从一个事件发生开始,浏览器会从文档开始往下逐级捕获,直至捕获到发生事件的那个目标元素,然后开始往上冒泡到文档的这个过程。

所以事件流有三个阶段,事件处理会被当成冒泡阶段的一部分。

捕获阶段 -> 目标阶段 -> 冒泡阶段

事件流

但是现代浏览器实现上默认都是从 window 开始的

事件绑定

开始的时候直接在HTML内绑定事件

// 绑定动态创建的事件处理函数
<input type="button" value="Click Me" onclick="alert('Clicked')" />

// 绑定定义好的事件处理函数
<input type="button" value="Click Me" onclick="clickHandler" />

动态创建适合简单的处理程序,稍微复杂的造成两种不同代码混合,而且会容易遇到单引号和双引号的嵌套混乱问题,可读性极低;用来做输入框的各种正则限制等很实用例如 oninput="value=value.replace(/[^\d]/g,'')"

这种方式还通过下面的with方案扩展了作用域,最终执行的代码可以直接读取自身或表单作用域的变量。

function() {
    with (document) {
        with (this.form) {
            with (this) {
                //...
            }
        }
    }
}

后者绑定一个已经定义好的函数方式则过于耦合,修改一个函数名就需要改动两处地方。而且假设在html渲染之后、事件处理函数创建之前触发该事件会造成浏览器报错。

事件规范

DOM0规范:当事件开始流行,各家浏览器用了相似但是不相同的方法绑定事件,这种不约而同的规范延续至今。也因为未形成标准所以习惯上被称为0级。

// 绑定
var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
    alert("Clicked"); 
    alert(this.value); 
};

// 解绑
btn.onclick = null;

因为这种绑定方式是直接赋值元素的onclick方法,所以函数内this会指向该元素。

DOM1规范:W3C尝试将各家浏览器的不约而同以及各取优点制定成标准,因为该标准没有完全被遵循,所以一般被忽略。

DOM2规范:使用addEventListener() 和 removeEventListener()绑定和解绑事件,与之前的方式相比可以添加多个事件处理函数,普通浏览器添加多个事件处理函数时会按照添加顺序从先到后触发。

addEventListener(type, listener[, useCapture])

三个参数分别是

  • type [String]:事件类型
  • listener [Function]:事件处理函数
  • useCapture [Boolean]:事件在是否在捕获阶段发生

DOM的最新规范中 将useCapture修改成可传一个对象,对象里面可传下面的参数,默认全为false

  • capture:事件是否在捕获阶段发生
  • once:事件是否仅执行一次
  • passive:是否让preventDefault函数失效

这个规范最重要的是passive,在以前的触摸事件中,当在某个元素内部触摸时候,浏览器无法知道是否要使用默认事件即是否执行了preventDefault(),所以会等待你触摸事件处理程序完成后才知道是否要滚动页面,造成卡顿感。 假设passive:true,则不阻止默认事件,在滚动的时候浏览器会直接滚动页面。

因为同个事件支持了多个处理程序,所以解绑的时候必须解绑参数与绑定参数完成相同该处理程序才能被解绑。

var btn = document.getElementById("myBtn");
var handler = function () {
    alert(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 完全一致

IE没遵循标准,使用了attachEvent()和 detachEvent()绑定和解绑事件。解绑也需要完全相同的参数。

attachEvent(type, listener)

attachEvent()的第一个参数事件类型与DOM0级方法一样,需要前缀on;两种绑定不一样的是该方法的事件处理函数会运行在全局作用域,this指向window。

添加多个事件处理函数时会按照添加顺序相反即从后到先触发。

浏览器事件兼容可参考下面代码

function addHandler(element, type, handler) {
    if (element.addEventListener) {
        // 现代浏览器
        element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {
        // IE浏览器
        element.attachEvent("on" + type, handler);
    } else {
        // 古代浏览器
        element["on" + type] = handler;
    }
}

DOM3规范:在DOM2级事件的基础上添加了更多的事件类型,同时允许自定义一些事件,全部类型如下

  • UI事件,当用户与页面上的元素交互时触发,如:load、scroll
  • 焦点事件,当元素获得或失去焦点时触发,如:blur、focus
  • 鼠标事件,当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
  • 滚轮事件,当使用鼠标滚轮或类似设备时触发,如:mousewheel
  • 文本事件,当在文档中输入文本时触发,如:textInput
  • 键盘事件,当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
  • 合成事件,当为IME(输入法编辑器)输入字符时触发,如:compositionstart
  • 变动事件,当底层DOM结构发生变化时触发,如:DOMsubtreeModified
© 2020 peal.cc 粤ICP备2020133024号