# 一.事件机制

# 1.1 自定义事件实现

  • createEvent,设置事件类型,是 html 事件还是鼠标事件
  • initEvent 事件初始化,事件名称,是否允许冒泡,是否阻止自定义事件
  • dispatchEvent 触发事件

# 1.2 前端事件流

事件触发三个阶段

  • window 往事件触发处传播,遇到注册的捕获事件会触发
  • 传播到事件触发处时触发注册事件
  • 从事件触发处往 window 传播,遇到注册的冒泡事件会触发

事件触发一般来说会按照上面的顺序进行,但是也有特例,如果给一个 body 中的子节点同时注册冒泡和捕获事件,事件触发会按照注册的顺序执行

// 以下会先打印冒泡然后捕获
node.addEventListener(
  "click",
  (event) => {
    console.log("冒泡")
  },
  false
)
node.addEventListener("click", (event) => {
  console.log("捕获")
})
1
2
3
4
5
6
7
8
9
10
11

事件流描述的是从页面中接受事件的顺序:事件捕获阶段-->处于目标阶段-->事件冒泡阶段

  • 捕获阶段:addevenListener 最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序
    • 实际目标 div 在捕获阶段不会接受事件,也就是在捕获阶段,事件从 document 到 html 再到 body 就停止了
  • 目标阶段
    • 事件在 div 发生并处理,但是事件处理会被看成是冒泡阶段的一部分。
  • 冒泡阶段:addevenListener 最后这个布尔值参数如果是 false,表示在冒泡阶段调用事件处理程序。
    • 事件又传播回文档

阻止冒泡事件:event.stopPropagation()

function stopBubble(e) {
  if (e && e.stopPropagation) {
    e.stopPropagation()
  } else {
    //ie
    window.event.cancelBubble = true
  }
}
1
2
3
4
5
6
7
8

阻止默认行为:event.preventDefault()

function stopDefault(e) {
  if (e && e.preventDefault) {
    e.preventDefault()
  } else {
    //ie
    window.event.returnValue = false
  }
}
1
2
3
4
5
6
7
8

事件如何先冒泡后捕获

在 dom 标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后在执行捕获事件。

那些事件不支持冒泡事件:

  • 鼠标事件:mouserleave mouseenter
  • 焦点事件:blur focus
  • UI 事件:scroll resize

# 1.3 事件委托(提高性能)

事件代理

如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在该父节点上

<ul id="ul">
 <li>1</li>
    <li>2</li>
 <li>3</li>
 <li>4</li>
 <li>5</li>
</ul>
<script>
 let ul = document.querySelector('#ul')
 ul.addEventListener('click', (event) => {
  console.log(event.target);
 })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

事件代理的方式相较于直接给目标注册事件来说,有以下优点:

  • 节省内存
  • 不需要给子节点注销事件

不在事件的(直接在 dom)上设置监听函数,而是在其父元素上设置监听函数。通过事件冒泡,父元素可以监听到子元素上事件的触发。

  • 目的

    • 通过动态绑定事件,新添加的子元素也会监听函数,减少内存消耗,提高性能
  • 写法

    • 通过判断事件发生元素 dom 的类型,来做出不同的响应。
    • 举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件的时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加

# 1.4 输入框 change 和 input 事件

  • onchange 事件: 要在 input 失去焦点的时候才触发
  • oninput 事件: 要在用户输入时触发,他是元素之发生变化时立即触发

# 1.5 document load / ready

  • 共同点:
    • 这两种事件都代表的是页面文档加载时触发。
  • 异同点:
    • ready 事件的触发,表示文档结构已经加载完成(不包含图片等非文字媒体文件)。
    • onload 事件的触发,表示页面包含图片等文件在内的所有元素都加载完成。

事件的触发过程是怎么样的?知道什么是事件代码吗?

# 注册事件

