普通视图
依旧性能优化,如何在浅比较上做文章,memo 满天飞,谁在裸奔?
超赞👍!优秀前端佬的电子布洛芬技术网站!
你一般用哪些状态管理库?别担心,Zustand和Redux就能说个10分钟
Webpack 背后做了什么?
JavaScript高级程序设计(第5版):代码整洁之道
Base64编码详解
鸿蒙ArkTS 与 Native 交互场景分类总结与关键实现
Vue3 响应式大对比:ref vs reactive,到底该怎么选?
🔒 前后端 AES 加密解密实战(Vue3 + Node.js)
第5章 高级UI与动画
CSS 盒模型:Margin vs Padding 的区别
写给自己的 LangChain 开发教程(二):格式化数据 & 提取 & 分类
CSS Visibility(可见性)
BFC原理剖析:前端布局救星
Vue状态管理工具pinia的使用以及Vue组件通讯
状态管理工具redux以及resso和zustand都用过了,这里pinia和resso是一样的,存储的状态数据直接写,方法也是直接写就可以了,会自动进行类型推断。
1.pinia的使用
首先入口文件引入pinia之后创建pinia对象用use安装pinia和路由一样
import { createApp } from "vue";
import App from './App.vue'
//引入pinia
import { createPinia } from "pinia";
const app =createApp(App)
//创建pinia
const pinia=createPinia()
//安装pinia
app.use(pinia)
app.mount('#app')
之后创建store文件夹,不同的数据以及数据方法单独创建文件。
编辑
然后用defineStore初始化store容器。
编辑
actions写方法,state写数据。
之后就可以在组件里面引入useCountStore函数调用生成countStore对象就可以引用里面的方法和数据了。
<template>
<div class="count">
<h2>当前求和{{ sum }}</h2>
<select name="number" id="" v-model="n">
<option value="1">1</option>
<option value="2">2</option>
</select>
<button @click="add">增</button>
<button @click="increase">减</button>
</div>
</template>
<script setup lang="ts" name="Count">
import { ref } from 'vue';
import {useCountStore} from '@/store/count'
import { storeToRefs } from 'pinia';
const countStore=useCountStore()
console.log(countStore.sum);
let {sum} = storeToRefs(countStore)
let n =ref(1)
const add=()=>{
//第一种修改
//countStore.sum=countStore.sum+Number(n.value)
//第二种修改 可以一次修改多种数据
// countStore.$patch({
// sum:+Number(n.value)
// })
//第三种修改方式actions
countStore.add(n.value)
}
const increase=()=>{
countStore.sum=countStore.sum-Number(n.value)
}
//store的监听事件
//subscribe回调函数接收两个参数 mutate修改的数据 state就是store存储的数据
countStore.$subscribe((mutate,state)=>{
console.log('state改变了',mutate,state);
})
</script>
<style scoped>
.count{
background-color: aqua;
}
select{
width: 50px;
}
button{
height: 100px;
width: 100px;
background-color: aquamarine;
color: #000;
}
</style>
还有一个$subscribe可以监听store种的数据变化,mutate第一个参数是变化的数据,state就是存储的所以数据对象。
2.Vue组件通讯的方式、
包括之前的vue博客,并不是零基础的,是在写过完整的前端项目之后去学的,所以原理大致都是通的,只是方法用法不同。
这里通讯方式其实和React是相通的,这里只是总结。
编辑
这里和react一致。父子和子夫通过props操作。
编辑
编辑
分享数据的组件on绑定事件,然后把数据放到回调函数中,然后触发事件的时候把参数传递之后,绑定事件的回调函数就会接收参数。
记得组件卸载的时候解绑。和pubsub一致
编辑
v-model双向绑定,无非就是把数据绑定到input的value上,然后@input事件发生的时候把value绑定到数据上,和react受控组件一个道理。
编辑
父子传递props子没有接收就会放到attrs属性中,可以借此用props传递给孙组件。
provide提供数据子组件inject拿。子获取父的时候需要父暴露数据,父获取子的实例对象也需要子暴露数据,不然数据是隐藏的。
如何更好的封装一个接口轮询?
相信大家做pc端开发的时候都会遇到一个需求,比如扫码登录的时候,通过手机端扫码之后一般都需要通过轮询查询登录状态,其实这是个很常见的需求,但是没做过的小伙伴可能就会不经意间写出一个bug。就是比如1秒钟轮询一次的时候,如果上次的接口还没成功响应那么下一次的接口就又发送出去了。正确做法应该是等待上个响应后再发出下一次请求。
其实解决bug的关键就是如何等待上次请求响应之后才发出下一次的请求,等待
具体应该怎么实现呢?可以参考下我的思路。
具体如下:
type PollingEvent = 'success' | 'error' | 'stop';
interface PollingOptions {
interval?: number; // 轮询间隔时间
retries?: number; // 接口失败后重试次数
immediate?: boolean; // 是否立即调用一次接口
}
type EventCallback<T> = (data?: T | Error) => void;
class PollingService<T = any> {
private requestFn: () => Promise<T>;
private interval: number;
private retries: number;
private immediate: boolean;
private timer: ReturnType<typeof setTimeout> | null = null;
private isPolling: boolean = false;
private currentRetry: number = 0;
private listeners = new Map<PollingEvent, Set<EventCallback<T>>>();
constructor(requestFn: () => Promise<T>, options: PollingOptions = {}) {
this.requestFn = requestFn;
this.interval = options.interval || 1500;
this.retries = options.retries || 0;
this.immediate = options.immediate ?? true;
}
on(event: PollingEvent, callback: EventCallback<T>): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
const listeners = this.listeners.get(event)!;
listeners.add(callback);
return () => listeners.delete(callback);
}
start(): void {
if (this.isPolling) return;
this.isPolling = true;
this.currentRetry = 0;
if (this.immediate) {
this.execute();
} else {
this.scheduleNext();
}
}
stop(): void {
this.isPolling = false;
this.clearTimer();
this.emit('stop');
}
private async execute(): Promise<void> {
if (!this.isPolling) return;
// 其实关键就在这里了,我们借助 async/await来实现
try {
const data = await this.requestFn();
this.currentRetry = 0;
this.emit('success', data);
} catch (error) {
this.handleError(error as Error);
return;
}
this.scheduleNext();
}
private handleError(error: Error): void {
this.emit('error', error);
if (this.currentRetry < this.retries) {
this.currentRetry++;
this.execute(); // 立即重试
return;
}
this.stop();
}
private scheduleNext(): void {
if (!this.isPolling) return;
this.clearTimer();
this.timer = setTimeout(() => {
this.execute();
}, this.interval);
}
private clearTimer(): void {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
private emit(event: PollingEvent, data?: T | Error): void {
const listeners = this.listeners.get(event);
if (listeners) {
listeners.forEach((callback) => callback(data));
}
}
}
export { PollingService };
完成上述代码之后,在页面中直接引入使用即可。
使用如下:
import React, { useEffect, useRef } from 'react';
import { PollingService } from 'xxx/PollingService';
const Demo = () => {
const pollingRef = useRef<any>(null);
// 轮询的方法
const requesFun = async () => {
const res = await axios.post('xxx')
// todo sth
}
useEffect(() => {
pollingRef.current = new PollingService()
// 启动轮询
pollingRef.current.start()
// !!!组件卸载一定要关闭轮询
return () => {
pollingRef?.current?.stop()
}
}, [)
<div>测试页面</div>
}
export default Demo;
震惊!多核性能反降11%?node接口压力测试出乎意料!
背景
最近在优化一个Node.js后端服务时,我决定对多进程模式进行一次真实压力测试。大家都知道Node.js是单线程模型,通常我们会用PM2的Cluster模式来启动多进程,试图榨干多核CPU的性能。理论上,这应该能让服务性能起飞,但事实真的如此吗?
测试设计
但我决定不做假设,而是用真实数据说话。我设计了一个压力测试对比:
- 单进程模式:一个 Node.js 实例运行在主核上
- 多进程模式:两个进程分别运行在两个核心上(通过 PM2 启动)
测试环境为:
- 机器:2核2G服务器
- Node.js 版本:24+
- 测试工具:autocannon
- 测试条件:10秒内发送100个并发请求
测试结果
单核心接口压力测试开始,10秒钟请求100次接口
autocannon -c 100 -d 10 http://your-server-ip:3000/api/endpoint
结果让人惊喜:平均延迟354ms哈哈哈,QPS达到278.11,数据传输量543 kB/s。表现....
多进程压测:搓手期待性能起飞,接下来配置PM2多进程模式,创建ecosystem.config.js:
module.exports = {
apps: [
{ name: "my-node-api",
script: "/var/www/my-node-api/index.js",
instances: 2, // 明确指定2个实例(或者max)
exec_mode: "cluster", //cluster-多线程模式 fork-单线程模式
env: {
NODE_ENV: "production", PORT: 3000
},
error_file: "/var/www/my-node-api/logs/err.log",
out_file: "/var/www/my-node-api/logs/out.log",
time: true,
max_memory_restart: "400M"
}]
}
开启多核心后继续压力测试,结果意想不到...
满怀期待启动服务,结果...让人大跌眼镜!
性能不升反降?数据说话
对比测试数据后,我发现了一个反直觉的结果:
1. 延迟性能下降 ⚠️
指标 | 单进程 | 多进程 | 变化 |
---|---|---|---|
平均延迟 | 354ms | 394ms | +11.4% |
中位数延迟 | 348ms | 390ms | +12.1% |
最大延迟 | 496ms | 517ms | +4.2% |
2. 吞吐量下降 ⚠️
指标 | 单进程 | 多进程 | 变化 |
---|---|---|---|
请求/秒 | 278.11 | 249.9 | -10.2% |
数据传输量 | 543 kB/s | 487 kB/s | -10.3% |
3. 稳定性略有改善 ✅
- 延迟波动(标准差):从 43.15ms 降至 41.49ms,改善 3.8%
- 吞吐波动(标准差):大幅减少,改善 25.4%
多进程模式为什么反而更差了?
没有想到在开启多核心的之后的压力测试并没有得到提升,反而接口延迟又变高了,请求数据量也从单核的5.48M变成了双核4.87M,我反复对比了下。 理论上多进程应该更好,但实际测试结果却相反。我分析主要有以下几个原因:
- 进程间需要通信:多个进程之间需要协调工作,这部分额外工作单进程不需要做
- 切换成本高:系统在不同进程间切换比在单个进程内切换开销更大
- 内存无法共享:每个进程都有自己的内存空间,无法共享数据
- 负载均衡本身也需要资源:分配请求的工作也需要消耗计算资源
那为什么稳定性反而提升了?
虽然性能下降了,但稳定性的确略有提升。这是因为:
- 压力分散到多个进程,单个进程负担减轻
- 一个进程出问题不会影响整个服务
- 错误被控制在单个进程内
结论与建议
多进程模式不是万能的!特别是在:
- 访问量不大时
- 业务逻辑不复杂时
- 主要是处理I/O操作时(如常见的API服务)
单进程模式可能效果更好。
什么时候应该用多进程?
- 访问量非常大时
- 需要处理大量计算任务时(如图片处理、数据加密等)
- 对稳定性要求特别高时
这次测试说明:
没有最好的方案,只有最适合的方案~
JavaScript - 观察者模式的实现与应用场景
1. 什么是观察者模式
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了一种一对多的依赖关系。当主题带有的状态被改变时,所有观察者都会收到通知并自动更新。
主题(Subject):Subject也可以称为被观察者,它维护一个Observer列表,实现新增、删除、通知Observer更新的方法。
观察者(Observer):Observer是接收主题通知的对象,需要实现一个更新方法,当收到Subject的通知时,调用该方法进行更新。
具体主题(Concrete Subject):ConcreteSubject继承了Subject,是Subject的具体实现类,我们在其中定义主题状态,当状态发生某些变化时通知Observer更新。
具体观察者(Concrete Observer):ConcreteObserver是Observer的具体实现类。它定义了在收到通知时需要执行的具体操作。
需要注意的是,在观察者模式的依赖关系中,并不是观察者主动拉取主题消息,而是被动的接收主题通知并产生相应变化。
2. 观察者模式举例
以拍卖会上拍卖者与竞价者的一对多的依赖关系举例:当价格更新时,所有的竞价者都会收到通知。
3. 例子的代码实现
定义Subject类与Observer类
/**
* 主题(Subject)中维护一个观察者列表,实现新增、删除观察者、与通知观察者更新的方法
*/
class Subject {
constructor() {
this.observerList = [];
}
/**
* 新增观察者方法
* @param observer
*/
addObserver(observer) {
this.observerList.push(observer);
}
/**
* 移除观察者方法
* @param observer
*/
removeObserver(observer) {
const findBidderIndex = this.observerList.findIndex(observerItem => {
return observerItem.id === observer.id;
})
if(findBidderIndex > -1) {
this.observerList.splice(findBidderIndex, 1);
}
}
/**
* 通知观察者进行更新
* @param data
*/
notify(data) {
this.observerList.forEach(observerItem => {
observerItem.update(data);
})
}
}
/**
* 观察者(Observer)中应定义一个更新方法
*/
class Observer {
/**
* 更新方法
* @param data
*/
update(data) {
}
}
定义Subject与Observer的具体类
/**
* 定义一个拍卖师类,作为具体主题(ConcreteSubject)
*/
class Auctioneer extends Subject {
constructor() {
super();
this.state = '30';
}
setState(state){
this.state = state;
this.notify(this.state);
}
getState(){
return this.state;
}
}
/**
* 定义一个竞拍者类,作为具体观察者(ConcreteObserver),实现update方法
*/
class Bidder extends Observer {
constructor(id) {
super();
this.id = id;
}
update(data) {
console.log('竞拍者 ' + this.id + ' 收到通知:' + data);
}
}
创建实例、修改状态
//实例化具体主题
const auctioneer = new Auctioneer();
//实例化具体观察者
const bidder1 = new Bidder(1);
const binder2 = new Bidder(2);
const binder3 = new Bidder(3);
//将具体观察者推入具体观察者列表
auctioneer.addObserver(bidder1);
auctioneer.addObserver(binder2);
auctioneer.addObserver(binder3);
auctioneer.removeObserver(binder2);
//修改状态
auctioneer.setState(20);
打印结果
竞拍者 1 收到通知:20
竞拍者 3 收到通知:20
4.观察者模式的适用场景
当目标对象与其它对象产生一对多关系时,当目标对象改变,希望其它对象也发生相应变化
比如在订单页面使用v-for生成出来几个Tab组件,通过滑动切换。这些Tab页标识着该用户所有订单的状态。
当用户操作的某一笔订单的状态发生改变时,我们希望每个与该笔订单相关的所有Tab组件都发生变化,都重新走一遍查询接口。
这时就可以定义这一组一对多的依赖,将相关标签页的key推入Store中定义的列表作为状态。使用观察者模式使那些存于列表中的Tab刷新。