HTML

语义化

新标签

webworkers

postmessage

onmessage

websocket

一次连接

WebSocket 协议本身并没有内置的短线重连机制,但可以通过在客户端实现相应的重连逻辑来实现短线重连。通常,客户端会在连接断开后尝试重新连接到 WebSocket 服务器,并且在一定的策略下进行重连,以确保连接的稳定性和可靠性。

以下是一种简单的 WebSocket 短线重连的实现方式:

  1. 重连策略
    客户端可以实现一个重连策略,决定在何种情况下进行重连,以及重连的间隔时间。例如,可以在连接断开后立即进行第一次重连,然后采用指数退避的方式进行后续重连,即每次重连间隔时间逐渐增加。

  2. 监听连接状态
    客户端需要监听 WebSocket 连接的状态,包括连接成功、连接断开、连接错误等。当连接断开时,触发重连逻辑。

  3. 重连逻辑
    当连接断开时,客户端根据重连策略进行重连。通常,客户端会尝试重新连接到服务器,并且在连接失败后等待一定的时间后再次尝试重连。可以根据实际情况设定重连的最大次数,避免无限重连造成资源浪费。

  4. 断线检测
    客户端可以周期性地向服务器发送心跳包,以检测连接是否断开。如果连续一定次数(如三次)心跳失败,则认为连接已经断开,并触发重连逻辑。

  5. 避免重复连接
    在重连过程中,避免重复建立多个连接,可以在重连前检查当前是否已经存在有效的连接,如果存在则不再进行重连。

通过实现上述短线重连机制,可以提高 WebSocket 连接的稳定性和可靠性,确保在网络不稳定或服务器故障等情况下能够及时恢复连接。

CSS

flex

flex:1=flex-grow,flex-shrink,flex-basic;

Grid

清除浮动

111

Margin塌陷

一、当父元素中第一个子元素设置margin-top时,目的是设置子元素与父元素顶部的距离,实际上却实现的是父元素和上一个元素的顶部距离,这种现象称之为margin塌陷。

解决方案:

1、在父元素中书写:overflow:hidden;

2、使用父元素的padding-top替代子元素的margin-top;

3、为父元素添加透明边框border:solid 1px transparent

二、兄弟元素之间的margin塌陷问题:

上方元素设置 margin-bottom,下方设置margin-top,最终两个元素之间的距离不等于两个margin之和。

解决方案:

无需解决,当需要设置两个元素之间的垂直距离时,为其中的一个元素设置margin即可。

三、元素自身的塌陷问题

当父元素的高度设置为auto或不写,同时内部子元素设置了浮动属性时,父元素的高度会发生自身塌陷。

解决方案:

1、内部子元素不使用浮动属性,可以使用display将子元素设置为内联块;

2、在父元素上添加overflow:hidden,

Javascript

深浅拷贝

深拷贝

1
2
3
function deepClone(value) {
return JSON.parse(JSON.stringify(value));
}

局限性:

  • 它无法复制函数。
  • 它无法复制循环引用。
  • 它不会拷贝 undefined
  • 它无法处理特定属性(如 Symbol 属性、属性名为 Symbol 类型的属性等)。

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepClone(value) {
if (typeof value !== "object" || value === null) {
return value; // 返回原始值类型
}

let result = Array.isArray(value) ? [] : {};
for (let key in value) {
// 使用 hasOwnProperty 检查以避免原型链中的键
if (value.hasOwnProperty(key)) {
// 递归复制每个属性值
result[key] = deepClone(value[key]);
}
}
return result;
}

浏览器的缓存

Cache-Control 是 HTTP 头部中控制缓存行为的重要指令。它可以指示客户端和代理服务器如何缓存响应以及在缓存中存储多长时间。

下面是 Cache-Control 头部常见的指令:

  1. public:指示响应可以被任何缓存(包括代理服务器)缓存。

  2. private:指示响应只能被终端用户缓存,不允许代理服务器缓存。这意味着每个用户都可以看到自己的个性化响应。

  3. no-cache:指示客户端在使用缓存之前必须先验证响应的有效性,即使缓存中有对应的响应也不例外。

  4. no-store:指示客户端和代理服务器不得缓存任何版本的响应。

  5. **max-age=<seconds>**:指示响应在被认为过期之前可以在缓存中存储的时间,以秒为单位。

  6. **s-maxage=<seconds>**:类似于 max-age,但它只适用于共享缓存,例如代理服务器。

  7. must-revalidate:指示客户端在使用缓存之前必须重新验证响应的有效性,如果验证失败,则必须从服务器获取新的响应。

  8. proxy-revalidate:类似于 must-revalidate,但它只适用于代理服务器。

  9. immutable:指示响应的内容不会随时间的推移而改变,这意味着客户端可以安全地假定缓存中的响应是不变的。

