JavaScript 事件派发器 EventEmitter
随着前端业务越来越复杂,为了更好地维护和拓展系统,我们需要将视图、逻辑和数据分层管理。像经典的 MVC 模型、MVP 模型及 MVVP 模型就是一种系统分层管理理念。而像前端常用框架 React 和 Vue 就是 MVVP 模型的最佳实践。
·
JavaScript 事件派发器 EventEmitter
前言
在 JavaScript 中有很多与用户交互的事件,比如 click
点击事件、scroll
滑动事件、keypress
键盘事件等等。这些事件都是绑定在 DOM 上的。在视图、逻辑和数据没有分离的时代,我们可以直接在事件的回调中去操作 DOM。
比如要实现 “点击【新增】按钮,页面数据更新” 的功能。传统的写法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="initial-scale=1">
<title>React App</title>
</head>
<body>
<button onclick="add()">新增</button>
<div id="content">测试</div>
<script src="EventEmitter.js"></script>
<script>
function add() {
var content = document.getElementById("content");
content.innerHTML = "开发";
}
</script>
</body>
</html>
但是随着前端业务越来越复杂,为了更好地维护和拓展系统,我们需要将视图、逻辑和数据分层管理。像经典的 MVC 模型、MVP 模型及 MVVP 模型就是一种系统分层管理理念。而像前端常用框架 React 和 Vue 就是 MVVP 模型的最佳实践。
视图和数据分离之后,数据改变引发视图改变,这个动作就会分离成 2 个动作。视图层负责添加与页面变动相关的监听事件,而数据层一旦有数据改变就触发监听。这种将两者的逻辑关系联动起来的机制就是 EventEmitter
。它是一种很典型的观察者模式。
EventEmitter 实现原理
- 定义一个
EventEmitter
构造函数,初始化一个事件集合events
。 - 通过不同的
type
来区分不同的事件类型,不同的对象同一个type
可以有多个事件。 - 在
EventEmitter
原型(EventEmitter.prototype
)上添加on
方法来注册事件。 - 在
EventEmitter
原型(EventEmitter.prototype
)上添加emit
方法来触发注册事件。 - 在
EventEmitter
原型(EventEmitter.prototype
)上添加off
方法来注销事件。
EventEmitter 使用流程
- 在需要监听的地方,通过
new EventEmitter
获取实例对象。 - 调用该实例对象的
on
方法注册回调事件。 - 在需要使用的地方,调用该对象上的
emit
方法触发事件。 - 当不需要使用该监听时,调用该对象上的
off
方法释放事件。
//获取实例对象
var event = new EventEmitter;
//添加监听事件
event.on('change',test);
//需要执行的时候就触发事件
setTimeout(()=>{
event.emit('change',"测试监听,1秒钟之后执行");
event.off('change');
},1000);
function test(a) {
console.log(a);
}
EventEmitter 实践案例
以下通过 MVC 模型来实现 “点击【新增】按钮,页面数据更新” 的功能。关于 MVC 模型可以参考文章 MVC 模型、MVP模型 和 MVVM 模型介绍(附案例说明)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="initial-scale=1">
<title>React App</title>
</head>
<body>
<button id="btn">新增</button>
<div id="content"></div>
<script src="EventEmitter.js"></script>
<script>
function init() {
var model = new Model();
var controller = new Controller(model);
var view = new View(controller,model);
view.build();
view.listen();
};
//view 视图层
function View(controller,model) {
this.model = model;
var btn = document.getElementById("btn");
btn.onclick = function () {
controller.change("开发");
}
};
View.prototype.build = function () {
var text = this.model.getText();
changeText(text);
};
View.prototype.listen = function () {
//注册视图变动的事件
this.model.event.on("change",changeText);
};
//model 数据层
function Model() {
//获取 EventEmitter 对象实例。
var event = new EventEmitter;
this.event = event;
this.initText = "测试";
};
Model.prototype.getText = function () {
return this.initText;
};
Model.prototype.setText = function (text) {
this.initText = text;
//数据一旦发生改变,就触发视图监听事件改变视图。
this.event.emit("change",text);
};
//control 控制层
function Controller(model) {
this.model = model;
};
Controller.prototype.change = function (text) {
this.model.setText(text);
};
function changeText(text){
var content = document.getElementById("content");
content.innerHTML = text;
}
init();
</script>
</body>
</html>
EventEmitter.js 源码
内容如下:
!function () {
function EventEmitter() {
this.events = {};
this.maxListeners = 10;
}
EventEmitter.prototype.on = function () {
var type = arguments[0];
var handler = arguments[1];
if(!this.events[type]){
this.events[type]=[];
}
this.events[type].push(handler);
};
EventEmitter.prototype.off = function () {
var type = arguments[0];
if(this.events[type]){
this.events[type] = null;
}
};
EventEmitter.prototype.emit = function () {
if (!this.events) {
new Error("没有 events 对象")
return false;
}
var type = arguments[0];
var handler = this.events[type];
if(handler){
if(handler.length>10){
new Error("超过了最大监听数,请注销一些不再使用的监听事件,或者通过 setMaxListeners 重新设置最大监听数");
return;
}
var al = arguments.length;
var args = new Array(al - 1);
for (let j = 1; j < al; j++) args[j - 1] = arguments[j];
handler.forEach((fn)=>{
fn.apply(this, args);
});
}
};
EventEmitter.prototype.getMaxListeners = function() {
return this._maxListeners;
};
EventEmitter.prototype.setMaxListeners = function (n) {
if (typeof n !== 'number' || n < 0 || Number.isNaN(n)) {
throw TypeError('参数必须为非负数');
return;
}
this.maxListeners = n;
}
var _global = new Function('','return this')();
_global.EventEmitter = EventEmitter;
}();
更多推荐
已为社区贡献12条内容
所有评论(0)