前言

在 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 实现原理

  1. 定义一个 EventEmitter 构造函数,初始化一个事件集合 events
  2. 通过不同的 type 来区分不同的事件类型,不同的对象同一个 type 可以有多个事件。
  3. EventEmitter 原型(EventEmitter.prototype )上添加 on 方法来注册事件。
  4. EventEmitter 原型(EventEmitter.prototype )上添加 emit 方法来触发注册事件。
  5. EventEmitter 原型(EventEmitter.prototype )上添加 off 方法来注销事件。

EventEmitter 使用流程

  1. 在需要监听的地方,通过 new EventEmitter 获取实例对象。
  2. 调用该实例对象的 on 方法注册回调事件。
  3. 在需要使用的地方,调用该对象上的 emit 方法触发事件。
  4. 当不需要使用该监听时,调用该对象上的 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;
}();
Logo

前往低代码交流专区

更多推荐