这些指令可以单独使用,也可以组合使用,以控制缓存的行为。例如,您可以使用 Cache-Control: max-age=3600, public 指令来允许公共缓存(包括代理服务器)在 3600 秒(1 小时)内缓存响应。

使用 Cache-Control 头部可以有效地管理浏览器和代理服务器的缓存行为,提高网站性能和用户体验。

promise

promise.all

promise.race

promise.reslove

promise.rejected

promise.then

fetch、axios、ajax

在处理前端的HTTP请求时,fetchaxiosajax(通常指使用jQuery的$.ajax)是三种常见的技术。每种技术都有其独特之处,适用于不同的场景:

1. Fetch

fetch是现代浏览器内置的一个原生API,用于异步请求。它不依赖于任何外部库,是XMLHttpRequest的现代替代品。fetch提供了一个更加强大和灵活的特性集,支持Promise,使得异步代码更容易编写和管理。

  • 优点

    • 原生支持Promise。
    • 语法简洁,易于理解和使用。
    • 不需要额外的库或框架。
    • 支持流,允许请求和响应体进行流处理。
  • 缺点

    • 默认不发送cookies,需要额外配置。
    • 不支持请求取消(直到最近的AbortController出现)。
    • 错误处理较为复杂,需要额外的代码来处理非200状态码。

2. Axios

axios是一个基于Promise的HTTP客户端,适用于浏览器和node.js。它是一个独立的第三方库,可以通过npm或CDN进行安装。

  • 优点

    • 支持浏览器和Node.js。
    • 可以拦截请求和响应,便于添加通用处理逻辑。
    • 自动转换JSON数据。
    • 支持请求和响应的配置,如超时设置。
    • 支持请求取消。
    • 更全面的错误处理。
  • 缺点

    • 需要额外安装,增加项目的依赖。
    • 体积相对较大,尤其是在只需要简单功能的场景中。

3. Ajax (jQuery)

ajax方法通常是指jQuery库中的$.ajax函数,这是处理异步HTTP请求的早期方式之一。虽然jQuery已经不如以前那么流行,但$.ajax依然在许多遗留项目中广泛使用。

  • 优点

    • 在jQuery生态系统中集成良好。
    • 简单的配置和调用方式。
    • 广泛的浏览器支持。
  • 缺点

    • 依赖于jQuery,对于不使用jQuery的现代项目来说可能是额外的负担。
    • 不支持Promise,虽然可以使用$.Deferred()
    • 体积相对较大,特别是对于需要轻量级库的现代前端应用来说。

在选择这三种技术时,应考虑项目需求、浏览器支持以及开发团队对这些技术的熟悉程度。对于现代Web应用,推荐使用fetchaxios,因为它们更符合当前的开发趋势和性能要求。

this

四叉树和游程编码

四叉树(Quadtree)和游程编码(Run-Length Encoding,RLE)是两种不同的数据结构和编码方式,用于在计算机科学和图形学中处理图像和空间数据。

四叉树(Quadtree):

四叉树是一种树状数据结构,它将二维空间递归地划分为四个象限(或子节点),每个节点可以继续划分为四个子节点,以此类推。四叉树通常用于表示二维空间中的点、区域或图像等数据结构,具有以下特点:

  • 递归划分:四叉树递归地将空间划分为四个象限,直到达到某个终止条件。
  • 空间分割:每个节点代表了空间中的一个矩形区域,通过四个子节点来表示该区域的四个子区域。
  • 空间查询:四叉树可以用于空间查询,例如快速查找某个点所在的区域、查找覆盖某个区域的所有点等。

四叉树在图像处理中常用于表示图像的分层结构,可以用于图像压缩、碰撞检测、空间索引等方面。

游程编码(Run-Length Encoding,RLE):

游程编码是一种简单的无损数据压缩技术,它通过记录连续重复出现的数据项来减少数据的存储空间。游程编码通常应用于处理二值图像或具有大量重复值的数据,具有以下特点:

  • 连续重复值编码:游程编码将连续重复出现的数据项用一个计数值和一个重复的数据值来表示,从而减少存储空间。
  • 简单高效:游程编码是一种简单而高效的压缩算法,适用于处理大量重复值的数据。

游程编码在图像处理中常用于黑白图像的压缩,尤其是在处理文档扫描图像或简单图形图像时,可以显著减少图像的存储空间。

综上所述,四叉树和游程编码是两种不同的数据结构和压缩算法,分别用于处理空间数据和减少数据存储空间。它们在不同的领域和应用场景中发挥着重要的作用。

二叉树-K

当我们说到二叉树的前序遍历时,我们指的是按照根节点 - 左子树 - 右子树的顺序遍历整个树。这里给出两种不同的实现方式:

递归实现

递归是最直接的方法之一,它基于以下思想:对于每个节点,先访问根节点,然后递归地遍历左子树和右子树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right

def preorderTraversal(root):
result = []
if root:
result.append(root.val)
result.extend(preorderTraversal(root.left))
result.extend(preorderTraversal(root.right))
return result

# 示例用法
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)

print(preorderTraversal(root)) # 输出: [1, 2, 4, 5, 3]

迭代实现(使用栈)

另一种实现方式是使用迭代,我们可以使用栈来模拟递归的过程。具体做法是维护一个栈,将右子节点和左子节点按相反的顺序压入栈中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def preorderTraversal(root):
if not root:
return []

result = []
stack = [root]

while stack:
node = stack.pop()
result.append(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)

return result

# 示例用法(使用相同的二叉树)
print(preorderTraversal(root)) # 输出: [1, 2, 4, 5, 3]

这两种方法都可以用来实现二叉树的前序遍历,具体选择哪种方法取决于您的偏好和实际需求。

Vue

响应式数据

Vue.js 的双向数据绑定是其核心特性之一,它允许数据的变化驱动视图更新,同时视图的交互也能改变数据。这种双向绑定在 Vue 2 和 Vue 3 中都是通过一些底层原理和技术实现的,其中就包括发布-订阅者模式。

发布-订阅者模式

发布-订阅者模式是一种消息通信模式,其中发送者(发布者)不直接发送消息给特定的接收者(订阅者),而是将消息发送到一个中介(通常是消息队列或事件中心),而接收者则从该中介获取消息。这种模式允许发送者和接收者之间的解耦,因为发送者不需要知道哪些接收者会收到消息,同样,接收者也不需要知道消息是从哪里发送的。

在 Vue 的上下文中,数据对象可以被视为发布者,当数据发生变化时,它会发布一个事件。Vue 的依赖追踪系统则充当了中介的角色,它跟踪了哪些组件依赖于哪些数据。当数据变化时,依赖追踪系统会将这个变化通知给所有相关的组件,这些组件就是订阅者。

实现一个发布订阅模式

在JavaScript中,实现一个简单的发布-订阅模式可以通过使用事件和回调函数来完成。下面是一个简单的实现,其中Publisher类负责发布事件,Subscriber类负责订阅事件并接收通知。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Publisher {
constructor() {
// 创建一个存储所有订阅者的对象
this.subscribers = {};
}

// 订阅事件
subscribe(event, callback) {
// 如果该事件还没有订阅者,则初始化一个数组
if (!this.subscribers[event]) {
this.subscribers[event] = [];
}
// 将回调函数添加到订阅者数组中
this.subscribers[event].push(callback);
}

// 取消订阅
unsubscribe(event, callback) {
if (this.subscribers[event]) {
// 查找回调函数并移除
const index = this.subscribers[event].indexOf(callback);
if (index !== -1) {
this.subscribers[event].splice(index, 1);
}
}
}

// 发布事件
publish(event, ...args) {
// 如果有订阅了该事件的订阅者,则通知他们
if (this.subscribers[event]) {
this.subscribers[event].forEach(callback => {
callback(...args);
});
}
}
}

class Subscriber {
constructor() {
// 订阅事件时需要的回调函数
this.callback = null;
}

// 设置回调函数
setCallback(callback) {
this.callback = callback;
}

// 订阅事件
subscribeTo(publisher, event) {
publisher.subscribe(event, this.callback);
}

// 取消订阅
unsubscribeFrom(publisher, event) {
publisher.unsubscribe(event, this.callback);
}
}

// 使用示例
const publisher = new Publisher();
const subscriber = new Subscriber();

// 设置订阅者的回调函数
subscriber.setCallback((message) => {
console.log(`Received message: ${message}`);
});

// 订阅者订阅事件
subscriber.subscribeTo(publisher, 'messageEvent');

// 发布者发布事件
publisher.publish('messageEvent', 'Hello, World!'); // 输出: Received message: Hello, World!

// 取消订阅
subscriber.unsubscribeFrom(publisher, 'messageEvent');

// 再次发布事件,订阅者不会收到通知
publisher.publish('messageEvent', 'Another message'); // 无输出

在这个例子中,Publisher类负责管理事件和订阅者,Subscriber类则用于订阅和取消订阅事件。subscribe方法用于订阅事件,它将回调函数存储在subscribers对象中,每个事件对应一个回调函数数组。unsubscribe方法用于取消订阅,它从相应的回调数组中移除指定的回调函数。publish方法用于发布事件,它会遍历所有订阅了该事件的回调函数,并调用它们。

