1、从在浏览器中输入URL到搜索到想要的网页的结果,这中间经历了哪些步骤?

1
2
3
4
5
6
7
8
9
10
01.浏览器查找域名对应的 IP 地址(DNS 查询:浏览器缓存->系统缓存->路由器缓存->ISP DNS 缓存->根 域名服务器)
02.浏览器向 Web 服务器发送一个 HTTP 请求(TCP 三次握手)
03.服务器 301 重定向(从 example.com 重定向到 www.example.com)
04.浏览器跟踪重定向地址,请求另一个带 www 的网址
05.服务器处理请求(通过路由读取资源)
06.服务器返回一个 HTTP 响应(报头中把 Content-type 设置为 'text/html')
07.浏览器进 DOM 树构建
08.浏览器发送请求获取嵌在 HTML 中的资源(如图片、音频、视频、CSS、JS 等)
09.浏览器显示完成页面
10.浏览器发送异步请求

2、进程与线程的联系和互相通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
进程和线程是操作系统中管理和执行程序的基本单位。它们之间有着密切的联系,并且可以通过各种机制进行通信。

联系:
共享资源: 进程内的所有线程共享同一份内存空间和其他资源,包括文件描述符、打开的文件、信号处理器等。这意味着进程中的线程可以相互访问和修改彼此的数据。

隔离性: 每个进程有自己的地址空间,进程之间的地址空间是相互独立的。而线程是在进程内部创建的,它们共享进程的地址空间。

资源分配: 进程是操作系统中的资源分配单位,每个进程拥有自己的内存空间、文件描述符、I/O 等。而线程是 CPU 调度的基本单位,是进程内的执行单元。

通信:
共享内存: 进程内的所有线程共享同一份内存空间,因此它们可以通过共享内存来进行通信。通过在内存中创建共享变量或者使用同步机制(例如互斥锁、信号量)来控制对共享内存的访问,线程可以进行相互通信。

消息传递: 线程可以通过消息队列、管道、套接字等方式进行消息传递。进程间通信(IPC,Inter-Process Communication)的机制同样适用于进程内的线程间通信。这些机制包括管道、消息队列、信号量、共享内存等。

同步机制: 线程可以使用各种同步机制来确保多个线程之间的顺序执行或者避免竞态条件。常见的同步机制包括互斥锁、条件变量、信号量等。

3、https为什么比http更安全

1
2
3
4
5
6
7
8
9
10
11
HTTPS(HyperText Transfer Protocol Secure)相对于HTTP(HyperText Transfer Protocol)更安全,主要体现在以下几个方面:

加密通信: HTTPS 使用 SSL/TLS 协议对数据进行加密传输,而 HTTP 不进行加密,数据以明文形式传输。通过使用加密技术,HTTPS 可以防止窃听者(例如黑客或网络监听器)截取和查看通信中的敏感信息,如用户登录凭证、银行卡信息等。

身份验证: HTTPS 要求服务器提供数字证书,证明其身份的合法性。这些数字证书由受信任的证书颁发机构(Certificate Authorities,CA)签发,用于验证服务器的身份。这样可以防止中间人攻击,确保客户端与服务器之间的通信不被篡改或伪装。

完整性保护: HTTPS 使用数字签名技术来保证数据的完整性,防止数据在传输过程中被篡改或修改。通过对传输的数据进行数字签名,可以确保数据在传输过程中没有被篡改,从而保证数据的完整性。

浏览器安全机制: 现代的 Web 浏览器对使用 HTTPS 访问的网站提供更高的安全性支持。例如,浏览器通常会在地址栏中显示一个锁图标来指示连接是安全的,并警告用户正在访问的网站是否存在安全风险。

总的来说,HTTPS 相对于 HTTP 更安全,因为它使用加密技术对数据进行保护,提供了身份验证和完整性保护机制,以及浏览器安全支持。这些安全特性使得 HTTPS 成为更可靠的通信协议,适用于传输敏感信息的场景,例如登录、支付等。

4、html和css的解析顺序,css的解析不会阻塞html的解析,会阻塞Html的渲染的原因

1
html和css可以并行同时解析,但是因为需要等到了css和html都解析完然后合并成为一个DOM树然后再去渲染

5、DOMContentLoad和load的区别

1
DOM树加载完成后并不代表页面就加载完成了,因为还有一些静态的资源需要去请求,而DOMContentLoad就是DOM树加载完成了,但是那些静态资源还没有进行请求的情况,而Load是全部资源都请求完毕

6、重绘和回流是什么以及如何避免重绘和回流关于重绘和回流相关的方requestAnimationFrame和getBoundingClientRect和intersectionObserver

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
重绘(Repaint)和回流(Reflow)是浏览器渲染页面时涉及的两个重要概念。

重绘(Repaint): 当元素的样式(如颜色、背景等)发生改变,但不影响其几何形状和位置时,浏览器会触发重绘。在重绘过程中,浏览器重新绘制受影响的部分,但不会影响其他元素的布局。

回流(Reflow): 当元素的几何属性发生改变,例如宽度、高度、位置等,或者当添加、删除、隐藏元素时,浏览器会触发回流。在回流过程中,浏览器需要重新计算并更新受影响元素及其子元素的几何属性,并重新布局页面。

由于重绘和回流都会导致浏览器重新渲染页面的部分或全部内容,因此它们都可能引起页面性能问题,尤其是在大型、复杂的页面中。

为了避免重绘和回流,可以采取以下几个策略:

使用 CSS 动画代替 JavaScript 动画: 使用 CSS 进行动画效果可以利用硬件加速,减少重绘和回流的发生,从而提高性能。

批量修改样式: 当需要修改多个元素的样式时,尽可能一次性进行修改,而不是分开多次修改。这样可以减少重绘和回流的次数。

使用离线 DOM 操作: 在需要对多个 DOM 元素进行操作时,可以先将它们从文档中移除,进行操作后再添加回文档中,这样可以避免多次回流。

避免强制同步布局: 尽量避免直接获取布局信息(如 offsetTop、offsetLeft 等),因为这会导致浏览器强制进行同步布局。如果需要获取元素的几何属性,可以使用异步的方式,例如使用 requestAnimationFrame 或 getBoundingClientRect 等方法。

使用 CSS3 属性优化: 部分 CSS3 属性(如 transform、opacity 等)可以在不引起回流的情况下改变元素的外观,因此在可能的情况下尽量使用这些属性。

通过以上方法,可以有效减少重绘和回流的次数,提高页面性能和用户体验。
requestAnimationFrame(RAF): 这是一个用于执行动画效果的浏览器 API。与传统的 setTimeout 或 setInterval 相比,requestAnimationFrame 更加高效,因为它会在浏览器下次重绘之前调用指定的回调函数。这样可以确保动画在每一帧之间的间隔是平滑的,避免了丢帧和视觉上的不连续性。使用 requestAnimationFrame 可以帮助我们优化动画效果,提高页面性能。

getBoundingClientRect: 这是一个 DOM API,用于获取指定元素相对于视口的位置和尺寸信息。使用 getBoundingClientRect 可以方便地获取元素的位置信息,从而进行一些基于元素位置的操作,例如实现元素的滚动、懒加载、动画等。这个方法可以避免使用 offsetTop、offsetLeft 等属性来计算元素的位置,从而提高性能。

Intersection Observer(交叉观察器): 这是一个用于监听元素与其祖先元素或视口的交叉状态的 API。通过使用 Intersection Observer,可以异步地观察元素的可见性,并在元素进入或离开视口时触发回调函数。这个 API 可以帮助我们实现诸如懒加载、无限滚动、元素的渐显动画等效果,而且能够避免因频繁监听元素可见性而导致的性能问题。

7、从后端请求到一个数组后如何渲染出来性能会更好

1
2
3
4
5
6
7
8
9
10
11
12
13
分页加载: 将大量数据分成多个页面或分块加载,而不是一次性加载所有数据。前端可以根据用户的浏览情况,动态加载所需的数据分页,以减少页面加载时间和减轻服务器负载。

虚拟滚动: 使用虚拟滚动技术,只渲染用户当前可见区域内的数据。这样可以降低内存占用和渲染成本,并提高页面加载速度和性能。常见的虚拟滚动库包括 React Virtualized、vue-virtual-scroller 等。

