目录
3.8 妇女节,祝愿天下的妇女节日快乐。
事件
Js 与 HTML 之间的交互通过事件完成,事件,就是文档或浏览器窗口中发生的一些 特定的交互瞬间。事件类型有很多分类,例如:DOM 事件类型,焦点事件,滚轮事件等等,说到这里就不得不说一下 js 的事件流
事件冒泡
事件冒泡,事件开始从具体的节点出发,逐步向上冒泡到外层节点。例如:点击 id 为 a 的 div 时,事件会一步步向上传递 a=> b => c,当我们点击 click 的时候,会依次输出 123
<div id="c" onclick="console.log(3)">
<div id="b" onclick="console.log(2)">
<div id="a" onclick="console.log(1)">click</div>
</div>
</div>
事件捕获
虽然事件捕获是 Netscape 唯一支持的事件流模型,但 IE9、Safari、Chrome、Opera 和 Firefox 目前也都支持这种事件流模型。尽管“DOM2 级事件”规范要求事件应该从 document 对象开始传播,但这些浏览器都是从 window 对象开始捕获事件的。 由于老版本的浏览器不支持,因此很少有人使用事件捕获。
DOM 事件流
DOM 事件流分三个阶段,捕获阶段,目标阶段和冒泡阶段,DOM 事件流又分为 DOM0 级和 DOM2 级。 DOM 事件流
DOM0 级事件处理程序
比如我们常见的 click,load 等等,DOM0 级别事件被认定是元素的方法,所以其中 this 为元素本身,其作用域也是当前目标元素。
<div id="a" onclick="console.log(this.id)">click</div>
打印出来的 this 就是元素本身。清除 DOM0 级事件可以直接赋值为 null 就会清除掉。
target.onclick = null;
DOM2 级事件处理程序
DOM2 级事件提供了两个方法,即大名鼎鼎的 addEventListener 和 removeEventListener. 他们都提供了三个参数 1.事件名称 2.事件方法 3.事件是在捕获阶段执行,还是在冒泡阶段执行,类型为布尔值,true 在捕获阶段,false 在冒泡阶段,默认为 false
addEventListener
var target = document.getElementById("a");
target.addEventListener(
"click",
function () {
console.log(this.id);
},
false
);
DOM2 级事件,不会和 DOM0 级的冲突,所以在 click 的时候。会先打印 this,再打印 id,同时可以添加多个 click 事件,此时会先打印 id,在打印 hello world,执行顺序和添加顺序保持一致
var target = document.getElementById("a");
target.addEventListener(
"click",
function () {
console.log(this.id);
},
false
);
target.addEventListener(
"click",
function () {
console.log("hello world");
},
false
);
事件优先级:DOM0 级事件 > DOM2 一道简单的面试题,点击 c 依次输出 abc 和依次输出 cba
<div id="a">
a
<div id="b">
b
<div id="c">c</div>
</div>
</div>
removeEventListener
用于移除事件,移除事件的时候,必须的有两个参数,事件名称和处理函数。函数必须为是同一个函数,否则无法移除。
var target = document.getElementById("a");
function sayHello() {
console.log("hello world");
}
target.addEventListener("click", sayHello, false);
target.removeEventListener(
"click",
function () {
console.log("hello world");
},
false
);
// 无效不能移除
target.removeEventListener("click", sayHello, false);
// 有效
IE 实现了与 DOM 中类似的两个方法:attachEvent()和 detachEvent()。这两个方法接受相同 的两个参数:事件处理程序名称与事件处理程序函数。由于 IE8 及更早版本只支持事件冒泡,所以通过 attachEvent()添加的事件处理程序都会被添加到冒泡阶段。
鉴于此封装一个小型的事件处理方法,用来兼用跨平台:
var EventUtil = {
addHandler: function (element, type, handler, isBubble = false) {
if (element.addEventListener) {
element.addEventListener(type, handler, isBubble);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function (element, type, handler, isBubble = false) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, isBubble);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
},
};
事件对象
无论是 DOM0 还是 DOM2 在执行事件的时候,都会返回一个 Event 对象给我们。其中包含很多的属性,重点说下下面几个。
1.preventDefault,取消默认行为,例如 a 标签的跳转;相关的属性为 cacelable,可以通过 preventDefault 取消默认行为,cacelable 为 true,否则为 false
2.type 为出发该行为的事件是什么类型,例如 click 等
3.eventPhase 当前触发事件的阶段,是冒泡,捕获,还是目标阶段
4.stopPropagation 可以停止冒泡和捕获的继续传递
5.srcElement 和 target 一样,事件的目标对象
自定义事件
除了平时使用的原生事件,有时候需要自定义事件。创建自定义事件的方法有两种,Event() 构造函数 和 CustomEvent() 构造函数,还有个 createEvent()已经废弃,但是仍然有浏览器支持,所以我们暂且不说,有兴趣的可以自己看看。
Event()
支持两个参数: 1.typeArg 事件名称 2.eventInitOption 事件初始化配置,配置内容包含三个字段
参数 类型 说明 bubbles 布尔 表示该事件是否冒泡 cancelable 布尔 表示该事件能否被取消 composed 布尔 指示事件是否会在影子 DOM 根节点之外触发侦听器。 ex:
// 创建事件
const ev = new Event("testEvent");
// 监听事件
document.addEventListener("testEvent", function () {
console.log("testEvent执行了");
});
// 触发事件
document.dispatchEvent(ev);
CustomEvent()
支持两个参数: 1.typeArg 事件名称 2.eventInitOption 事件初始化配置,配置内容包含事件部分字段,详情可以查看 CustomEvent,额外还有一个 detail 字段可用于传递额外的参数
// 创建事件
const ev = new CustomEvent("testEvent", {
detail: {
extraParams: 1,
},
});
// 监听事件
document.addEventListener("testEvent", function (e) {
console.log("testEvent执行了", e.detail);
});
// 触发事件
document.dispatchEvent(ev);
事件代理
事件代理:就是将子元素的事件通过冒泡的形式交由父元素来执行。 常规应用:
<div id="idd">
<div>
<section>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
<div>1</div>
</section>
</div>
</div>
function findParent(child, parent) {
if (child.parentNode === parent) {
return true;
} else {
if (child.parentNode.nodeName.toLowerCase !== "html") {
findParent(child.parentNode, parent);
} else {
return false;
}
}
}
window.onload = function () {
const parent = document.getElementById("idd");
parent.onclick = function (e) {
let flag = findParent(e.target, parent);
if (e.target.nodeName.toLowerCase() === "div" && flag) {
console.log("div");
}
};
};
常见的事件中心
思考
一个事件中心应该包含哪些功能
· 发布
· 订阅
· 取消订阅
· 一次执行
基本上起码要包含以上几种方法
下面我们就来实现一下
// 第一步:新建一个类别
function E () {
}
// 原型
E.prototype = {
// on 方法
// e是一个对象,维护了事件的名称和cb
// e: {
'eventName': [{
ctx: _this1,
fn: () => {console.log(1)}
}, {
ctx: _this2,
fn: () => {console.log(3)}
}]
}
on: function (name, callback, ctx) {
var e = this.e || (this.e = {});
(e[name] || (e[name] = [])).push({
fn: callback,
ctx: ctx
});
return this;
},
// once 方法
// 创建一个listener,通过调用off,在执行一次就移除掉
once: function (name, callback, ctx) {
var self = this;
function listener () {
self.off(name, listener);
callback.apply(ctx, arguments);
};
listener._ = callback
return this.on(name, listener, ctx);
},
// emit方法
emit: function (name) {
// emit 参数
var data = [].slice.call(arguments, 1);
// 当前事件下面的所有cb的拷贝
var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
var i = 0;
var len = evtArr.length;
// 执行
for (i; i < len; i++) {
evtArr[i].fn.apply(evtArr[i].ctx, data);
}
return this;
},
// off 方法
off: function (name, callback) {
var e = this.e || (this.e = {});
// 某个事件 cb list
var evts = e[name];
var liveEvents = [];
// 如果存在 evts and cb
// 过滤出cb不一致的
// 这里可以用filter改造
// const liveEvents = evts.filter(item => item.fn != callback && item.fn._ != callback /* 用于once的取消订阅 */)
if (evts && callback) {
for (var i = 0, len = evts.length; i < len; i++) {
if (evts[i].fn !== callback && evts[i].fn._ !== callback)
liveEvents.push(evts[i]);
}
}
// 将过滤掉的重新赋值
// 或则直接删除掉eventName
(liveEvents.length)
? e[name] = liveEvents
: delete e[name];
return this;
}
};
module.exports = E;
module.exports.TinyEmitter = E;
以上的栗子,能够让我们减少对 DOM 的操作,提升性能,发现这段代码的事前消耗更低,因为只取得了一个 DOM 元素,只添加了一个事件处理程序。虽然对用户来说最终的结果相同,但这种技术需要占用的内存更少。所有用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。
最后,事件种类太多,使用的难易程度也不尽相同;在使用事件时,需要考虑如下一些内存与性能方面的问题。
1.有必要限制一个页面中事件处理程序的数量,数量太多会导致占用大量内存,而且也会让用户感觉页面反应不够灵敏。
- 建立在事件冒泡机制之上的事件委托技术,可以有效地减少事件处理程序的数量。
- 建议在浏览器卸载页面之前移除页面中的所有事件处理程序。
有错误的地方或者不足的地方欢迎指正。