请注意,这个简单的发布-订阅模式实现没有考虑诸如异步通知、错误处理、取消订阅特定事件的所有回调等高级功能。在实际应用中,可能需要使用更复杂和健壮的实现,比如使用现成的库(如EventEmitter3、lodash的_.events等)或者使用更高级的异步编程模式(如Promises和async/await)。

Vue 2 的双向绑定原理

在 Vue 2 中,双向数据绑定主要依赖于以下几个部分:

数据劫持:Vue 使用 Object.defineProperty() 方法劫持了各个属性的 setter 和 getter,在数据变动时发布消息给订阅者。
依赖收集:当组件渲染时,会遍历其依赖的数据,并为这些数据添加一个订阅者(通常是组件的 watcher)。这样,当数据变化时,就可以通知到这个组件。
发布-订阅者模式:如上所述,当数据变化时,会发布一个事件,所有依赖这个数据的组件(订阅者)都会收到通知并更新。
Vue 3 的双向绑定原理

Vue 3 在数据绑定方面进行了很多优化和改进,主要依赖于 Proxy 对象来实现。

Proxy 对象:Vue 3 使用 Proxy 对象来劫持数据对象的所有属性,包括新增和删除的属性。这使得 Vue 3 在处理数据变化时更加高效和灵活。
Reactive 和 Ref API:Vue 3 提供了 Reactive 和 Ref API 来创建响应式数据。Reactive 用于创建响应式对象,而 Ref 则用于创建响应式引用。
依赖收集和通知更新:与 Vue 2 类似,Vue 3 也使用了依赖收集和发布-订阅者模式来实现数据的变化驱动视图更新。但由于使用了 Proxy,这个过程在 Vue 3 中更加高效和简洁。

总的来说,无论是 Vue 2 还是 Vue 3,双向数据绑定都是通过发布-订阅者模式实现的,但具体的实现细节和技术有所不同。Vue 3 在这方面进行了很多优化和改进,使得数据绑定更加高效和可靠。

diff算法

Vue.js 的 diff 算法是一个核心机制,用于有效地更新真实 DOM,尤其是在数据改变时更新视图。Vue 使用虚拟 DOM 来表示界面上的结构,虚拟 DOM 是一个轻量级的 JavaScript 对象结构。当数据发生变化时,Vue 会生成一个新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行比较,这个比较过程称为 “diffing”。Vue 的 diff 算法基于以下几个关键步骤和原则:

  1. 节点比较

    • Vue diff 算法首先比较根节点,如果根节点的类型(如元素类型、组件类型)或 key 值不同,则直接替换整个节点。
    • 如果节点类型相同,则进一步比较其子节点。
  2. 同级比较

    • Vue 的 diff 算法主要关注同级别的子节点比较。对于子节点,Vue 使用 “updateChildren” 函数来进行高效的比较和更新。
    • Vue 采用 “双端比较” 策略。算法维护四个指针:两个指向旧节点的开始和结束位置,两个指向新节点的开始和结束位置。在比较过程中,根据不同情况移动这些指针并相应地更新 DOM。
  3. 关键优化

    • 头头比较(oldStartVnode vs. newStartVnode)
    • 尾尾比较(oldEndVnode vs. newEndVnode)
    • 头尾比较(oldStartVnode vs. newEndVnode)
    • 尾头比较(oldEndVnode vs. newStartVnode)
    • 在以上四种基本比较中,一旦发现节点匹配,就会进行适当的 DOM 操作,如移动 DOM 节点或更新节点等,并调整指针继续比较。
  4. 列表中的 key 值

    • Vue 推荐为每个使用 v-for 指令的元素指定一个唯一的 key 值,这可以极大地提高 diff 算法的效率。有了 key,Vue 可以更快地识别节点是否可以重用,从而减少不必要的 DOM 操作。
  5. 性能考虑

    • Vue 的 diff 算法尽量减少遍历次数和最小化 DOM 操作,以保证高效的性能。通过智能比较和有条件的节点重用,Vue 可以快速准确地更新 DOM。

总之,Vue 的 diff 算法通过比较旧的虚拟 DOM 和新的虚拟 DOM 的差异,有效地更新真实 DOM,只改变必要的部分,而不是重建整个 DOM 树,从而保证了应用的性能和响应速度。这种机制是现代前端框架中提高效率的关键。

watch和computed

  • computed(计算属性)用于声明式地声明一个依赖于其他数据属性的值,这个值是基于其他数据属性的计算结果,只有在依赖的数据属性发生变化时才会重新计算,computed支持缓存,可以提高性能,适用于需要缓存计算结果的场景,如购物车商品结算等。
  • watch则用于观察和响应数据属性的变化,当指定的数据属性发生变化时,它会执行相应的回调函数,watch更适合用于执行异步操作或开销较大的操作,如网络请求或动画效果等,它不支持缓存,且可以设置为立即执行或在数据对象内部发生变化时深度监听,适用于需要对数据进行复杂处理或异步操作的场景。

