JS知识点回顾——事件处理

202 阅读5分钟

DOM节点

网页是由DOM树组成的;大概的DOM树

image.png

其实document不止html一个节点

image.png

window和document的关系

BOM(browser object model):浏览器对象模型,没有相关标准,表示一些和网页无关的浏览器功能,如window、location、navigator、screen、history等

DOM(document object model):文档对象模型,W3C标准,HTML和XML文档的编程接口

window属于BOM,document属于DOM中的核心,但是window引用着document

DOM0级事件

<button onclick="console.log(1)">按钮</button>

特点:

1、效率高

2、节点上onclick属性被Node.cloneNode克隆,通过JS赋值的onclick不可以

3、移除事件简单:dom.onclick=null

4、兼容性好

注意事项:

1、事件处理函数中,this是当前的节点

2、如果调用函数,会在全局作用域查找

3、只能定义一个事件回调函数

<body>
  <button onclick="console.log('按钮1被点击了')">按钮1</button>
  <div>
    <button onclick="onclick2()">按钮2</button>
  </div>
  <div>
    <button onclick="console.log(this)">按钮3</button>
  </div>
  <div>
    <button onclick="onclick4()">按钮4</button>
  </div>
  <script>
    function onclick2() {
      console.log("按钮2被点击了")
    }
    (function() {
      function onclick4() {
        console.log("按钮4被点击了")
      }
    })()
  </script>
</body>

image.png

点击复制区域的btn2事件不会生效

<body>
  <div id="sourceZone">
    <div>原始区域:</div>
    <div id="sourceButtons">
      <button onclick="console.log('按钮1被点击了')">按钮1</button>
      <div>
        <button class="btn2">按钮2</button>
      </div>
    </div>
  </div>
  <div id="copyZone">
    <div>复制区域:</div>
  </div>
  <script>
    function onclick2() {
      console.log("按钮2被点击了")
    }
    document.querySelector(".btn2").onclick = onclick2;
    copyZone.append(sourceButtons.cloneNode(true));
  </script>
</body>

DOM2级事件

image.png

事件注册:

语法:targe.addEventListener(type, listener, useCapture)或者targe.addEventListener(type, listener, options)

useCapture: true表示捕获节点传播到目标的时候触发,反之为冒泡阶段传到目标的时候触发,默认是false

注意: 如果这个参数相同并且事件回调相同,事件将不会被添加

<body>
  <button id="btn">按钮</button>
  <script>
    function onclick() {
      console.log("btn被点击")
    }
    btn.addEventListener("click",onclick)
    btn.addEventListener("click",onclick)
    btn.addEventListener("click",onclick,{capture:true})
    btn.addEventListener("click",onclick,{capture:true,once:true})  
  </script>
</body>

image.png

options:

once:是否只响应一次,可以在视频播放的时候在document上绑定事件并指定只能触发一次

passive:设置为true时,事件处理成不会调用preventDefault();某些触摸事件的事件监听器在尝试处理滚动时,可能阻止浏览器的主线程,导致滚动事件处理期间性能降低

signal:AbortSignal的abort()方法被调用时,监听器会被移除,AbortSignal可以用于取消fetch事件

按钮只能被点击一次

<body>
  <button id="btn">按钮</button>
  <script>
    const controller = new AbortController();
    const signal = controller.signal;
    btn.addEventListener("click",function () {
      console.log("btn被点击")
      controller.abort();
    },{
      signal
    })
  </script>
</body>

点击屏幕播放视频

image.png

事件冒泡和捕获

<body>
  <button class="btn">按钮</button>
  <script>
    const btn = document.querySelector(".btn")
    window.addEventListener("click",function () {
      console.log("window捕获")
    },true)
    document.addEventListener("click",function () {
      console.log("document捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn冒泡")
    })
    document.addEventListener("click",function () {
      console.log("document冒泡")
    })
    window.addEventListener("click",function () {
      console.log("window冒泡")
    })
  </script>
</body>

image.png

event.preventDefault:阻止默认行为,比如有href属性的a标签,阻止默认行为之后将不会再跳转;但是并不影响事件继续传播

stopPropagation:阻止捕获和冒泡阶段中当前事件的进一步传播

<body>
  <button id="btn">按钮</button>
  <script>
    const btn = document.querySelector("#btn")
    btn.addEventListener("click",function () {
      console.log("btn捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn冒泡")
    })
    document.addEventListener("click",function () {
      console.log("document冒泡")
    })
    document.addEventListener("click",function (e) {
      console.log("document捕获")
      e.stopPropagation();
    },true)
  </script>
</body>

image.png

stopImmediatePropagation:阻止监听同一事件的其他事件监听器被调用