增量渲染: 将数据分批次加载并逐步渲染到页面上,而不是一次性加载所有数据。这种方法可以减少页面阻塞时间和首屏加载时间,提高用户体验。你可以根据用户的滚动行为来触发数据的增量加载。

客户端缓存: 在前端使用客户端缓存技术,如浏览器缓存、LocalStorage 或 IndexedDB,缓存已经加载过的数据,减少重复请求和渲染。这样可以减少服务器负载,并提高页面加载速度。

数据压缩: 如果数据量很大,可以考虑对数据进行压缩,减小数据传输的大小,从而降低网络传输成本和加载时间。可以使用 gzip、Brotli 等压缩算法来压缩数据。

异步加载: 使用异步加载技术,如异步请求、Web Workers 等,将数据加载和渲染过程与主线程分离,避免阻塞页面渲染。这样可以提高页面的响应速度和性能。

综上所述,通过以上方法的组合使用,可以显著提高从后端请求到大量数据后的渲染性能,并实现更好的用户体验。

8、DocumentFragment文档碎片列表优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DocumentFragment 实现了文档碎片列表优化的主要原理是减少 DOM 操作的次数,从而提高页面渲染性能。具体来说,使用 DocumentFragment 可以将多个 DOM 操作合并成一次操作,减少了页面重绘和回流的次数,优化了页面的渲染性能。以下是 DocumentFragment 实现文档碎片列表优化的几个方面:

批量操作节点: 将多个节点添加到 DocumentFragment 中,然后一次性将 DocumentFragment 添加到文档中。由于添加到 DocumentFragment 的节点在内存中,并没有真正添加到文档中,因此这些操作不会触发页面的重绘和回流。当将 DocumentFragment 添加到文档中时,浏览器只需要一次性更新页面,从而减少了页面渲染的时间。

减少 DOM 操作: 使用 DocumentFragment 可以将多个 DOM 操作合并成一次操作,从而减少了页面的 DOM 操作次数。这样可以降低页面的渲染成本,提高页面的渲染效率。

减少重绘和回流: 页面的重绘和回流是由于 DOM 结构发生变化而导致的,使用 DocumentFragment 可以减少页面的重绘和回流次数。因为将多个 DOM 操作合并成一次操作,可以减少页面的 DOM 结构变化,从而减少了页面的重绘和回流。

优化动态列表: 在动态列表中,使用 DocumentFragment 可以优化性能。当需要频繁添加、删除或更新列表项时,可以将新的列表项添加到 DocumentFragment 中,然后一次性将 DocumentFragment 添加到列表中。这样可以减少页面的 DOM 操作次数,提高页面的渲染效率。

综上所述,DocumentFragment 实现了文档碎片列表优化的主要原理是减少 DOM 操作次数,降低页面的重绘和回流次数,从而提高了页面的渲染性能。通过合理地使用 DocumentFragment,可以优化页面的渲染过程,提高用户体验。
1、创建文档碎片: 首先,使用 document.createDocumentFragment() 方法创建一个新的文档碎片对象
2、将结点添加到文档碎片中:fragment.appendChild(div);
3、将文档碎片插入到文档中:document.body.appendChild(fragment);
当文档碎片中的节点准备好后,可以将整个文档碎片一次性地插入到文档中,而不是逐个插入节点。这样可以减少 DOM 操作的次数,提高性能。

9、Mutationobserver用于监控DOM的变化:使用场景例如:自己在做水印的时候,我们要防止别人把水印给删掉,那么我们就需要这个方法去监控到这个DOM被删除了,此时我们可以再去执行一下生成水印的方法。第二个就是这个方法还可以计算守屏时间

10、computed和watch的使用场景和区别:计算属性:多对一(缓存)监听属性:一对多。响应式的原理(双向绑定的原理)

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
33
34
35
36
37
38
39
40
41
Computed 属性
computed 属性用于声明一个计算属性,它的值是根据其他响应式数据计算而来的,只有当依赖的数据发生变化时,才会重新计算。

使用场景:
派生数据: 当需要根据其他响应式数据计算出一些派生数据时,通常可以使用 computed。例如,计算商品总价、过滤数据、格式化日期等。

缓存数据: computed 属性会缓存计算结果,在依赖数据不变的情况下,多次访问计算属性会返回缓存的结果,避免重复计算。

简化模板逻辑: 将一些复杂的逻辑抽象为计算属性,可以使模板更加简洁清晰,减少模板中的计算逻辑。

区别:
特性: computed 属性是基于依赖数据的计算属性,它的值是响应式的,会根据依赖数据的变化自动更新。

语法: 在 Vue.js 组件中使用 computed 属性时,可以像访问普通属性一样直接使用,而不需要在模板中添加函数调用。

Watcher 监听器
watch 监听器用于观察数据的变化并执行一些副作用,例如执行异步操作、发起网络请求、手动操作 DOM 等。

使用场景:
监听数据变化: 当需要监听某个数据的变化并进行一些操作时,通常可以使用 watch。例如,监听输入框的变化、监听路由参数的变化等。

异步操作: 在监听到数据变化后,需要执行一些异步操作时,可以使用 watch。例如,监听搜索关键词的变化,并发起搜索请求。

深度监听: watch 可以深度监听对象或数组的变化,可以在对象或数组发生深层次变化时执行回调函数。

区别:
特性: watch 监听器是用于观察数据的变化并执行副作用,它是一个函数,接收两个参数:新值和旧值。

语法: 在 Vue.js 组件中使用 watch 监听器时,需要在组件选项中使用 watch 属性,并指定需要监听的数据以及对应的回调函数。

总的来说,computed 适用于计算派生数据和简化模板逻辑,而 watch 适用于监听数据变化并执行副作用,例如异步操作和深度监听。在实际开发中,根据具体的需求选择合适的响应式方式来处理数据变化是非常重要的。

数据劫持(Data Observation): Vue.js 使用数据劫持技术来实现响应式数据。在组件初始化时,Vue.js 会递归遍历组件的所有属性,并使用 Object.defineProperty() 方法将每个属性转换为 getter 和 setter。这样当访问或修改属性时,Vue.js 将会捕获到这些操作,并触发相应的更新。

依赖追踪(Dependency Tracking): 当模板中使用了数据时,Vue.js 会建立数据与视图之间的依赖关系。每个响应式数据都会有一个依赖收集器,用于收集依赖该数据的所有 Watcher 对象(观察者)。当数据被访问时,Watcher 对象会将自己添加到数据的依赖收集器中。

发布订阅模式(Pub/Sub Pattern): 当响应式数据发生变化时,会通知所有依赖该数据的 Watcher 对象进行更新。这种通知机制是通过发布订阅模式实现的,即数据作为发布者,Watcher 对象作为订阅者,当数据发生变化时,发布者会向所有订阅者发送通知。

虚拟 DOM(Virtual DOM): Vue.js 使用虚拟 DOM 来提高渲染性能。当数据发生变化时,Vue.js 不会立即更新 DOM,而是将变化记录在虚拟 DOM 中,并进行比对。通过比对虚拟 DOM 的变化,Vue.js 可以找出最小的更新范围,并将更新应用到实际的 DOM 上,从而减少渲染次数和提高性能。

双向绑定(Two-way Data Binding): 双向绑定是 Vue.js 的另一个核心特性,它允许数据的变化能够自动反映到视图上,同时也允许视图上的变化能够自动同步到数据中。在双向绑定中,当数据发生变化时,会触发更新视图的操作;当视图发生变化时,会触发更新数据的操作。

11、dep和watcher互相收集

1
2
3
4
5
6
7
8
9
10
11
12

在 Vue.js 的响应式系统中,Dep(依赖收集器)和 Watcher(观察者)之间确实会相互收集。下面解释一下它们之间的关系:

Dep(依赖收集器): Dep 是一个依赖收集器,用于收集依赖于响应式数据的 Watcher 对象。每个响应式数据都会有一个对应的 Dep 对象,它是一个容器,用于存储依赖于该数据的 Watcher 对象。

Watcher(观察者): Watcher 是一个观察者对象,用于观察响应式数据的变化并执行相应的回调函数。每个 Watcher 对象都会关联一个或多个响应式数据,并在数据发生变化时收到通知并执行更新操作。