Nexttick

Vue.nextTick 是 Vue.js 中的一个方法,它用于延迟执行一段代码,直到下次 DOM 更新循环结束。这有助于我们在修改数据后获取更新后的 DOM 状态。

原理:Vue.nextTick 是通过 JavaScript 的 Promise 和 MutationObserver 实现的。如果浏览器不支持 Promise 和 MutationObserver,Vue 会使用 setTimeout 作为回退方案。

用途

Vue.nextTick 的主要用途包括:

  1. 获取更新后的 DOM 状态:如果你需要在数据变化后获取更新后的 DOM 元素或计算属性,可以使用 Vue.nextTick 来确保你获取的是最新的状态。
  2. 执行依赖于更新后 DOM 的操作:例如,你可能需要在数据变化后立即执行某些动画或者测量 DOM 元素的大小。在这些情况下,使用 Vue.nextTick 可以确保你的代码在 DOM 更新后执行。
  3. 确保多个 DOM 更新顺序执行:有时你可能需要顺序执行多个更新操作,并且希望它们都在 DOM 更新后执行。通过使用 Vue.nextTick,你可以确保这些操作按照预期的顺序执行。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 假设有一个 Vue 实例
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
updateMessage: function () {
this.message = 'Updated message';
// 使用 Vue.nextTick 获取更新后的 DOM
this.$nextTick(() => {
// DOM 更新了
console.log(this.$refs.div.textContent); // 输出: Updated message
});
}
}
});

在这个例子中,当 updateMessage 方法被调用时,它会更新 message 属性。然后使用 this.$nextTick 来注册一个回调函数,这个回调会在 DOM 更新完成后执行,此时我们可以安全地读取更新后的 DOM 元素内容。

ref和reactive区别

  • 定义类型和数据类型不同。ref主要用于定义基本类型(如字符串、数字、布尔值等)的数据,而reactive主要用于定义对象和数组等复杂类型的数据。如果ref传入的是引用类型,其内部源码也是调用reactive来实现的。
  • 使用方式和访问不同。在使用上,ref需要在模板中使用ref指令,在JavaScript代码中使用ref函数进行创建和使用。而reactive则需要通过调用Vue.js提供的reactive函数进行包装和创建。对于通过ref函数创建的响应式数据,需要通过.value属性来访问值;而对于通过reactive函数创建的响应式对象,可以直接访问其属性或调用其方法。
  • 设计理念不同。ref主要解决单一元素/数据的响应式问题,而reactive则是为了解决JavaScript对象和数组等复杂数据结构的响应式问题。

当涉及到 Vue.js 中的 refreactive,它们都是 Vue.js 提供的函数,用于创建响应式数据和引用。下面我将为您提供具体的实现讲解。

使用 ref

1
2
3
4
5
6
7
8
9
10
import { ref, reactive } from 'vue';

// 创建一个响应式引用
const count = ref(0);

// 访问引用的值
console.log(count.value); // 输出: 0

// 修改引用的值
count.value++; // 现在 count 的值为 1

在上面的代码中,我们使用 ref() 函数创建了一个名为 count 的响应式引用,其初始值为 0。注意在访问引用的值时,我们需要使用 .value 属性。修改引用的值也需要通过 .value 属性。这样做的好处是,Vue.js 可以追踪到这个引用的变化。

使用 reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
import { reactive } from 'vue';

// 创建一个普通 JavaScript 对象
const state = { count: 0 };

// 将普通对象转换为响应式对象
const reactiveState = reactive(state);

// 访问响应式对象的属性
console.log(reactiveState.count); // 输出: 0

// 修改响应式对象的属性
reactiveState.count++; // 现在 reactiveState.count 的值为 1

在这个示例中,我们首先创建了一个普通的 JavaScript 对象 state,其中包含一个属性 count。然后,我们使用 reactive() 函数将这个普通对象转换为一个响应式对象 reactiveState。与 ref 不同,reactive 可以将整个对象转换为响应式,而不仅仅是其中的一个值。这意味着当我们修改 reactiveState 对象的属性时,Vue.js 会自动追踪这些变化,并且更新相关的视图。

通过以上示例,您可以了解到如何在 Vue.js 中使用 refreactive 函数来创建响应式数据和引用。

React

Fiber架构是什么,以及它解决了React之前版本中的哪些问题