<body>
  <button id="btn">按钮</button>
  <script>
    const btn = document.querySelector("#btn")
    document.addEventListener("click",function (e) {
      console.log("document捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn冒泡1")
    })
    btn.addEventListener("click",function (e) {
      console.log("btn冒泡2");
      e.stopImmediatePropagation();
    })
    btn.addEventListener("click",function () {
      console.log("btn冒泡3")
    })
    document.addEventListener("click",function () {
      console.log("document冒泡")
    })
  </script>
</body>

image.png

<body>
  <button id="btn">按钮</button>
  <script>
    const btn = document.querySelector("#btn")
    document.addEventListener("click",function (e) {
      console.log("document捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn捕获")
    },true)
    btn.addEventListener("click",function () {
      console.log("btn冒泡1")
    })
    btn.addEventListener("click",function (e) {
      console.log("btn冒泡2");
      e.stopPropagation();
    })
    btn.addEventListener("click",function () {
      console.log("btn冒泡3")
    })
    document.addEventListener("click",function () {
      console.log("document冒泡")
    })
  </script>
</body>

image.png

target和currentTarget

target:触发事件的元素,谁触发的

currentTarget:事件绑定的元素,谁添加的事件监听函数

<body>
  <button id="btn">按钮</button>
  <script>
    const btn = document.querySelector("#btn")
    document.addEventListener("click",function (e) {
      console.log("target:",e.target)
      console.log("currentTarget:",e.currentTarget)
    })
    btn.addEventListener("click",function () {
      console.log("btn被点击了")
    })
  </script>
</body>

image.png

事件委托:

利用事件传播的机制,利用外层节点处理事件的思路

优点:

减少内存消耗

”动态性“更好,比如增加节点的时候,不需要额外添加事件

<body>
  <ul id="ulList">
    <li>
      <div>白菜</div>
      <button class="btn-buy" data-id="1">购买</button>
    </li>
    <li>
      <div>萝卜</div>
      <button class="btn-buy" data-id="2">购买</button>
    </li>
  </ul>
  <script>
    ulList.addEventListener("click",function (e) {
      if(e.target.classList.contains("btn-buy")){
        console.log("商品id:", e.target.dataset.id)
      }
    })
  </script>
</body>

DOM3级事件

image.png

自定义事件

内置事件:click、blur、mousewheel等等;内置事件一般是用户交互触发,也有系统自己调用,比如onload,动画停止等等

代码触发内置事件:element.eventType或者new [Event] + dispatchEvent

<body>
  <div>
    <button id="btnDownload">下载文件</button>
  </div>
  <script>
    function createBlob(content,type) {
      return new Blob([content],{type})
    }
    function downloadFile(fileName,content,type="text/plain") {
      const aEl = document.createElement("a");
      // 指定下载文件名
      aEl.download = fileName;
      const blob = createBlob(content,type);
      // 生成url
      const url = URL.createObjectURL(blob);
      aEl.href = url;
      document.body.appendChild(aEl);
      // 触发点击事件
      aEl.click();
      // 删除节点
      document.body.removeChild(aEl);
      // 释放url
      URL.revokeObjectURL(url)
    }
    btnDownload.addEventListener("click",function () {
      downloadFile("测试.txt","测试的文本")
    })
  </script>
</body>

这里是使用aEl.click()触发的事件;也可以使用dispatch触发

// 触发点击事件
// aEl.click();
const event = new MouseEvent("click");
aEl.dispatchEvent(event)

这里有个小技巧url是自带uuid的,所以可以使用

console.log(URL.createObjectURL(new Blob([''])).split('/').pop())

自定义事件的方法

document.createEvent(已废弃)

语法:const event = document.createEvent(type)

image.png

new Event()

语法:const event = ​new Event(type,eventInit);缺点:不能携带额外参数

image.png

<body>
  <div class="container">
    <button id="btn">开始吧</button>
  </div>
  <div>
    <div>
      流程1:
      <div id="step1"></div>
    </div>
    <div>
      流程2:
      <div id="step2"></div>
    </div>
  </div>
  <script>
    function dispatchEE(target, type) {
      const event = new Event(type,{
        // 这里可以定义参数
      });
      target.dispatchEvent(event);
    }
    btn.addEventListener("click", function(){
      setTimeout(() => {
        dispatchEE(step1,'step-1')
      }, 2000);
    })
    step1.addEventListener("step-1", function(){
      step1.textContent = "流程1进行中。。。。";
      setTimeout(() => {
        dispatchEE(step2,'step-2')
      }, 2000);
    })
    step2.addEventListener("step-2", function(){
      step2.textContent = "流程2进行中。。。。";
      setTimeout(() => {
        dispatchEE(window,'finish');
      }, 2000);
    })
    window.addEventListener("finish",function() {
      console.log("finish")
    })
  </script>