依赖收集过程: 在 Vue.js 的数据劫持过程中,当模板中使用了响应式数据时,会创建一个 Watcher 对象,并将该 Watcher 对象添加到对应数据的依赖收集器(Dep 对象)中。这样一来,当数据发生变化时,依赖于该数据的所有 Watcher 对象都会收到通知并执行更新操作。

Watchers 收集过程: 在模板编译过程中,当解析到模板中的表达式或指令时,会创建一个 Watcher 对象,并将其关联到对应的响应式数据。这样一来,当数据发生变化时,与之相关的 Watcher 对象就能够收到通知并执行更新操作。

通过这种方式,Dep 对象和 Watcher 对象之间建立了一个联系,实现了数据与视图之间的响应式绑定。当数据发生变化时,Dep 对象会通知所有依赖于该数据的 Watcher 对象进行更新,从而实现了数据的响应式更新和双向绑定。

12、diff算法

1
2
3
4
5
6
7
8
9
10
11
Vue.js 和 React 等现代 JavaScript 框架都使用了 Virtual DOM 和 diff 算法来提高页面的性能。下面是 diff 算法的基本原理:

虚拟 DOM(Virtual DOM): 在 diff 算法中,首先会通过 JavaScript 对象模拟出一颗虚拟 DOM 树,即 Virtual DOM。这颗虚拟 DOM 树和真实 DOM 树结构一样,但是只是存在于内存中,并没有实际渲染到页面上。

对比两棵树: 当数据发生变化时,会生成一棵新的虚拟 DOM 树。此时,会将新的虚拟 DOM 树和旧的虚拟 DOM 树进行对比,找出它们之间的差异。这个过程就是 diff 算法的核心。

更新差异节点: 在对比过程中,会将两棵树的节点逐个对比,找出新增、删除、移动或更新的节点。然后根据这些差异,生成一系列 DOM 操作指令(如插入、删除、更新等),并将这些指令应用到实际的 DOM 树上,从而实现页面的更新。

优化策略: 在执行 diff 算法时,会采用一些优化策略来提高性能。例如,diff 算法会尽量避免对整棵树进行完全对比,而是采用深度优先遍历的方式,尽早发现不同之处,并尽量减少不必要的 DOM 操作。

总的来说,diff 算法通过对比两棵虚拟 DOM 树的差异,找出需要更新的节点,并生成一系列更新指令,从而实现页面的高效更新。通过使用 diff 算法,可以最小化页面更新的开销,提高页面的渲染性能。

13、flex布局(blog)

14、ES6:箭头函数为什么不能作为构造函数

1
2
3
4
5
6
7
箭头函数不能作为构造函数的主要原因是因为箭头函数没有自己的 this 绑定。在普通函数中,this 的值是在函数被调用时动态确定的,而在箭头函数中,this 的值是由它定义时所在的词法作用域决定的,无法被改变。

在箭头函数中,this 的值是继承自外围作用域的,因此无法通过 new 关键字来创建一个新的对象并绑定到 this 上。如果尝试使用箭头函数作为构造函数来创建实例,会导致以下问题:

箭头函数中的 this 无法被修改,即使使用了 new 关键字也无效。
箭头函数没有自己的 prototype 属性,因此无法通过 new 关键字来创建原型链。
因此,为了避免混淆和错误,ECMAScript 6 标准规定箭头函数不能作为构造函数来使用。如果需要使用构造函数来创建对象,应该使用普通的函数声明或函数表达式。

15、promise相关的内容,解决回调地狱问题

16、需求:用promise封装了一个请求数据的函数,如果5s后还没请求到数据,就显示请求超时

1
2
3
你可以使用 Promise 的 Promise.race() 方法来实现这个功能。Promise.race() 方法接收一个 Promise 数组作为参数,并返回一个新的 Promise,该 Promise 将与第一个解决(或拒绝)的 Promise 相关联。

下面是一个示例代码,演示了如何使用 Promise.race() 来实现请求超时的功能:
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
function fetchData() {
return new Promise((resolve, reject) => {
// 模拟异步请求
setTimeout(() => {
// 模拟请求成功
resolve('Data received');
}, 3000); // 这里设置请求超时时间为3秒
});
}

function fetchWithTimeout() {
// 创建一个 Promise,用于控制请求超时
const timeoutPromise = new Promise((resolve, reject) => {
// 指定超时时间为5秒
setTimeout(() => {
reject(new Error('Request timed out'));
}, 5000);
});

// 使用 Promise.race() 方法,同时发起数据请求和超时控制的 Promise
return Promise.race([fetchData(), timeoutPromise]);
}

// 调用函数并处理结果
fetchWithTimeout()
.then(data => {
console.log(data); // 如果请求成功,则输出数据
})
.catch(error => {
console.error(error.message); // 如果请求超时或失败,则输出错误信息
});

在这个示例中,fetchData() 函数模拟了一个异步请求,实际项目中可以替换为真实的请求函数。然后,fetchWithTimeout() 函数使用 Promise.race() 方法将请求数据的 Promise 和超时控制的 Promise 组合在一起,以实现超时功能。如果请求在 3 秒内完成,则会正常返回数据;如果请求在 5 秒内仍未完成,则会触发超时,返回一个错误。

17、promise的all方法:请求所有都成功才会返回成功,如果有一个失败就会返回那个失败

18、promise的allSettled方法

Promise.allSettled() 是 ES2020 引入的一个静态方法,它接收一个 Promise 数组作为参数,返回一个新的 Promise,该 Promise 在所有输入的 Promise 都已经完成(即 resolved 或 rejected)后才会被 resolve。

不同于 Promise.all() 方法,Promise.allSettled() 不会在输入的 Promise 中有任何一个被拒绝(rejected)时立即中断,并且它返回的 Promise 的状态总是成功(resolved)。返回的 Promise 的结果是一个包含所有输入 Promise 的结果的数组,每个元素是一个对象,包含了该 Promise 的状态和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const promises = [
Promise.resolve(42),
Promise.reject(new Error('Error occurred')),
new Promise(resolve => setTimeout(() => resolve('Delayed result'), 1000))
];

Promise.allSettled(promises)
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Fulfilled:', result.value);
} else {
console.error('Rejected:', result.reason);
}
});
});

在这个示例中,promises 数组包含了三个 Promise 对象,其中一个是成功的,一个是失败的,还有一个是 1 秒后返回结果的 Promise。Promise.allSettled() 方法会等待所有的 Promise 都已经 settled(即 resolved 或 rejected)后,返回一个包含所有 Promise 的结果的数组。在 then 方法中,我们可以根据每个结果对象的 status 属性来判断对应的 Promise 是成功还是失败,并输出相应的信息。

19、async/await解决了什么问题是如何解决的

1
2
3
4
5
6
7
8
9
解决回调地狱(Callback Hell): 在传统的 JavaScript 异步编程中,经常会出现多层嵌套的回调函数,这种情况被称为“回调地狱”。回调地狱使得代码难以理解、难以维护,而且容易导致代码混乱、出错。async/await 通过使用 async 函数和 await 关键字,可以让异步代码以同步的方式书写,从而避免了回调地狱的问题,使代码更加清晰易读。

优化 Promise 的链式调用: 在使用 Promise 进行异步编程时,经常会使用链式调用的方式来处理多个异步操作。虽然 Promise 提供了优雅的解决方案,但是在处理多个异步操作时,仍然需要嵌套多层 then() 方法,导致代码复杂度增加。async/await 可以让异步操作像同步操作一样被执行,从而简化了 Promise 的链式调用。

使用 async/await 的基本原理是:

async 函数用来定义一个异步函数,它会返回一个 Promise 对象。在 async 函数内部,可以使用 await 关键字来等待其他 Promise 对象的结果,而不需要使用 then() 方法。
await 关键字可以暂停异步函数的执行,等待 Promise 对象的状态变为 resolved 后,将 Promise 的 resolved 值作为 await 表达式的返回值,并继续执行 async 函数。
通过使用 async/await,可以让异步代码的书写方式更加简洁、清晰,提高了代码的可读性和可维护性,同时也解决了回调地狱和 Promise 链式调用的问题。