Fiber架构是React16中引入的新的核心算法,它重新实现了React的调和和渲染过程,使得React能够更高效地处理大型和复杂的组件树。Fiber解决了React16之前版本中存在的一些关键问题,如:

  1. 不可中断的渲染过程:在React16之前,一旦开始渲染,React会阻塞主线程直到渲染完成。这可能导致UI冻结,特别是在处理大型组件树或高优先级任务(比如用户输入)时。
  2. 固定的任务优先级:之前的React版本无法区分任务的优先级,导致所有任务都按照相同的顺序执行。这不利于响应性,因为高优先级的任务(比如用户交互)可能会被低优先级的任务(比如数据获取)阻塞。

Fiber通过以下方式解决了这些问题:

  • 任务拆分与中断:Fiber架构将渲染过程拆分成多个小任务,并且可以在任意时间点中断和恢复这些任务。这使得React能够在渲染过程中响应其他高优先级任务,提高了应用的响应性。
  • 优先级调度:Fiber架构引入了任务优先级的概念,允许React根据任务的优先级来调度工作。高优先级的任务会优先得到处理,从而确保用户交互等关键任务的流畅执行。

组件通信

props、回调函数

context

redux

event bus

hooks

usestate

useeffect

useref 使用原因

使用 state 会造成没必要的重新渲染,用全局变量又会造成闭包问题

usememo 缓存值

usecallback 缓存函数

usecontext

获取原生DOM节点的方法

getElementById

getElementByTagName

getElementByName

getElementByClassName

querySelector

querySelectorAll

Webpack

entry

output

derServe

loader–css部分-对象+plugin–js–全程–数组

loader:modules

性能优化

页面阻塞

页面渲染阻塞是Web性能优化中常见的问题,这会影响到页面加载时间和用户体验。通常,渲染阻塞的原因与资源的加载、解析以及执行方式有关。以下是一些常见的原因:

1. 同步加载的JavaScript

  • 阻塞解析:当浏览器在HTML文档中遇到一个<script>标签时,它会停止HTML的解析,转而去加载并执行JavaScript文件。如果这些脚本是同步加载的(没有asyncdefer属性),它们会阻塞页面的渲染直到脚本执行完毕。
  • 依赖于DOM的脚本:如果脚本需要在DOM完全解析后才能运行,它们同样会导致页面渲染的延迟。

2. 非异步的CSS文件

  • CSS是渲染阻塞的:CSS必须被下载和解析完成后,浏览器才能正确渲染页面内容,因为浏览器需要知道如何显示页面中的所有元素。如果CSS资源加载延迟,页面渲染也会被延迟。

3. 大量DOM元素

  • DOM复杂性:页面上DOM元素数量过多或结构过于复杂,会导致浏览器在构建DOM树和渲染树时花费更多时间,从而延迟了页面渲染。

4. 重定向和网络延迟

  • 重定向:HTTP重定向增加了额外的网络请求,这不仅增加了页面加载时间,也可能阻塞页面渲染,直到重定向完成。
  • 网络问题:网络延迟或连接问题会延迟资源的下载,包括HTML、CSS、JavaScript等,从而影响页面的首次渲染时间。

5. 大型资源和未优化的内容

  • 未压缩的资源:大型的JavaScript和CSS文件,特别是未经压缩或优化的,需要更多时间加载和解析。
  • 图片和媒体内容:大型的图片和视频资源没有进行适当的优化和懒加载,会占用大量带宽,延迟页面内容的可见时间。

6. JavaScript执行占用主线程

  • 长时间运行的JavaScript:复杂的脚本或大量的同步JavaScript代码可以占用浏览器主线程,阻止页面的进一步渲染直到脚本执行完成。

优化策略

为了避免这些阻塞,通常会采取以下优化策略:

  • 使用asyncdefer属性加载JavaScript,以非阻塞方式执行脚本。
  • 确保关键CSS尽可能早地加载,并使用媒体查询或条件加载非关键CSS。
  • 简化DOM结构,减少页面复杂性。
  • 优化重定向,减少不必要的中间跳转。
  • 压缩资源,包括JavaScript、CSS和图片,减少文件大小和下载时间。
  • 对于大型的媒体文件,实施懒加载策略,只有在需要时才加载这些资源。

通过这些方法,可以显著提升页面的加载速度和用户体验。

文件上传-X

