2862字 | 浏览器的十万个为什么🗣(@0728)

500 阅读8分钟

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战

本文会随时有更新,更新会在标题上体现日期,方便查看。收藏专栏或文章不怕迷路噢~ ECMA-262 规范 镇楼 🙏🏻 ,祝看到此文的小伙伴快乐暴富~

线程和进程的区别

// todo

chrome多进程架构 @0728

多进程架构.png

此图来自网络,侵删,具体可参考下图

从输入URL到页面展示这中间发生了什么 @0728

用户输入URL到页面展示这中间发生了什么

pwa的原理

// todo

ajax原理,实现原生ajax

// todo

什么是三次握手,为什么一定要三次握手四次挥手 👋

// todo

首屏时间和白屏时间,如何优化

// todo

简述https原理,与http的区别,http1.1/http2.0区别

// todo

http头部

// todo

为什么常见的cdn域名和业务域名不一样?

// todo

http header 怎么判断协议是不是 websocket

// todo

怎么与服务器保持连接

// todo

多个请求方法有什么作用和区别

// todo

常见状态码

// todo

浏览器HTTP缓存命中流程 @0727

W5fVE9.png

强缓存 vs. 协商缓存

W5fp90.png

304状态码,通过什么来判断是否用缓存

上图中有说明,具体还可参考

📌 可参考 HTTP Caching >

封装ajax

// todo

浏览器本地存储

// todo

http请求跨域问题,如何解决

// todo

Fetch API 是什么?能代替 AJAX 吗?

// todo

  1. XMLHTTPRequest
  2. Fetch

// 如何封装fetch(AbortController)

router和route的区别?

// todo

路由传参,获取参数

// todo

延时路由

// todo

cookie有哪些属性

// todo

options请求方法有什么用

// todo

怎么禁止js访问cookie

// todo

node多进程

node中cluster是怎样开启多进程的,并且一个端口可以被多个进程监听吗

node进程如何通信

node可以开启多线程吗

操作系统中进程和线程是怎么通信的

事件模型

事件

浏览器上的所有行为都是通过事件驱动的,当一个事件发生时,我们可以为该事件绑定监听函数,函数内部执行我们需要在这个事件发生时执行的一些逻辑。浏览器提供了许多支持被监听的元素事件(一定是监听在某个对象/元素上的),当事件触发时就会立即执行注册在该对象上的监听函数。 📌 更多HTML事件参考w3school >

事件传播

浏览器事件模型决定了当前事件发生后,事件传播的方向。

事件传播有3个阶段:

  • 捕获阶段:事件从最外层的祖先元素向内传播到目标元素(top父 -> 目标)
  • 目标阶段:事件到达目标元素(@目标)
  • 冒泡阶段:事件从目标元素传播向外传播到最外层的祖先元素(目标 -> top父)

浏览器基于DOM流事件模型,事件传播过程为 捕获阶段 -> 目标阶段 -> 冒泡阶段

事件监听

事件监听有3种方式, HTML on-事件监听dom 事件属性绑定dom addEventListener

HTML on-事件监听

<button onclick="console.log(1)"> click me </button>

如上代码是在html标签上做事件监听的例子,可以将html支持的事件写在标签上。但是这种方式js代码和html就混在一起不清晰了,所以我们尽量减少使用这种事件监听绑定方式。

dom 事件属性绑定

<!--html-->
<div id="example"> test </div>
// js
const div = document.getElementById('example')

div.onclick = (e) => {
    console.log(1)
}

如上代码是在指定dom元素上设置该元素支持的事件属性以绑定监听事件。这种方式的短板是,在同一个元素上不支持对一个事件绑定多个监听,默认如果有事件绑定多次监听函数,后面的会覆盖前面的。

dom addEventListener

我们常用的注册方式如下:

target.addEventListener(type, listener, useCapture);

target.addEventListener(type, listener, options);

target: 绑定监听函数的dom元素
type: 监听事件类型
listener: 监听(回调)函数
useCapture: 事件监听阶段( false :冒泡阶段,默认值、 true :捕获阶段)
options:{ once :只执行一次; passive :承诺此事件监听不会调用 preventDefault ,这有助于性能; useCapture :是否捕获(否则冒泡)} // 这个参数我基本没用到过,不举例了

<!--html-->
<div id="example"> test </div>
// js
const div = document.getElementById('example')

div.addEventListener('onclick', function(e) {
    console.log(1)
})

addEventListener 是我们当下比较推荐的事件监听绑定方式,与前两者相比,它支持在一个事件上绑定多个不同的监听函数,还可以设定注册事件的监听阶段等等。