通常我使用addEventListener注册事件,该函数的第三个参数可以是布尔值,也可以是对象。对于布尔值useCapture参数来说,该参数默认值为false,useCapture决定了注册的事件是捕获事件还是冒泡事件。对于对象来说,可以使用以下几个属性

  • capture :布尔值,和useCapture
  • once: 布尔值,值为true表示该回调只会调用一次,调用后会移除监听
  • passive:布尔值,表示永远不会调用preventDefault

一般来说,如果我们只希望事件只触发在目标上,这时候可以使用stopPropagation来阻止事件的进一步传播。通常我们认为stopPropagation是用来阻止事件冒泡的,其实该函数可以阻止捕获事件。stopImmediatePropagation同样也能实现阻止事件,但是还能阻止该事件目标执行别的注册事件。

node.addEventListener(
  "click",
  (event) => {
    event.sotpImmediatePropagation()
    console.log("冒泡")
  },
  false
)
// 点击node只会执行上面的函数,该函数不会执行
node.addEventListener(
  "click",
  (event) => {
    console.log("捕获")
  },
  true
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3.存储

有几种方式可以实现存储功能,分别有什么优缺点?什么是 Service Worker? CookielocalStoragesessionStorageindexDB

# Service Worker

Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到install事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询时候存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。

// install.js
if (navigator.serviceWorker) {
  navigator.serviceWorker
    .register("sw.js")
    .then(function (registration) {
      console.log("service worker 注册成功")
    })
    .catch(function (err) {
      console.log("service worker 注册失败")
    })
}
// sw.js
// 监听 `install`事件,回调中缓存所需文件
self.addEventListener("install", (e) => {
  e.waitUntil(
    caches.open("my-cache").then(function (cache) {
      return cache.addAll(["./index.html", "./index.js"])
    })
  )
})
// 拦截所有请求事件
// 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
self.addEventListener("fetch", (e) => {
  e.respondWith(
    caches.match(e.request).then(function (response) {
      if (response) {
        return response
      }
      console.log("fetch source")
    })
  )
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 6.什么是内存溢出和内存泄漏

给的不够用,用了不归还

  • 内存溢出:
    • 在程序申请内存是,没有足够的内存空间供其使用,出现 out of memoryp;比如申请了一个 integer,但是给他存了 long 才能存下的数据,那就是内存溢出。
  • 内存泄漏:
    • 在程序申请内存后,无法释放已申请的内存空间,一次内存泄漏的危害可以忽略,但是内存泄漏堆积的后果很严重,无论多少内存,迟早会被占光
    • 内存泄漏会导致一系列问题,比如:运行缓慢,崩溃,高延迟
  • 哪些操作会造成内存泄漏
    • 闭包中的 this,对象函数
    • 匿名函数返回函数
    • setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏

# 1.存储方式与传输方式

  • 1.indexDB:h5 的本地存储库,存储空间可以在 250m 以上,把一些数据存储到浏览器中,没有网络,浏览器可以从这里读取数据,离线缓存用
  • 2.cookie:通过浏览器记录信息确认用户的身份,最大 4kb,这也限制了传输的数据,请求的性能会受到影响
    • 属性
      • value:如果用于保存用户登录状态,应该将该值加密,不能使用明文的用户标识
      • http-only:不能通过 js 访问 cookie,减少 xss 攻击
      • secure:只能在协议为 https 的请求中携带
      • sname-site:规定浏览器不能在跨域请求中携带 cookie,减少 csrf 攻击
    • 优点:极高的扩展性和可用性
      • 1.通过良好的编程,控制保存在 cookie 中的 session 对象的大小
      • 2.通过加密和安全传输技术(ssl),减少 cookie 被破解的可能性
      • 3.只在 cookie 中存放不敏感数据,即使被盗也不会有重大损失
      • 4.控制 cookie 的生命周期,使之不会永远有效,偷盗者很可能拿到一个过期的 cookie
    • 缺点
      • 1.cookie 数量和长度的限制。每个 domain 最多只能有 20 条 cookie,每个 cookie 长度不能超过 4kb,否则会被截掉
      • 2.安全性问题。如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也于事无补,因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了
      • 3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器,如果我们把这个计数器保存在客户端,那么它起不到任何作用。
  • 3.Session:服务器端使用的一种记录客户状态的机制(session_id 存在 set_cookie 发送到客户端,保存为 cookie)
  • 4.localStorage:h5 的本地存储,在谷歌浏览器中为 5mb,数据永久保存在客户端
  • 5.sessionStorage:h5 的本地存储,在谷歌浏览器中为 5mb,数据在绘画时保存在客户端
  • 数据有效期不同
    • cookie 在设置(服务器设置)有效期内有效,不管窗口和浏览器关闭
    • sessionStorage 只在当前浏览器窗口有效,关闭即销毁(临时存储)
    • localStorage 始终有效,除非手动删除
  • sessionStorage 和 localStorage 区别
    • 1.sessionStorage 用本地存储一个会话中的数据,这些数据只有在用一个会话的页面中才能被访问(也就是说在第一次通信过程中)
    • 2.localStorage 用于持久化的本地存储,除非主动删除数据,否则不会过期
  • indexDB:除非被清理否则一直存在
  • 1.token 就是令牌,比如你授权(登录)一个程序时,他就是个依据,判断你是否已经授权该软件(最好的身份认证,安全性好,而且时唯一的)用户身份的验证方式
    • 基于 token 的身份验证:(token:uid 用户唯一的身份识别+time 当前时间戳+sign 签名)
      • 1.用户通过用户名和密码发送请求
      • 2.服务端验证
      • 3.服务端返回一个带签名的 token 给客户端
      • 4.客户端存储 token,并且每次用于发送请求
      • 5.服务器验证 token 并且返回数据 每一次请求都需要 token
  • 2.cookie 是卸载客户端的一个 txt 文件,里面包括登录信息之类的,这样你下次登录某个网站,就会调用 cookie 自动登录用户名服务器生成,发送到浏览器 浏览器保存 下次请求再次发送给服务器(存放登录信息)
  • 3.session 是一类用来客户端和服务器之间保存状态的解决方案,会话完成被销毁(代表就是服务器和客户端的一次会话过程)cookie 中存放着 sessionID,请求会发送这个 id,session 因为 request 对象而产生
  • cookie 和 session 的区别
    • 1.cookie 数据存放在客户的浏览器上,session 数据存放在服务器上
    • 2.cookie 不是很安全,别人可以分析存放本地的 cookie 并进行 cookie 欺骗,考虑安全应当使用 session
    • 3.session 会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用 cookie
    • 4.单个 cookie 保存的数据不能超过 4k,很多浏览器都限制一个站点最多保存 20 个 cookie
  • session 和 token 的区别
    • 1.session 认证只是简单的 user 信息存储 session 里面,sessionID 不可预测,一种认证手段,不能共享到其他的网站和第三方 app
    • 2.token 是 oAuth Token,目的就是让某 app 有权访问某用户的信息,token 是唯一的;token 不能转移到其他的 app,也不能转到其他用户上(使用 app)
    • 3.session 的状态是存在服务端的,客户端只存在 session id,token 状态是存在客户端的
  • cookie 的弊端有哪些
    • 优点:保存客户端数据,分担了服务器存储的负担
    • 1.数量和长度的限制,每个特定的域名下最多生成 20 个 cookie(chorme 和 safari 没有限制)
    • 2.安全性问题

# 事件代理

如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在该父节点上

<ul id="ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
</ul>
<script>
  let ul = document.querySelector("#ul");
  ul.addEventListener("click", (event) => {
    console.log(event.target);
  });
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

事件代理的方式相较于直接给目标注册事件来说,有以下优点:

  • 节省内存
  • 不需要给子节点注销事件
上次更新: 2025/2/12 上午9:35:56