文件上传是 Web 开发中常见的功能之一,可以通过一些优化来提升用户体验和系统性能。以下是一些文件上传的优化方法和实现方式:

  1. 进度条显示
    显示上传进度条可以让用户清楚地知道文件上传的进度,提升用户体验。您可以使用 HTML5 的 progress 元素或 JavaScript 库(如 Axios、jQuery 等)来实现上传进度的动态更新。

  2. 分片上传
    对大文件进行分片上传可以减少单个请求的负载,提高上传速度。前端可以将大文件切分成多个小块,然后分别上传到服务器,服务器端再将这些小块合并成完整的文件。

  3. 并发上传
    如果服务器支持,可以使用并发上传来同时上传多个文件或多个文件分片,以提高上传效率。通过使用多线程或异步处理,可以更快地完成文件上传过程。

  4. 压缩文件
    如果用户上传的文件支持压缩,可以在上传之前对文件进行压缩处理,减小文件大小,从而降低上传时间和网络带宽的消耗。

  5. 文件类型限制
    在前端和后端都进行文件类型的限制,防止用户上传不安全或不支持的文件类型,可以提高系统安全性和稳定性。

  6. 服务端缓存
    在服务器端设置文件上传的临时缓存,减少文件传输和处理过程中的数据传输量和IO操作,提高系统性能。

  7. CDN 加速
    使用 CDN(内容分发网络)来加速文件上传过程中的数据传输,减少网络延迟和提高数据传输速度。

  8. 断点续传
    实现断点续传功能可以让用户在上传中断后恢复上传进度,而不需要重新上传整个文件。可以通过记录已上传的文件分片信息或使用第三方库来实现。

  9. 本地预处理
    在文件上传之前进行客户端的本地预处理,例如压缩、校验、格式转换等,减轻服务器的压力和网络带宽的消耗。

综上所述,文件上传的优化可以从多个方面入手,包括显示上传进度、分片上传、并发上传、压缩文件、文件类型限制、服务端缓存、CDN 加速、断点续传和本地预处理等。根据具体的业务需求和系统架构,选择合适的优化方法来提升文件上传的性能和用户体验。

Web Workers 是一种在后台运行 JavaScript 代码的机制,可以在单独的线程中执行长时间运行的任务,而不会阻塞页面的主线程。通过结合 Web Workers 和文件上传,可以实现文件上传的性能优化,特别是在处理大文件时。

以下是结合 Web Workers 实现文件上传性能优化的一般步骤:

  1. 分片上传
    使用 Web Workers 将大文件切分成多个小块,并在后台并行上传这些小块,以提高上传速度。每个 Web Worker 负责上传一个文件分片,这样可以避免单个请求过于庞大,减少网络延迟和提高上传效率。

  2. 并发上传
    创建多个 Web Workers 来并发上传多个文件分片,以充分利用浏览器的多线程能力,进一步提高上传效率。通过设置适当的并发数,可以在不过度消耗系统资源的情况下实现最大化的上传速度。

  3. 断点续传
    在 Web Workers 中实现断点续传功能,可以让用户在上传中断后恢复上传进度,而不需要重新上传整个文件。Web Workers 可以记录已上传的文件分片信息,以便在中断后能够恢复上传进度。

  4. 本地预处理
    在 Web Workers 中进行文件的本地预处理,例如文件的压缩、校验、格式转换等,可以减轻服务器的压力和网络带宽的消耗,提高上传效率。

  5. 进度报告
    使用 Web Workers 实时报告上传进度,以便用户可以清晰地了解文件上传的进展。可以通过在 Web Workers 中监听上传事件并发送进度信息到主线程,然后在页面上更新进度条或显示上传百分比。

综上所述,结合 Web Workers 可以实现文件上传的性能优化,特别是在处理大文件时。通过分片上传、并发上传、断点续传、本地预处理和进度报告等方式,可以提高文件上传的效率、减少用户等待时间,并且减轻服务器的压力。

如何保留上传文件的状态

在网页开发中,保留已经上传的文件状态是一种常见的需求,特别是在用户需要在表单中填写大量信息或处理大文件时。以下是一些技术和方法,可以在刷新页面后保持文件的上传状态:

1. 使用浏览器的本地存储

  • LocalStorage或IndexedDB:在文件上传前,可以将文件数据存储在浏览器的LocalStorage或IndexedDB中。这种方式适合存储较小的文件或文件的元数据。对于大文件,IndexedDB更为合适,因为它提供了更大的存储空间。

2. 将文件分片后上传

  • 分片上传:对于大文件,可以在客户端将文件分成多个小片段,然后逐个上传。每上传完一个片段,就可以在服务器上标记该片段的上传状态,并在本地存储已上传的片段信息。如果页面刷新,上传过程可以从上次中断的地方恢复。

3. 使用Session或Cookies

  • Session或Cookies:将文件的相关信息(如文件名、大小、已上传的片段索引等)存储在Session或Cookies中。这种方法适合跟踪小型数据,但由于Cookies和Session的存储限制,它们不适合直接存储文件内容。

4. 服务器端的文件恢复机制

  • 服务器存储状态:服务器可以跟踪每个文件的上传进度。当用户重新加载页面时,可以通过一个API调用询问服务器关于当前上传状态的信息,并据此恢复上传过程。