📌 更多addEventListener参数详见MDN >

addEventListener 使用示例很多文章的都有展示,也比较好理解,这里我就不做具体展示了。具体说下在使用 addEventListener 的时候有哪些关键概念。

第三个参数

addEventListener 的第三个参数一般用于设置该监听函数是设置在事件发生的捕获阶段还是冒泡阶段。(默认为冒泡阶段)

移除事件监听

对应的移除事件监听可以用 removeEventListener 方法,参数和 addEventListener 保持一致(如果绑定和移除方法接收的事件处理 listener 函数都为匿名函数则两者不会认为是同一个监听函数)。

dom.addEventListener('click', function(e) {})

dom.removeEventListener('click', function(e) {})

这样写会起不到移除监听的效果,绑定方法和移除方法必须接收同一个监听函数才可以生效,

const listener = (e) => {}

dom.addEventListener('click', listener)

dom.removeEventListener('click', listener)

阻止事件传播

e.stopPropagation()

- document
  - html
    - body
      - div

假如现在有如上dom结构(从外到内), p.addEventListener('click', handler) 我们在div上绑定了一个监听事件,当div的监听事件发生时,dom流事件模型传播过程为:捕获 -> 目标 -> 冒泡

捕获阶段: document -> html -> body -> div

目标阶段: div

冒泡阶段: div -> body -> html-> document

这个过程意味着,在事件流传播的路径上,经过的元素上如果有绑定相同的事件监听函数并且满足监听阶段(该例子是 click 事件)都会被触发执行。

而回到标题,很多人都知道这个 e.stopPropagation 这个方法可以阻止事件冒泡,也就是事件传播不再向后续进行冒泡传播(在后续绑定在冒泡阶段执行的监听函数都不会被执行)。但是其实该方法是用来阻止事件传播的,也就是不只是事件冒泡,只要调用了该方法的,无论是捕获还是冒泡,事件传播都会在当下立即停止。

用一个例子更清晰的看一下:

// html
<html>

<body>
    <div id="parent">
        parent
        <span id="child">
            child
            <p id="son">son
            </p>
        </span>
    </div>
    <div id="scriptPanel"><img src="indexjs.png" /></div>
</body>
<script src="index.js"></script>

</html>

为了看得更清晰一些,我们给嵌套dom加了一点样式,并将 <script> 内容放到了右侧对照。

1

e.target.nodeName

在js中我们用到了 e.target.nodeName ,这个属性是用来输出当前事件目标元素的节点名称(大写),当我们点击 son 的时候,输出如下:

2

我们会发现输出的总是 P ,因为我们触发事件的真正元素是 P ,而在事件传播的过程中,经过了parent、child并且它们也有同样的监听事件,一同被触发了。那么我们如何在监听函数中获取当前绑定监听事件的元素呢?

e.currentTarget.nodeName

e.currentTargete.target 这两个属性看着非常的像,它们的区别是 e.target 指触发当前事件真正的目标元素, e.currentTarget 指监听函数是绑定在哪个元素上的,绑定监听事件的元素

一个事件触发后会经历事件传播的过程,事件传播会经历n个元素,每个元素上都可以注册监听函数,不论是在哪个监听函数中打印目标元素 e.target 都会是一样的。而 e.currentTarget 则是不同的监听函数绑定的元素打印出来的都各不相同。

3

点击 son 输出如下:

4

回到正题,我们把 e.stopPropagationparent 点击事件中调用一下,

5

点击 son 输出如下:

6

可以看到 parent 以后的监听事件全都没有被执行,即调用 e.stopPropagation 不止是会阻止事件冒泡,是将整个事件传播停止了 => 阻止事件传播。

这个功能可以用在一些全局禁用的场景,比如过期无权限等场景,在全局点击事件上可以做一个监听,满足禁用场景时阻止事件传播。

阻止默认行为

e.preventDefault()

dom元素在事件发生时会有一些自带的默认行为,譬如点击 a 标签会自动跳转。而当我们不需要默认行为时就可以在事件监听函数中调用 e.preventDefault() 阻止默认行为的发生。

事件委托

正因为事件发生时,一定会经历事件传播的过程事件委托的概念就由此产生了。

我们可以将目标元素的事件委托给父元素,即在父元素上监听同样的事件,这种行为适合在繁多的子元素对同一行为有相同处理时,我们需要给子元素一个个绑定监听事件,而只需要在它们公共的父元素上注册一次事件监听,减少事件绑定减少dom引用减少页面内存。并且根据上述的栗子使用 e.target 就可以获取真正触发事件的目标元素。