20、图片和路由懒加载、防抖、节流、瀑布流(blog)

21、取消重复请求的问题,响应拦截器和请求拦截器

取消重复请求是指在发起一个网络请求之前,先检查当前是否已经有相同的请求在进行中,如果是,则取消之前的请求,只保留最新的请求。这样可以避免因为重复请求而导致的资源浪费和性能问题。

在前端开发中,可以通过拦截器(interceptors)来实现取消重复请求的功能。通常,拦截器分为请求拦截器和响应拦截器。

  • 请求拦截器(Request Interceptors): 请求拦截器用于在发送请求之前对请求进行处理,例如添加公共参数、设置请求头等。在实现取消重复请求的功能时,可以在请求拦截器中判断当前是否存在相同的请求正在进行,如果存在,则取消之前的请求。这样可以确保每次只有一个请求被发送到服务器。

  • 响应拦截器(Response Interceptors): 响应拦截器用于在接收到响应之后对响应进行处理,例如处理响应数据、错误处理等。在实现取消重复请求的功能时,可以在响应拦截器中处理请求的结果,例如保存请求的结果或者清除请求状态。

以下是一个简单的示例代码,演示了如何使用 Axios(一个流行的 HTTP 客户端库)和拦截器来实现取消重复请求的功能:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import axios from 'axios';

// 创建一个 Axios 实例
const instance = axios.create();

// 定义一个变量用于保存当前正在进行的请求
let pendingRequest = null;

// 请求拦截器
instance.interceptors.request.use(
config => {
// 如果存在正在进行的请求,则取消之前的请求
if (pendingRequest) {
pendingRequest.cancel('Request canceled due to new request');
}
// 创建一个新的取消令牌
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
config.cancelToken = source.token;
pendingRequest = source;
return config;
},
error => {
return Promise.reject(error);
}
);

// 响应拦截器
instance.interceptors.response.use(
response => {
// 处理响应数据
return response;
},
error => {
// 处理请求失败
return Promise.reject(error);
}
);

// 发起请求
instance.get('/api/data')
.then(response => {
console.log('Response:', response.data);
})
.catch(error => {
console.error('Error:', error.message);
});

在这个示例中,我们创建了一个 Axios 实例,并使用请求拦截器和响应拦截器来实现取消重复请求的功能。在请求拦截器中,我们判断当前是否存在正在进行的请求,如果存在,则取消之前的请求,并创建一个新的取消令牌。这样可以确保每次只有一个请求被发送到服务器。

22、cookie和本地浏览器缓存有什么区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Cookie 和本地浏览器缓存(LocalStorage 和 SessionStorage)都是用于在客户端存储数据的机制,但它们之间有一些区别,主要包括以下几点:

存储位置:

Cookie: Cookie 是存储在客户端的一种数据,它是由服务器发送给浏览器的一小段文本信息,每次请求时都会自动携带到服务器。Cookie 的大小一般受到限制(通常为 4KB 左右)。
本地浏览器缓存: 本地浏览器缓存包括 LocalStorage 和 SessionStorage,它们都是存储在浏览器中的一种持久化的存储方式,可以用来保存较大量的数据(一般为几 MB)。
生命周期:

Cookie: Cookie 可以设置过期时间,可以是会话级的(浏览器关闭时失效)、永久的(过期时间为未来某个时间点)或者一段时间后过期。如果不设置过期时间,那么 Cookie 将默认为会话级,即在浏览器关闭时失效。
本地浏览器缓存: LocalStorage 中的数据是永久保存的,除非用户手动删除,否则数据将一直保留在浏览器中;而 SessionStorage 中的数据只在当前会话(浏览器窗口)有效,关闭窗口后数据将被清除。
与服务器通信:

Cookie: 每次向服务器发起请求时,会将相应的 Cookie 自动发送给服务器,因此 Cookie 可以用来在客户端和服务器之间传递数据。
本地浏览器缓存: 本地浏览器缓存的数据仅在客户端存储,不会自动发送给服务器,它们主要用于在客户端保存用户数据或应用状态。
安全性:

Cookie: 由于 Cookie 是存储在客户端的,因此可能受到跨站脚本(XSS)和跨站请求伪造(CSRF)等安全攻击的影响。为了增强安全性,可以将 Cookie 设置为 HttpOnly,使得 Cookie 无法被 JavaScript 访问。
本地浏览器缓存: 本地浏览器缓存的数据只能通过 JavaScript 访问,不会被发送给服务器,因此相对于 Cookie 更安全。
总的来说,Cookie 用于在客户端和服务器之间传递数据,并且具有较小的存储容量和可设置的生命周期,而本地浏览器缓存用于在客户端保存用户数据或应用状态,并且具有较大的存储容量和持久性。在实际开发中,根据具体的需求和安全考虑,选择合适的存储机制非常重要。

23、Vue3在Vue2上的优化,以及是如何优化的(blog)

24、JS闭包(blog)

25、git的基本使用方法(blog)

26、JS全局作用域的特殊情况和var,let,const三者的特点和区别(blog)

27、JS预解析(blog)

28、数组去重(blog)

29、JS的7种基本数据类型

1
Boolean、null、undefined、Number、String、Symbol(一种实例是唯一且不可改变的数据类型)、BigInt、Object

30、null和undefine的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在 JavaScript 中,null 和 undefined 是两种不同的特殊值,它们有着不同的含义和用法,下面是它们的区别:

null:

null 表示一个空值或者不存在的对象。
当一个变量被赋值为 null 时,表示该变量的值为空。
通常情况下,null 是由程序员显式地赋值给变量的,表示清空或者重置某个变量。
例如,let myVar = null;
undefined:

undefined 表示一个未定义的值,即一个变量已经声明但没有被赋值。
在 JavaScript 中,变量声明但未赋值时,默认值为 undefined。
当访问一个未初始化的变量时,其值为 undefined。
例如,let myVar; console.log(myVar); // 输出 undefined
在使用上,null 通常表示主动赋值为空,而 undefined 表示变量未定义或者未初始化。另外,它们的数据类型也不同,null 是一个对象,而 undefined 是一个原始值。

31、ES6新增了那些数组方法(blog)

32、JS种常用容器的使用(blog)

33、DOM事件流(blog)

34、e.target和this的区别(blog)

35、阻止事件冒泡和默认行为(blog)

36、事件委托(blog)

37、this的指向问题(blog)

38、JS的执行机制(blog)

39、JS事件循环(eventloop)

1
同步任务最先执行,由上到下执行的过程中如果遇到异步任务就放在 任务队列 最后排队,同步任务执行完成之后,优先执行所有的微任务,微任务执行完成之后,按顺序执行微任务,如此反复循环,事件循环。

40、宏任务和微任务(blog)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
什么是宏任务什么是微任务?
在 JavaScript 中,事件循环(Event Loop)是管理异步任务执行的机制。事件循环将任务分为两种类型:宏任务(MacroTask)和微任务(MicroTask)。
宏任务(MacroTask):
宏任务代表着一个独立的、完整的执行单元,它可以包含多个子任务,例如:定时器回调函数、事件回调函数、IO 操作等。
宏任务包括了整体代码块、setTimeout、setInterval、setImmediate、I/O、UI 渲染等任务。
宏任务会被放入宏任务队列中等待执行,只有当执行栈为空时,事件循环才会从宏任务队列中选择一个宏任务执行。
微任务(MicroTask):
微任务是相对于宏任务而言的,它是在当前任务执行结束后立即执行的任务。
微任务包括了 Promise 回调函数和 MutationObserver 回调函数等。
微任务会在当前任务执行完毕并将结果返回给调用者之后立即执行,而不需要等待宏任务队列中的其他任务执行完毕。
微任务的执行时机在每个宏任务执行完成后,在下一个宏任务执行之前。
事件循环的执行流程大致如下:
执行一个宏任务(如执行整体代码、定时器回调等)。
执行过程中,遇到微任务,将其添加到微任务队列中。
当前宏任务执行完毕后,检查是否存在微任务队列,如果存在,则依次执行所有微任务。
执行完所有微任务后,如果存在其他宏任务,从宏任务队列中选择一个宏任务继续执行,重复以上步骤。
通过微任务和宏任务的结合,可以实现一些复杂的异步任务调度和控制逻辑,同时也能够确保任务的及时执行和顺序性。