5. 使用JavaScript库或框架

  • Resumable.js:这是一个JavaScript库,允许对大文件进行分片上传,并且能在上传过程中断后恢复。它通过在客户端存储文件的上传状态来实现这一功能。

6. 创建持久的前端状态

  • Vue.js、React等前端框架:可以利用现代前端框架的状态管理库(如Vuex、Redux)来持久保存文件的状态。结合本地存储或其他客户端存储解决方案,可以在页面刷新后恢复这些状态。

实现示例

假设您使用的是分片上传方法,以下是使用JavaScript进行实现的基本步骤:

  1. 文件选择并分片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function sliceFile(file, size) {
    let chunks = [];
    let count = Math.ceil(file.size / size);
    for (let i = 0; i < count; i++) {
    let chunk = file.slice(size * i, size * (i + 1));
    chunks.push(chunk);
    }
    return chunks;
    }
  2. 上传每个分片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function uploadChunk(chunk, index) {
    const formData = new FormData();
    formData.append("file", chunk);
    formData.append("index", index);
    // 使用 fetch 或其他 AJAX 方法上传
    fetch('upload_url', { method: 'POST', body: formData })
    .then(response => response.json())
    .then(data => console.log('Chunk uploaded', data))
    .catch(error => console.error('Error uploading chunk', error));
    }
  3. 在本地存储中跟踪已上传的分片

    1
    2
    3
    function updateUploadProgress(index) {
    localStorage.setItem('uploadedChunks', JSON.stringify([...JSON.parse(localStorage.getItem('uploadedChunks') || '[]'), index]));
    }
  4. 刷新页面后检查上传状态,并恢复

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function checkAndResumeUpload(file) {
    let uploadedChunks = new Set(JSON.parse(localStorage.getItem('uploadedChunks') || '[]'));
    let chunks = sliceFile(file, CHUNK_SIZE);
    chunks.forEach((chunk, index) => {
    if (!uploadedChunks.has(index)) {
    uploadChunk(chunk, index);
    }
    });
    }

这些方法提供了不同的方式来处理页面刷新时文件上传状态的保留问题,具体选择哪

种方法取决于应用的需求、文件大小以及用户体验的考量。

说说 weback 与 vite

Webpack 和 Vite 都是现代前端开发中常用的构建工具,它们有一些相似之处,但也有一些显著的区别。

Webpack:

Webpack 是一个功能强大的静态模块打包工具,可以处理 JavaScript、CSS、图片等多种资源,并且支持代码分割、懒加载、热模块替换(HMR)等功能。Webpack 使用配置文件来定义构建过程,可以通过配置文件来配置各种加载器(loader)和插件(plugin),对各种资源进行处理和优化。

优点:

  • 功能强大:Webpack 提供了丰富的功能和插件生态系统,可以满足各种复杂项目的构建需求。
  • 灵活性高:Webpack 的配置灵活,可以根据项目需求进行定制和优化。
  • 社区庞大:Webpack 是目前最流行的前端构建工具之一,拥有庞大的社区支持和丰富的插件资源。

缺点:

  • 配置复杂:Webpack 的配置相对复杂,学习曲线较陡峭,初学者可能需要花费一些时间来理解和掌握其用法。
  • 构建速度慢:由于 Webpack 需要对整个项目进行构建和打包,对于大型项目而言,构建速度可能较慢。

Vite:

Vite 是一个由 Vue.js 核心团队开发的新型前端构建工具,它专注于快速开发环境和快速构建速度。Vite 基于现代浏览器的原生 ES 模块导入功能,采用了“即时编译”(Instant Build)的模式,可以实现快速的开发和热更新。

优点:

  • 极速开发:Vite 基于原生 ES 模块导入,利用现代浏览器的原生支持,可以实现毫秒级的冷启动和热更新,极大地提高了开发效率。
  • 简单配置:Vite 的配置相对简单,不需要复杂的配置文件,使用默认配置即可快速启动项目。
  • 支持 Vue 3:作为 Vue.js 核心团队开发的工具,Vite 对 Vue 3 的支持非常友好,并且提供了一些针对 Vue 3 的优化和扩展。

缺点:

  • 生态相对较弱:相比于 Webpack,Vite 的生态相对较新,插件资源相对较少,可能不适用于某些复杂的项目需求。
  • 对传统项目支持有限:由于 Vite 基于原生 ES 模块导入,对于传统的 CommonJS 模块导入的项目支持有限。

总的来说,Webpack 和 Vite 都是优秀的前端构建工具,具有各自的特点和优势。选择合适的工具取决于项目的需求和团队的偏好,有些项目可能更适合使用 Webpack,而有些项目可能更适合使用 Vite。

计算机网络