</body>
new CustomEvent()

语法:const event = ​new CustomEvent(type,eventInit)

image.png

<body>
  <div class="container">
    <button id="btn">开始吧</button>
  </div>
  <div>
    <div>
      流程1:
      <div id="step1"></div>
    </div>
    <div>
      流程2:
      <div id="step2"></div>
    </div>
  </div>
  <script>
    function dispatchEE(target, type,data) {
      const event = new CustomEvent(type,{
        detail:data
      });
      target.dispatchEvent(event);
    }
    btn.addEventListener("click", function(){
      setTimeout(() => {
        dispatchEE(step1,'step-1',{param:"step1的启动参数"})
      }, 2000);
    })
    step1.addEventListener("step-1", function(ev){
      step1.textContent = "流程1进行中。。。。,参数有:" + ev.detail.param;
      setTimeout(() => {
        dispatchEE(step2,'step-2')
      }, 2000);
    })
    step2.addEventListener("step-2", function(){
      step2.textContent = "流程2进行中。。。。";
      setTimeout(() => {
        dispatchEE(window,'finish');
      }, 2000);
    })
    window.addEventListener("finish",function() {
      console.log("finish")
    })
  </script>
</body>

从继承关系来看,CustomEvent是Event的扩展

Event时候简单的自定义事件,CustomEvent支持传递数据的自定义事件

CustomEvent可以在webwork中使用

CustomEvent可能存在兼容性问题,需要使用垫片

image.png

JS操作样式

样式来源

浏览器默认样式(用户代理样式)

image.png

浏览器用户自定义样式、link引入的外部样式、style标签样式、内联样式

样式优先级

1、内联样式

2、ID选择器

3、类和伪类选择器

4、标签选择器

5、通配符选择器

还有important优先级最高

image.png

操作元素节点上的style属性

style属性名是驼峰语法

<body>
  <div id="divEl">这是一个div</div>
  <script>
    divEl.style.backgroundColor="red";
    divEl.style.fontSize = "20px"
  </script>
</body>

style.cssText批量赋值

<body>
  <div id="divEl">这是一个div</div>
  <script>
    divEl.style.cssText = "background-color:red;font-size:20px"
  </script>
</body>

注意: style是自读的,不可以直接使用object修改

操作元素节点classList和className属性

image.png DOMTokenList.toggle:从列表中删除一个给定的标记并返回false,如果标记不存在则添加并返回true

语法:tokenList.toggle(token,force);如果force为真则只添加不删除

<body>
  <button id="btnTooggleFalse">btnTooggleFalse</button>
  <button id="btnTooggleTrue">btnTooggleTrue</button>
  <div id="divEl">这是一个div</div>
  <script>
    btnTooggleFalse.addEventListener("click", function(){
      divEl.classList.toggle("test-div")
    })
    btnTooggleTrue.addEventListener("click", function(){
      divEl.classList.toggle("test-div",true)
    })
  </script>
</body>

点击btnTooggleFalse按钮,会在添加和删除test-div之前切换

点击btnTooggleTrue按钮,之后添加test-div

操作style节点的内容

style节点本质还是Node节点

image.png

 <style id="test">
    .test-div {
      color: red;
    }
  </style>
<body>
  <button id="btnReplace">替换</button>
  <div class="test-div">这是一个div</div>
  <script>
    const ssEl = document.getElementById("test")
    btnReplace.addEventListener("click", function(){
      ssEl.textContent = ssEl.textContent.replace("color: red","color: green")
    })
  </script>
</body>

操作已有的style节点

CSSOM:CSS Object Model是一组允许用Javascript操作CSS的API

image.png

image.png

image.png

<body>
  <button id="btnUpdate">更改style节点</button>
  <div class="test-div">这是一个div</div>
  <script>
    const ssEl = document.getElementById("test")
    btnUpdate.addEventListener("click", function(){
      // 找到所有的style节点
      const styleSheets = Array.from(document.styleSheets);
      const st = styleSheets.find(s=>s.ownerNode.id === "test");
      const rule = Array.from(st.rules).find(r=>r.selectorText === ".test-div");
      const styleMap = rule.styleMap;
      styleMap.set("background-color","green")
    })
  </script>
</body>

动态创建style节点

<body>
  <button id="btnUpdate">更改style节点</button>
  <div class="test-div">这是一个div</div>
  <script>
    const ssEl = document.getElementById("test")
    btnUpdate.addEventListener("click", function(){
      const styleNode = document.createElement("style");
      styleNode.textContent = `.test-div {
        background-color:green;
      }`;
      document.head.appendChild(styleNode)
    })
  </script>