微任务的执行时机比微任务早
常见宏任务:setTimeout, setInterval, DOM事件,AJAX请求
常见微任务:Promise, async/await
为什么宏任务比微任务先执行呢?
微任务 先于 DOM渲染 先于 宏任务

41、原型和原型链(blog)

42、webSocket的基本使用(blog)

43、什么是同步什么是异步以及二者的区别

1
2
3
4
5
常见的异步编程:
fs文件操作:require('fs').readFile('./index.html', (err,data)=>{})
数据库操作
AJAX:$.get('/server', (data)=>{})
定时器:setTimeout(()=>{}, 2000)

44、生成随机数的函数

1
2
3
4
5
6
7
//生成m~n的随机数
function rand(m,n) {
return Math.ceil(Math.random() * (n-m) + m)//常用
/**或者
return Math.floor(Math.random() * (n-m+1) + m)
*/
}

45、TCP和IP、三次握手四次挥手

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
TCP(Transmission Control Protocol,传输控制协议)和 IP(Internet Protocol,互联网协议)是互联网中两个重要的网络协议,它们共同构成了TCP/IP协议栈,用于实现网络通信。

IP(Internet Protocol):

IP 是一种网络层协议,负责在网络中传输数据包。
IP 协议使用 IP 地址来唯一标识网络中的设备,通过路由器将数据包从源地址传输到目标地址。
IP 协议是不可靠的,它只负责数据包的传输,不保证数据包的可靠性、顺序性和完整性。
TCP(Transmission Control Protocol):

TCP 是一种传输层协议,建立在 IP 协议之上,提供可靠的、面向连接的通信服务。
TCP 协议通过三次握手来建立连接,四次挥手来释放连接,保证数据传输的可靠性和顺序性。
TCP 协议提供了流量控制、拥塞控制、重传机制等功能,确保数据的可靠传输。
下面是 TCP 连接的建立(三次握手)和释放(四次挥手)过程:

三次握手(TCP 连接建立):

1.1.客户端发送 SYN(同步)报文给服务器,并进入 SYN_SENT 状态。
1.2.服务器收到 SYN 报文后,返回 SYN+ACK(同步确认)报文给客户端,并进入 SYN_RECV 状态。
1.3.客户端收到 SYN+ACK 报文后,发送 ACK(确认)报文给服务器,完成三次握手,建立连接,并进入 ESTABLISHED 状态。
四次挥手(TCP 连接释放):

2.1.客户端发送 FIN(结束)报文给服务器,并进入 FIN_WAIT_1 状态。
2.2.服务器收到 FIN 报文后,返回 ACK 报文给客户端,并进入 CLOSE_WAIT 状态。
2.3.服务器处理完数据后,发送 FIN 报文给客户端,并进入 LAST_ACK 状态。
2.4.客户端收到 FIN 报文后,返回 ACK 报文给服务器,进入 TIME_WAIT 状态,等待 2MSL(最长报文段寿命) 后,进入 CLOSED 状态。
2.5.服务器收到 ACK 报文后,进入 CLOSED 状态。
通过三次握手建立连接和四次挥手释放连接,TCP 协议确保了数据的可靠传输和连接的可靠建立和释放。

46、浏览器的渲染原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
浏览器的渲染过程涉及多个步骤,包括从服务器获取HTML、解析HTML、构建DOM树、构建渲染树、布局(Layout)和绘制(Painting)等。以下是浏览器渲染的基本流程:

1.获取HTML: 浏览器通过网络请求从服务器获取 HTML 文件。

2.解析HTML: 浏览器将获取到的 HTML 文件解析成 DOM 树(Document Object Model,文档对象模型),DOM 树表示了 HTML 文档的结构和内容。

3.构建渲染树: 浏览器根据 DOM 树构建渲染树(Render Tree),渲染树是由 DOM 树中的可见元素(如 div、span、img 等)和 CSS 样式表中的样式信息组成的,它忽略了不可见的元素(如 head、script 等)和设置了 display: none 的元素。

4.布局(Layout): 浏览器计算渲染树中每个元素在页面中的位置和大小,这个过程称为布局或回流(Reflow)。布局过程涉及到页面中的盒模型、浮动、定位、文本方向等属性的计算,以及页面的自适应和响应式布局等。

5.绘制(Painting): 浏览器使用布局阶段计算得到的位置和大小信息,将渲染树中的每个元素转换成页面上的实际像素,绘制在屏幕上。这个过程称为绘制或重绘(Repaint),涉及到页面的像素填充、边框绘制、文本渲染等。

6.合成(Composite): 最后,浏览器将绘制好的图层按照合适的顺序合成到页面的显示缓冲区中,生成最终的页面视图。这个过程称为合成或合成器(Compositor)。

以上是浏览器渲染的基本流程,不同浏览器可能会有一些优化和特殊的处理方式,但整体的渲染原理大致相同。渲染过程的优化和提升对于页面的性能和用户体验至关重要,因此开发者需要在编写代码时注意页面的结构、样式和脚本的性能,以便提高页面的渲染速度和流畅度。

47、什么是MVVM、MVC模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

MVVM(Model-View-ViewModel)和 MVC(Model-View-Controller)是两种常见的软件架构模式,用于组织和管理应用程序的结构。它们都将应用程序分成三个主要部分:模型(Model)、视图(View)和控制器(Controller)或视图模型(ViewModel),但它们在设计和实现上有所不同。

MVC(Model-View-Controller)模型:

MVC 是一种基于分层的软件架构模式,将应用程序分成三个独立的部分:模型(Model)、视图(View)和控制器(Controller)。
模型(Model)负责存储数据和业务逻辑,提供对数据的操作接口。
视图(View)负责展示用户界面,并将用户的操作传递给控制器。
控制器(Controller)负责接收用户的输入,处理用户的请求,调用模型进行数据操作,并更新视图。
控制器充当了模型和视图之间的中介,将两者解耦,提高了代码的可维护性和可测试性。
MVVM(Model-View-ViewModel)模型:

MVVM 是一种基于数据绑定的软件架构模式,它是在 MVC 模式的基础上演变而来的。
MVVM 将应用程序分成三个部分:模型(Model)、视图(View)和视图模型(ViewModel)。
模型(Model)和 MVC 中的模型相同,负责存储数据和业务逻辑。
视图(View)和 MVC 中的视图相同,负责展示用户界面。
视图模型(ViewModel)是 MVVM 中的新角色,它是视图的抽象,负责封装视图的状态和行为,并与模型进行交互。视图模型通过数据绑定将视图与模型解耦,使视图的更新更加简洁和高效。
MVVM 中的视图模型充当了控制器的角色,但它与视图紧密绑定,能够更好地处理视图中的逻辑和状态。
总的来说,MVC 模式强调分层和分离,而 MVVM 模式强调数据绑定和视图模型。选择合适的模式取决于应用程序的复杂度、团队的技术水平和个人的偏好。

48、Vue双向数据绑定的原理

49、Vue的生命周期有哪些(blog)

50、常见的数组方法有哪些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined 。
findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true, 否则返回 false。
indexOf() 方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。 (通常用它判断数组中有没有这个元素)
join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。 如果数组只有一个项目,那么将返回该项目而不使用分隔符。
pop() 方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度。
push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。
unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内 容。此方法会改变原数组。 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删 除元素,则返回空数组。
slice() 方法同上,但不会改变原数组
reverse() 方法将数组中元素的位置颠倒,并返回该数组。该方法会改变原数组。
sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字 符串,然后比较它们的 UTF-16 代码单元值序列时构建的

51、什么是原型链

1
每一个实例对象上有一个proto 属性,指向的构造函数的原型对象,构造函数的原型 对象也是一个对象, 也有 proto 属性,这样一层一层往上找的过程就形成了原型链。

