1. 引言

在Web中,事件,通常是指某个动作的完成,比如,鼠标点击、鼠标移动、键盘按下等

事件监听,通常是指监听某个动作,动作完成后触发相应的变化,如,鼠标点击按钮后弹出确认框

通过事件监听,当某个事件完成或者状态改变时,可以触发监听者的更新或预设行为,比如,点击按钮弹出确认框、窗口大小改变时网页内容也改变大小等

在软件工程中,这种设计模式叫观察者模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新

2. 内置事件

在Web中,事件的主体(即,可以触发与监听事件的对象)通常是DOM对象,如,Button、Div等

2.1 事件监听

事件监听是主要的应用场景

HTML和JS已经内置了大部分会出现的事件,经典的事件如点击事件,通常Button可以这样监听:

<body>
  <button>按钮</button>
  <script>
    var btn = document.querySelector('button');
      
    btn.onclick = function() {
      console.log('click');
    }
  </script>
</body>

即设置on+<event name>属性的方式,设置对事件的监听

现在通常使用下面的方式实现监听:

<body>
  <button>按钮</button>
  <script>
    var btn = document.querySelector('button');

    btn.addEventListener('click', function() {
      console.log('click');
    });
    
  </script>
</body>

浏览器中实现的详细的事件,可以从MDN中查询:Event - Web API 接口参考 | MDN (mozilla.org)

2.2 事件触发

通常事件触发是行为触发的,比如,鼠标去点击按钮触发

有时需要通过代码去主动触发,如何实现代码主动触发事件呢

经典的事件,通常DOM对象内置了主动触发的方法,如点击事件:

<body>
  <button>按钮</button>
  <script>
    var btn = document.querySelector('button');

    btn.addEventListener('click', function() {
      console.log('click');
    });
      
    btn.click(); // 触发点击事件
    
  </script>
</body>

如果DOM对象没有内置了主动触发的方法,可以使用下面这种方式:

<body>
  <button>按钮</button>
  <script>
    var btn = document.querySelector('button');

    btn.addEventListener('click', function() {
      console.log('click');
    });

    btn.dispatchEvent(new Event('click')); // 触发click事件
    
  </script>
</body>

3. 自定义事件

3.1 DOM对象

上述事件是浏览器内置所支持的,加入浏览器没有支持或者想自定义事件呢,比如,想监听数据加载,加载完成后对应的DOM元素刷新

这时可以设置一个名为dataLoaded的自定义事件(Custom Event),将DOM元素设置为监听者,然后加载数据后触发事件:

<body>
  <div id="containter"></div>
  <script>
    const container = document.getElementById('containter');

    container.addEventListener('dataLoaded', (e) => {
      console.log(e.detail);
      container.innerHTML = `<h1>${e.detail.name}</h1><p>${e.detail.age}</p>`
    })

    function load() {
      // 模拟数据加载
      const data = {
        name: 'John',
        age: 20
      }
      const event = new CustomEvent('dataLoaded', {
        detail: data
      })
      container.dispatchEvent(event); // 触发dataLoaded事件
    }

    load();
  </script>
</body>

CustomEvent的详细介绍可以参考:CustomEvent() - Web API 接口参考 | MDN (mozilla.org)

上述代码中,看上去这个事件机制用法是多余的,完全可以在加载数据后执行相应的更新函数即可,何必再套一层事件监听与事件触发呢

倒也不然,如下面这个例子,有三个文件,分别是index.htmlindex.jsrequest.js

index.html:

<body>
  <div id="containter"></div>
  <script>
    const container = document.getElementById('containter');

    container.addEventListener('dataLoaded', (e) => {
      console.log(e.detail);
      container.innerHTML = `<h1>${e.detail.name}</h1><p>${e.detail.age}</p>`
    })

  </script>
</body>

index.js:

const container = document.getElementById('containter');

container.addEventListener('dataLoaded', (e) => {
   doSomething()
})

function doSomething(){
    // ...
}

request.js:

function load() {
    // 模拟数据加载
    const data = {
        name: 'John',
        age: 20
    }
    const event = new CustomEvent('dataLoaded', {
        detail: data
    })
    container.dispatchEvent(event);
}

load();

上述示例中,在数据加载后,其余各处会执行相应的函数,代码组织也更为易懂

3.2 JS对象

上面所述的均为DOM对象,JS中的对象能否监听事件呢

答曰,可以,需要继承EventTarget

EventTarget详细的API介绍可以参考MDN:EventTarget - Web API 接口参考 | MDN (mozilla.org)

下面是一个示例代码,展示了如何编写一个类,继承自EventTarget,并实现事件监听功能:

class MyObject extends EventTarget {
  myMethod() {
    // 在方法内部触发事件
    this.dispatchEvent(new Event('myEvent'));
  }
}

// 实例化对象
const obj = new MyObject();

// 添加事件监听器
obj.addEventListener('myEvent', myEventHandler);

// 事件处理函数
function myEventHandler(event) {
  // 处理事件的逻辑
  console.log('Event triggered:', event);
}

// 调用方法,触发事件
obj.myMethod();

4 .参考资料

[1] 事件参考 | MDN (mozilla.org)

[2] CustomEvent - Web API 接口参考 | MDN (mozilla.org)

[3] EventTarget - Web API 接口参考 | MDN (mozilla.org)