</body>

更改外部引入的样式

本质也是CSSStyleSheet

image.png

​动态创建link节点引入样式

function importCSSByUrl(url) {
      const link = document.createElement("link");
      link.rel = "stylesheet";
      link.type = "text/css";
      link.href = url;
      document.head.appendChild(link);
}

​window.getComputeStyle

获取计算后的样式

语法:const style = window.getComputedStyle(element,[pseudoElt])

pseudoElt:表示是否查询伪元素,比如可以传入"before"

该方法会引起重排

订阅发布中心

是一种消息通知机制,也是一种发布订阅模式的实际应用;应用场景比如公众号信息、短信提醒等

基础功能

on:订阅

once:订阅,但是只接受一次通知

off:取消订阅

emit:派发事件,通知订阅者

使用window绑定事件

<script>
    window._on = document.addEventListener;
    window._off = document.removeEventListener;
    window._emit = (type,data)=>window.dispatchEvent(new CustomEvent(type,{detail:data}));
    window._once = (type,listener)=>window.addEventListener(type,listener,{capture:true,once:true});
  </script>
  <script>
    function onEvent(e) {
      console.log(`event-x收到数据:${e.detail}`)
    }
    // 订阅消息
    window._on("event-x",onEvent);
    window._once("event-once",e=>console.log(`once:${e.detail}`));
    // 触发两次once只会收到一次信息
    window._emit("event-once",{meg:"传递了一次信息"})
    window._emit("event-once",{meg:"传递了两次次信息"})
    // 给event-x发送信息
    window._emit("event-x",{meg:"给event-x发送信息"})
    window._off("event-x",onEvent)
    window._emit("event-x",{meg:"再给event-x发送信息"})
  </script>
原理:

根源是EventTarget

document和元素节点也是继承于EventTarget

window也是继承于EventTarget

XMLHttpRequest、WebSocket也继承于EventTarget

EventTarget:是一个构造函数,可以创建一个新的EventTarget对象实例

EventTarget身上的方法:

EventTarget.addEventListener:在​EventTarget上注册特定事件类型的事件处理程序

EventTarget.removeEventListener:​EventTarget中删除事件侦听器

​EventTarget.dispatchEvent将事件分派到此​EventTarget

​缺点:

1、不能多实例化

2、挂载window影响全局

3、不能传递多个参数

​使用构造函数

参数不能传递多个

<script>
    class EventEmitter extends EventTarget {
      on = this.addEventListener;
      off = this.removeEventListener;
      emit = (type,data)=>this.dispatchEvent(new CustomEvent(type,{detail:data}));
      once = (type,listener)=>this.addEventListener(type,listener,{capture:true,once:true});
    }
  </script>
  <script>
    const emitter = new EventEmitter();
    function onEvent(e) {
      console.log(`event-x收到数据:${e.detail}`)
    }
    // 订阅消息
    emitter.on("event-x",onEvent);
    emitter.once("event-once",e=>console.log(`once:${e.detail}`));
    // 触发两次once只会收到一次信息
    emitter.emit("event-once",{meg:"传递了一次信息"})
    emitter.emit("event-once",{meg:"传递了两次次信息"})
    // 给event-x发送信息
    emitter.emit("event-x",{meg:"给event-x发送信息"})
    emitter.off("event-x",onEvent)
    emitter.emit("event-x",{meg:"再给event-x发送信息"})
  </script>

升级改造

<script>
    class EventEmitter extends EventTarget {
      on = (type,listener,options) => this.addEventListener(type,function wrap(e) {
        return (listener.__wrap__ = wrap,listener.apply(this,e.detail || []))
      },options);
      off = (type,listener)=>this.removeEventListener(type,listener.__wrap__);
      emit = (type,...args)=>this.dispatchEvent(new CustomEvent(type,{detail:args}));
      once = (type,listener)=>this.on(type,listener,{capture:true,once:true});
    }
  </script>
  <script>
    const emitter = new EventEmitter();
    function onEvent(msg) {
      console.log(`event-x收到数据:`,this,msg)
    }
    // 订阅消息
    emitter.on("event-x",onEvent);
    emitter.once("event-once",(msg)=>console.log(`once:${msg}`));
    // 触发两次once只会收到一次信息
    emitter.emit("event-once","传递了一次信息")
    emitter.emit("event-once","传递了两次次信息")
    // 给event-x发送信息
    emitter.emit("event-x","给event-x发送信息")
    emitter.off("event-x",onEvent)
    emitter.emit("event-x","再给event-x发送信息")
  </script>