52、常见的继承有哪些

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
一、原型链继承
特点: 1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新 实例不会继承父类实例的属性!
缺点: 1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属 性,另一个实例的原 型属性也会被修改!)
二、借用构造函数继承
重点: 用 .call()和.apply() 将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复 制))
特点: 1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点 1、2、3。
3、可以继承多个构造函数属性(call 多个)。
4、在子实例中可向父实例传参。
缺点: 1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
三、组合继承(组合原型链继承和借用构造函数继承)(常用)
重点: 结合了两种模式的优点,传参和复用
特点: 1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点: 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函 数。
四、原型式继承
重点: 用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的 实例或对象。object.create()就是这个原理。
特点: 类似于复制一个对象,用函数来包装。
缺点: 1、所有实例都会继承原型上的属性。 2、无法实现复用。(新实例属性都是后面添加的)
五、class 类实现继承
通过 extends 和 super 实现继承
六、寄生式继承
重点: 就是给原型式继承外面套了个壳子。
优点: 没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成 了创建的新对象。
缺点: 没用到原型,无法复用。

53、后台管理系统中的权限管理是怎么实现的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
登录: 当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个 token,
拿到 token 之后(我会将这个 token 存贮到 cookie 中,保证刷新页面后能记住用户登录状态),前端会 根据 token 再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
权限验证: 通过 token 获取用户对应的 权限,动态根据用户的 权限算出其对应有权限的路由,通过 router.addRoutes 动态挂载这些路由。
具体思路:
登录成功后,服务端会返回一个 token(该 token 的是一个能唯一标示用户身份的一个 key),之后我 们将 token 存储在本地 cookie 之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不 用再去登录页面重新登录了。
ps:为了保证安全性,我司现在后台所有 token 有效期(Expires/Max-Age)都是 Session,就是当浏览器关 闭了就丢失了。重新打开游览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新 token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账 号。
用户登录成功之后,我们会在全局钩子 router.beforeEach 中拦截路由,判断是否已获得 token,在 获得 token 之后我们就要去获取用户的基本信息了 页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有 token, 就会把这个 token 返给后端去拉取 user_info,保证用户信息是最新的。 当然如果是做了单点登录得功 能的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登 录获取最新的内容。
先说一说我权限控制的主体思路,前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登 录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过 router.addRoutes 动态挂载路由。但这些控制都只是页面级的,说白了前端再怎么做权限控制都不是 绝对安全的,后端的权限验证是逃不掉的。
我司现在就是前端来控制页面级的权限,不同权限的用户显示不同的侧边栏和限制其所能进入的页面(也 做了少许按钮级别的权限控制),后端则会验证每一个涉及请求的操作,验证其是否有该操作的权限,每 一个后台的请求不管是 get 还是 post 都会让前端在请求 header 里面携带用户的 token,后端会根据 该 token 来验证用户是否有权限执行该操作。若没有权限则抛出一个对应的状态码,前端检测到该状 态码,做出相对应的操作。 使用 vuex 管理路由表,根据 vuex 中可访问的路由渲染侧边栏组件。
具体实现:
创建 vue 实例的时候将vue-router挂载,但这个时候 vue-router 挂载一些登录或者不用权限的公用的页 面。
当用户登录后,获取用role,将 role 和路由表每个页面的需要的权限作比较,生成最终用户可访问的路 由表。
调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
使用vuex管理路由表,根据 vuex 中可访问的路由渲染侧边栏组件。

54、es6 有哪些新特性?

1
2
3
4
5
6
ES6 是 2015 年推出的一个新的版本、这个版本相对于 ES5 的语法做了很多的优化、例如:新增了let、 const
let 和 const具有块级作用域,不存在变量提升的问题。新增了箭头函数,简化了定义函数的写法,
同时 可以巧用箭头函数的 this、(注意箭头函数本身没有 this,它的 this 取决于外部的环境),
新增了promise 解决了回调地域的问题,新增了模块化、利用 import 、export 来实现导入、导出。
新增了结构赋值, ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构 (Destructuring)。
新增了class 类的概念,它类似于对象。

55、v-for 循环为什么一定要绑定 key ?

1
页面上的标签都对应具体的虚拟 dom 对象(虚拟 dom 就是 js 对象), 循环中 ,如果没有唯一 key , 页面上删除 一条标签, 由于并不知道删除的是那一条! 所以要把全部虚拟 dom 重新渲染, 如果知道 key 为 x 标签被删除 掉, 只需要把渲染的 dom 为 x 的标签去掉即可!

56、平时都是用什么实现跨域的?

1
2
3
4
5
6
7
8
9
jsonp: 利用 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP 请求一 定需要对方的服务器做支持才可以。
JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具 有局限性,不安全可能会遭受 XSS 攻击。
声明一个回调函数,其函数名(如 show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获 取目标数据(服务器返回的 data)。 创建一个 <script> 标签,把那个跨域的 API 数据接口地址,赋值给 script 的 src,还要在这个地址中向服 务器传递该函数名(可以通过问号传参:?callback=show)。
服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符 串,例如:传递进去的函数名是 show,它准备好的数据是 show('我不爱你') 。
最后服务器把准备的数据通过 HTTP 协议返回给客户端,客户端再调用执行之前声明的回调函数 (show),对返回的数据进行操作。
CORS:跨域资源共享(CORS)是一种机制;当一个资源访问到另外一个资源(这个资源放在 不同的域名或者不同的协议或者端口),资源就会发起一个跨域的 HTTP 请求需要浏览器和服务器同时支持;
1.整个 CORS 通信,都是浏览器自动完成。浏览器发现了 AJAX 请求跨源,就会自动添加一些附加的头 信息,有时还会多出一次附加的请求,但用户不会有感觉;
2.实现 CORS 的关键是服务器,只要服务器实现了 CORS 接口,就可以跨源通信
3.服务器对于不同的请求,处理方式不一样; 有简单请求和非简单请求

57、this 的指向有哪些?

1
2
3
4
5
6
7
1、普通函数中的 this 指向 window
2、定时器中的 this 指向 window
3、箭头函数没有 this,它的 this 指向取决于外部环境、
4、事件中的 this 指向事件的调用者 黑马程序员
5、 构造函数中 this 和原型对象中的 this,都是指向构造函数 new 出来实例对象
6、类 class 中的 this 指向由 constructor 构造器 new 出来的实例对象
7、自调用函数中的 this 指向 window

58、谈谈你平时都用了哪些方法进行性能优化?

1
减少 http 请求次数、打包压缩上线代码、使用懒加载、使用雪碧图、动态渲染组件、CDN 加载包。

59、vue 实例是挂载到那个标签上的?

1
vue 实例最后会挂载在body 标签里面,所以我们在 vue 中是获取不了 body 标签的,如果要使用 body 标 签的话需要用原生的方式获取

60、请写至少三种数组去重的方法?(原生 js)

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
33
//利用filter
function unique(arr) {
return arr.filter(function (item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index
})
}
var arr = [
1,
1,
'true',
'true',
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
'NaN',
0,
0,
'a',
'a',
{},
{}
]
console.log(unique(arr))
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
//利用ES6 Set去重(ES6中最常用)
function unique(arr) {
return Array.from(new Set(arr))
}
var arr = [
1,
1,
'true',
'true',
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
'NaN',
0,
0,
'a',
'a',
{},
{}
]
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
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
33
34
35
36
37
38
39
40
41
//利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i] == arr[j]) {
//第一个等同于第二个,splice方法删除第二个
arr.splice(j, 1)
j--
}
}
}
return arr
}
var arr = [
1,
1,
'true',
'true',
true,
true,
15,
15,
false,
false,
undefined,
undefined,
null,
null,
NaN,
NaN,
'NaN',
0,
0,
'a',
'a',
{},
{}
]
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]
//NaN和{}没有去重,两个null直接消失了

