关于事件你需要知道一些事情

3/8/2021 2223 阅读需要11分钟
0
0
AI总结
3.8妇女节,祝愿天下的妇女节日快乐。JavaScript与HTML的交互通过事件完成,事件分为DOM事件、焦点事件、滚轮事件等。事件流包括事件冒泡和事件捕获,DOM事件流分为捕获、目标和冒泡三个阶段。DOM0级事件处理程序通过元素的方法绑定事件,DOM2级事件处理程序使用`addEventListener`和`removeEventListener`方法,支持捕获和冒泡阶段。事件对象包含`preventDefault`、`type`、`eventPhase`等属性,用于控制事件行为。自定义事件可以通过`Event()`和`CustomEvent()`构造函数创建。事件代理通过冒泡机制将子元素事件交由父元素处理,减少事件处理程序数量,提升性能。事件中心应具备发布、订阅、取消订阅和一次执行功能,通过原型方法实现。使用事件委托技术可以减少内存占用,提升页面响应速度。

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.有必要限制一个页面中事件处理程序的数量,数量太多会导致占用大量内存,而且也会让用户感觉页面反应不够灵敏。

  1. 建立在事件冒泡机制之上的事件委托技术,可以有效地减少事件处理程序的数量。
  2. 建议在浏览器卸载页面之前移除页面中的所有事件处理程序。

有错误的地方或者不足的地方欢迎指正。