61、请写出至少两种常见的数组排序的方法(原生 js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//快速排序
function quickSort(elements) {
if (elements.length <= 1) {
return elements
}
var pivotIndex = Math.floor(elements.length / 2)
var pivot = elements.splice(pivotIndex, 1)[0]
var left = []
var right = []
for (var i = 0; i < elements.length; i++) {
if (elements[i] < pivot) {
left.push(elements[i])
} else {
right.push(elements[i])
}
}
return quickSort(left).concat([pivot], quickSort(right))
//concat()方法用于连接两个或者多个数组;该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
}
var elements = [3, 5, 6, 8, 2, 4, 7, 9, 1, 10]
document.write(quickSort(elements))
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
//插入排序
function sort(elements) {
// 假设第0个元素是一个有序数列,第1个以后的是无序数列,
// 所以从第1个元素开始将无序数列的元素插入到有序数列中去
for (var i = 1; i <= elements.length; i++) {
// 升序
if (elements[i] < elements[i - 1]) {
// 取出无序数列中的第i个作为被插入元素
var guard = elements[i]
//记住有序数列的最后一个位置,并且将有序数列的位置扩大一个
var j = i - 1
elements[i] = elements[j]
// 比大小;找到被插入元素所在位置
while (j >= 0 && guard < elements[j]) {
elements[j + 1] = elements[j]
j--
}
elements[j + 1] = guard //插入
}
}
}
var elements = [3, 5, 6, 8, 2, 4, 7, 9, 1, 10]
document.write('没调用之前:' + elements)
document.write('<br>')
sort(elements)
document.write('被调用之后:' + elements)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//冒泡排序
function sort(elements) {
for (var i = 0; i < elements.length - 1; i++) {
for (var j = 0; j < elements.length - 1 - i; j++) {
if (elements[j] > elements[j + 1]) {
var swap = elements[j]
elements[j] = elements[j + 1]
elements[j + 1] = swap
}
}
}
}
var elements = [3, 5, 6, 8, 2, 4, 7, 9, 1, 10]
console.log('before' + elements)
sort(elements)
console.log('after' + elements)
1
//选择排序

62、清除浮动的方法有哪些?

为什么要清除浮动,因为浮动的盒子脱离标准流,如果父盒子没有设置高度的话,下面的盒子就会撑上 来。

1.额外标签法(在最后一个浮动标签后,新加一个标签,给其设置 clear:both;)(不推荐)

2.父级添加 overflow 属性(父元素添加 overflow:hidden)(不推荐)

3.使用 after 伪元素清除浮动(推荐使用)

1
2
3
4
5
6
7
8
9
10
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: block;
height: 0;
clear:both;
visibility: hidden;
}
.clearfix{
*zoom: 1;/*ie6清除浮动的方式 *号只有IE6-IE7执行,其他浏览器不执行*/

4.使用 before 和 after 双伪元素清除浮动

1
2
3
4
5
6
7
8
9
10
11
.clearfix:after,
.clearfix:before {
content: '';
display: table;
}
.clearfix:after {
clear: both;
}
.clearfix {
*zoom: 1;
}

63、ES6中的class类

在 ES6 中,引入了 class 关键字,使得 JavaScript 可以更加方便地实现面向对象编程(OOP)的特性。class 关键字提供了一种更加简洁、清晰的语法来定义类和创建对象,它是一种语法糖,基于原型继承的底层机制。

下面是使用 class 关键字定义类的基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClassName {
constructor(parameters) {
// 构造函数,在创建对象时调用,用于初始化对象的状态
this.property = parameters;
}

method1() {
// 类方法
}

method2() {
// 类方法
}

// 其他方法...
}
  • class 关键字用于定义一个类,类名通常采用大驼峰命名规范。
  • constructor 方法是一个特殊的方法,在创建对象时被调用,用于初始化对象的状态,可以接收参数。
  • 类中的方法可以直接定义在类体内,方法之间不需要使用逗号分隔。
  • 使用 new 关键字和类名可以创建类的实例对象,调用构造函数初始化对象的状态。
  • 类方法可以直接在实例对象上调用,用于操作实例对象的状态和行为。

除了构造函数和普通方法之外,类还支持静态方法(static methods)和 getter/setter(存取器),用于定义与类本身关联的方法和属性。

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
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}

static greet() {
console.log('Hello!');
}

get name() {
return this._name;
}

set name(value) {
this._name = value;
}

sayHello() {
console.log(`Hello, my name is ${this._name} and I'm ${this._age} years old.`);
}
}

const person = new Person('Alice', 30);
person.sayHello(); // 输出: Hello, my name is Alice and I'm 30 years old.
Person.greet(); // 输出: Hello!

在上面的示例中,Person 类定义了一个构造函数和两个实例方法(sayHello)、一个静态方法(greet)、一个 getter 和一个 setter。可以通过 new 关键字创建 Person 类的实例对象,并调用实例方法和静态方法。

64、谈谈盒子模型?

1
2
3
在标准盒子模型中,width 和 height 指的是内容区域的宽度和高度。
增加内边距、边框和外边距不会 影响内容区域的尺寸,但是会增加元素框的总尺寸。
IE 盒子模型中,width 和 height 指的是内容区域+border+padding的宽度和高度。

65、箭头函数有哪些特征,请简单描述一下它?

1
2
3
箭头函数没有自己的 this,this 指向定义箭头函数时所处的外部执行环境的 this
即使调用call/apply/bind也无法改变箭头函数的 this 箭头函数本身没有名字 箭头函数不能 new,会报错
箭头函数没有 arguments,在箭头函数内访问这个变量访问的是外部执行环境的 arguments 箭头函数没有 prototype

66、移动端点击事件的300ms延迟的问题(blog)

67、什么是同源策略?

1
所谓同源策略是浏览器的一种安全机制,来限制不同源的网站不能通信。同源就是域名、协议、端口一 致。

68、http 状态码分别代表什么意思?

1
2
3
4
5
1xx 表示 HTTP 请求已经接受,继续处理请求
2xx 表示 HTTP 请求已经处理完成(200)
3xx 表示把请求访 问的 URL 重定向到其他目录(304 资源没有发生变化,会重定向到本地资源)
4xx 表示客户端出现错误 (403 禁止访问、404 资源不存在)
5xx 表示服务端出现错误

69、安全问题 :CSRF 和 XSS 攻击?

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
CSRF ( Cross-site request forgery ):跨站请求伪造。
方法一、Token 验证:(用的最多)

服务器发送给客户端一个 token ;
客户端提交的表单中带着这个 token 。
如果这个 token 不合法,那么服务器拒绝这个请求。

方法二:隐藏令牌:
把 token 隐藏在 http 的 head 头中。
方法二和方法一有点像,本质上没有太大区别,只是使用方式上有区别。
方法三、Referer 验证:
Referer 指的是页面请求来源。意思是,只接受本站的请求,服务器才做响应;
如果不是,就拦截 XSS(Cross Site Scripting):跨域脚本攻击。
1.编码:
对用户输入的数据进行 HTML Entity 编码。 如上图所示,把字符转换成 转义字符。 Encode 的作用是将 $var`等一些字符进行转化,使得浏览器在最终输出结果上是一样的

若不进行任何处理,则浏览器会执行 alert 的 js 操作,实现 XSS 注入。进行编码处理之后,L 在浏览
器中的显示结果就是 <script>alert(1)</script> ,实现了将 `$var 作为纯文本进行输出,且
不引起 J avaScript 的执行。

2.过滤:
移除用户输入的和事件相关的属性。如 onerror 可以自动触发攻击,还有 onclick 等。(总而言是,过滤掉一些不安全的内容)
移除用户输入的 Style 节点、 Script 节点、 Iframe 节点。(尤其是 Script 节点,它可是支持跨域的呀,一定要移除)。
3.校正:
避免直接对 HTML Entity 进行解码。
使用 DOM Parse 转换,校正不配对的 DOM 标签。
备注: 我们应该去了解一下 DOM Parse 这个概念,它的作用是把文本解析成 DOM 结构。 比较常用的做法是,通过第一步的编码转成文本,然后第三步转成 DOM 对象,然后经过第二步的过滤。

70、CSRF 和 XSS 的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
区别一:


CSRF :需要用户先登录网站 A ,获取 cookie


XSS :不需要登录。


区别二:(原理的区别)


CSRF :是利用网站 A 本身的漏洞,去请求网站 A 的 api 。


XSS :是向网站 A 注入 JS 代码,然后执行 JS 里的代码,篡改网站 A 的内容。

71、call、apply、bind 三者的异同

1
2
3
4
5
6
共同点 : 都可以改变 this 指向;
不同点: call 和 apply 会调用函数, 并且改变函数内部 this 指向. call 和 apply传递的参数不一样,call 传递参数使用逗号隔开,apply 使用数组传递 bind 不会调用函数, 可以改变函 数内部 this 指向. 应用场景

call 经常做继承.
apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
bind 不调用函数,但是还想改变 this 指向. 比如改变定时器内部的 this 指向

72、状态码304

1
2
3
4
5
6
7
8
9
10
11
服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制。当客户端再次请求页面时,服务器会判断请求的页面是否已被缓存,若已经被缓存则返回304,此时客户端将调用缓存内容。

状态码304不应该被认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。

产生较多304状态码的原因是什么?
页面更新周期长或者长时间未更新
纯静态页面或强制生成静态HTML
304状态码过多会造成什么问题?
网站快照停止
收录减少
权重下降

73、exports、module.exports和JS模块加载机制(blog)

74、运算规则

75、类型转换(npm install hexo-markmap)

76、移动端和PC端如何实现屏幕适配

移动端屏幕适配:

  1. Viewport Meta标签:

    1
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    这个标签告诉浏览器使用设备的宽度作为视口宽度,并且默认缩放比例为1.0。

  2. 媒体查询(Media Queries):

    1
    2
    3
    4
    5
    @media only screen and (max-width: 600px) {
    body {
    font-size: 14px;
    }
    }

    这段CSS代码将在屏幕宽度小于或等于600px时,将body元素的字体大小设置为14px。

  3. 弹性布局(Flexbox):

    1
    2
    3
    4
    .container {
    display: flex;
    flex-direction: column;
    }

    这个例子将一个容器内的子元素以垂直方向排列,适应不同尺寸的移动设备屏幕。

  4. REM和EM单位:

    1
    2
    3
    4
    5
    6
    body {
    font-size: 16px; /* 假设根元素的基础字体大小为16px */
    }
    .element {
    font-size: 1.2rem; /* 相对于根元素的字体大小,这里相当于19.2px */
    }

    这个例子中,使用REM单位相对于根元素的字体大小进行设置。

PC端屏幕适配:

  1. 媒体查询(Media Queries):

    1
    2
    3
    4
    5
    6
    @media only screen and (min-width: 1024px) {
    .container {
    width: 960px;
    margin: 0 auto;
    }
    }

    这段CSS代码在屏幕宽度大于等于1024px时,将容器的宽度设置为960px,并水平居中显示。

  2. 百分比布局:

    1
    2
    3
    .element {
    width: 50%; /* 相对于父元素宽度的50% */
    }

    这个例子中,元素的宽度被设置为其父元素宽度的50%。

  3. 最大宽度设置:

    1
    2
    3
    4
    .element {
    max-width: 1200px; /* 最大宽度为1200px */
    width: 100%; /* 如果屏幕宽度小于1200px,则宽度为100% */
    }

    这个例子中,元素的最大宽度被设置为1200px,但如果屏幕宽度小于这个值,则会自动调整为100%。

  4. 栅格系统:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="container">
    <div class="row">
    <div class="col-md-6">
    <!-- 这个列会占据父容器的一半宽度,当屏幕宽度较小时,它会自动调整 -->
    </div>
    <div class="col-md-6">
    <!-- 另一个占据父容器的一半宽度的列 -->
    </div>
    </div>
    </div>

    这是Bootstrap栅格系统的一个简单示例,它可以根据屏幕宽度自动调整列的大小。

VH单位:(二者都适用)

VH单位表示视窗高度的百分比。使用vh单位可以确保元素相对于视窗高度进行适配,这在移动端和PC端都很有用。

1
2
3
.element {
height: 50vh; /* 元素高度为视窗高度的50% */
}

这个示例中,元素的高度设置为视窗高度的50%,这样无论是在移动设备还是PC上,该元素始终会占据屏幕高度的一半。

JavaScript适配:

JavaScript可以通过检测设备的屏幕宽度和高度,然后根据具体情况动态调整页面元素的大小、布局和样式。以下是一个简单的JavaScript示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取视口的宽度和高度
var viewportWidth = window.innerWidth || document.documentElement.clientWidth;
var viewportHeight = window.innerHeight || document.documentElement.clientHeight;

// 根据视口宽度和高度进行动态调整
if (viewportWidth < 600) {
// 移动端样式
document.body.style.fontSize = "14px";
} else if (viewportWidth >= 600 && viewportWidth < 1024) {
// 平板样式
document.body.style.fontSize = "16px";
} else {
// PC端样式
document.body.style.fontSize = "18px";
}

这个示例根据视口的宽度动态设置了body元素的字体大小,以实现简单的移动端、平板和PC端的屏幕适配。JavaScript还可以用于更复杂的适配场景,例如根据设备类型加载不同的样式文件或动态调整元素的布局等。

77、路由懒加载和异步组件

78、Promise详细讲解

1、Promise是什么

  1. 抽象表达
    • Promise是一门新的技术(ES6规范)
    • Promise是JS中进行异步编程的新的解决方案(旧的解决方案是单纯的回调函数)
  2. 具体表达:
    • 从语法上来说:Promise是一个构造函数
    • 从功能上来说:promise对象用来封装一个异步操作并可以获取其成功/失败的结果值

2、为什么要用Promise

  1. promise在指定回调函数的方式更加灵活

    • 旧的:必须在启动异步任务前指定
    • promise:启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定一个/多个)
  2. 支持链式调用,可以解决回调地狱的问题

    • 回调地狱是指:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件

      • ```js
        asyncFunc1(opt, (…args1) => {
        asyncFunc2(opt, (...args2) => {
            asyncFunc3(opt, (...args3) => {
                asyncFunc4(opt, (...args4) => {
                    ...
                })
            })
        })
        
        })
        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

        - 回调地狱的缺点:不便于阅读和异步处理

        3. 解决方案

        - promise链式调用

        4. promise的基本使用

        ```js
        //resolve 解决 函数类型的数据
        //reject 拒绝 函数类型的数据
        const p = new Promise((resolve, reject) => {
        setTimeout(() => {
        if(成功) {
        resolve(value); //将 promise 对象的状态设置为 【成功】
        }else {
        reject(err); //将 promise 对象的状态设置为 【失败】
        }
        }, 1000)
        })
        //调用 then 方法
        p.then((value) => {
        成功时的回调
        }, (reason) => {
        失败时的回调
        })
  3. util.promisify的使用

    • 它可以将以前的那种基于回调函数的异步函数转换为promise

    • ```js
      //引入 util 模块
      const util = require(‘util’)
      //引入 fs 模块
      const fs = require(‘fs’)
      //返回一个新的函数
      let myReadFile = util.promisify(fs.readFile)

      myReadFile(‘./resource/content.txt’).then(value=> {

      console.log(value.toString())
      

      })

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24

      6. 封装一个AJAX请求

      ```js
      function sendAJAX(url) {
      return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest()
      xhr.open("GET", url)
      xhr.send()
      //处理结果
      xhr.onreadystatechange = function() {
      if(xhr.readyState === 4) {
      //判断成功
      if(xhr.status >= 200 && xhr.status < 300) {
      //成功的结果
      resolve(xhr.response)
      }else {
      reject(xhr.status)
      }
      }
      }
      })
      }
      sendAJAX(url).then(value => {},reason => {})
  4. promise的状态改变

    • promise的状态其实就是实例对象中的一个属性 【PromiseState】
    • pending 未决定的
    • resolved / fullfilled 成功
    • rejected 失败
  5. Promise对象的值

    • 实例对象中的另一个属性 【PromiseResult】保存着对象异步任务 【成功/失败】的结果
    • 可以通过resolve和reject函数去修改
  6. 中断Promise链条

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    p.then(value => {
    console.log(111);
    //有且只有一个方式
    return new Promise(() => {})//返回一个pending状态的promise
    }).then(value => {
    console.log(222)
    }).then(value => {
    console.log(333)
    }).catch(reason => {
    console.warn(reason)
    })

79、Vue和jQuery的区别:

Vue和jQuery对比jQuery是使用选择器()选取DOM对象,对其进行赋值、取值、事件绑定等操作,和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。

Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,他们通过Vue对象这个vm实现相互的绑定,这就是MVVM模型。

80、IntersectionObserver的使用

供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。

当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。

IntersectionObserver - Web API 接口参考 | MDN (mozilla.org)

超好用的API之IntersectionObserver - 掘金 (juejin.cn)