腾讯实时音视频(Tencent Real-Time Communication,TRTC)是腾讯云提供的一套低延时、高质量的音视频通信服务。本文将详细介绍如何在 uni-app 和 Web 端接入 TRTC,并提供完整的示例代码,包括多人视频通话的实现。
· 19 min read
前端存储之IndexedDB
· 5 min read
技术方案
1. IndexedDB
IndexedDB 是一个强大的浏览器内置数据库,具有以下优势:
- 支持存储大量结构化数据
- 支持索引,便于快速检索
- 支持事务,保证数据一致性
- 异步 API,不会阻塞主线程
2. 数据加密:jsencrypt
使用 jsencrypt 进行数据加密,确保离线数据的安全性:
- 使用 RSA 非对称加密
- 公钥加密,私钥解密
- 保证数据在本地存储的安全性
核心实现
1. IndexedDB 数据库设计
// db.js
const DB_NAME = "medical_reports";
const DB_VERSION = 1;
const initDB = () => {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 报告基本信息表
if (!db.objectStoreNames.contains("reports")) {
const reportStore = db.createObjectStore("reports", { keyPath: "id" });
reportStore.createIndex("patientId", "patientId", { unique: false });
reportStore.createIndex("timestamp", "timestamp", { unique: false });
}
// 波形数据表
if (!db.objectStoreNames.contains("waveforms")) {
const waveformStore = db.createObjectStore("waveforms", {
keyPath: "id",
});
waveformStore.createIndex("reportId", "reportId", { unique: false });
}
};
});
};
2. 数据加密存储
// encryption.js
import JSEncrypt from "jsencrypt";
const encrypt = new JSEncrypt();
encrypt.setPublicKey(process.env.VUE_APP_PUBLIC_KEY);
export const encryptData = (data) => {
return encrypt.encrypt(JSON.stringify(data));
};
export const decryptData = (encryptedData) => {
const decrypt = new JSEncrypt();
decrypt.setPrivateKey(process.env.VUE_APP_PRIVATE_KEY);
return JSON.parse(decrypt.decrypt(encryptedData));
};
3. Vue 组件实现
<!-- ReportViewer.vue -->
<template>
<div class="report-viewer">
<div v-if="loading" class="loading">
<loading-spinner />
</div>
<template v-else>
<div class="report-header">
<h1>{{ report.title }}</h1>
<div class="meta-info">
<span>患者:{{ report.patientName }}</span>
<span>时间:{{ formatDate(report.timestamp) }}</span>
</div>
</div>
<div class="report-content">
<div class="waveform-container">
<waveform-chart :data="waveformData" />
</div>
<div class="report-text">{{ report.content }}</div>
</div>
</template>
</div>
</template>
<script>
import { initDB } from "@/utils/db";
import { decryptData } from "@/utils/encryption";
import WaveformChart from "./WaveformChart.vue";
import LoadingSpinner from "./LoadingSpinner.vue";
export default {
name: "ReportViewer",
components: {
WaveformChart,
LoadingSpinner,
},
data() {
return {
loading: true,
report: null,
waveformData: null,
db: null,
};
},
async created() {
try {
this.db = await initDB();
await this.loadReport();
} catch (error) {
console.error("Failed to load report:", error);
} finally {
this.loading = false;
}
},
methods: {
async loadReport() {
const reportId = this.$route.params.id;
// 从 IndexedDB 加载报告数据
const report = await this.getReportFromDB(reportId);
const waveform = await this.getWaveformFromDB(reportId);
// 解密数据
this.report = decryptData(report.encryptedData);
this.waveformData = decryptData(waveform.encryptedData);
},
async getReportFromDB(id) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(["reports"], "readonly");
const store = transaction.objectStore("reports");
const request = store.get(id);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
},
async getWaveformFromDB(reportId) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction(["waveforms"], "readonly");
const store = transaction.objectStore("waveforms");
const index = store.index("reportId");
const request = index.get(reportId);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
},
formatDate(timestamp) {
return new Date(timestamp).toLocaleString();
},
},
};
</script>
<style scoped>
.report-viewer {
padding: 20px;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.report-header {
margin-bottom: 20px;
}
.meta-info {
color: #666;
font-size: 14px;
}
.meta-info span {
margin-right: 20px;
}
.waveform-container {
margin: 20px 0;
border: 1px solid #eee;
border-radius: 4px;
padding: 10px;
}
.report-text {
line-height: 1.6;
}
</style>
性能优化
1. 数据预加载
// preload.js
export const preloadReports = async (db, reportIds) => {
const transaction = db.transaction(["reports", "waveforms"], "readonly");
const reportStore = transaction.objectStore("reports");
const waveformStore = transaction.objectStore("waveforms");
const reports = await Promise.all(reportIds.map((id) => reportStore.get(id)));
const waveforms = await Promise.all(
reportIds.map((id) => {
const index = waveformStore.index("reportId");
return index.get(id);
})
);
return { reports, waveforms };
};
2. 数据分片存储
对于大型波形数据,采用分片存储策略:
// waveformStorage.js
const CHUNK_SIZE = 1024 * 1024; // 1MB per chunk
export const storeWaveform = async (db, reportId, waveformData) => {
const chunks = splitIntoChunks(waveformData, CHUNK_SIZE);
const transaction = db.transaction(["waveforms"], "readwrite");
const store = transaction.objectStore("waveforms");
for (let i = 0; i < chunks.length; i++) {
await store.put({
id: `${reportId}_${i}`,
reportId,
chunkIndex: i,
data: chunks[i],
});
}
};
const splitIntoChunks = (data, size) => {
const chunks = [];
for (let i = 0; i < data.length; i += size) {
chunks.push(data.slice(i, i + size));
}
return chunks;
};
使用效果
通过以上实现,我们达到了以下目标:
首屏加载时间 < 1s
- 使用 IndexedDB 的索引功能实现快速检索
- 数据预加载策略
- 分片存储大型波形数据
完整的离线功能
- 支持查看完整的报告内容
- 包含波形图等复杂数据
- 数据加密存储保证安全性
良好的用户体验
- 流畅的加载过程
- 响应式界面设计
- 错误处理和加载状态提示
注意事项
- 定期清理过期数据,避免存储空间占用过大
- 实现数据同步机制,确保离线数据与服务器数据的一致性
- 考虑添加数据压缩功能,进一步优化存储空间
- 实现错误重试机制,提高系统稳定性
Vue2与Vue3的Diff算法对比
· 7 min read
Vue2 与 Vue3 的 Diff 算法对比
Vue 的虚拟 DOM 和 Diff 算法是其高效渲染的核心。Vue3 在 Vue2 的基础上对 Diff 算法进行了重大优化,本文将深入对比两者的差异,并分析这些优化带来的性能提升。
1. 虚拟 DOM 基础
虚拟 DOM 结构
Vue2 和 Vue3 的虚拟 DOM 结构有所不同:
// Vue2 的虚拟 DOM 结构
{
tag: 'div',
data: {
attrs: { id: 'app' },
on: { click: handler }
},
children: [
{
tag: 'span',
data: { attrs: { class: 'text' } },
children: ['Hello']
}
]
}
// Vue3 的虚拟 DOM 结构
{
type: 'div',
props: {
id: 'app',
onClick: handler
},
children: [
{
type: 'span',
props: { class: 'text' },
children: ['Hello']
}
]
}
主要区别
- Vue3 使用
type
替代tag
表示节点类型 - Vue3 扁平化处理 props,不再区分 attrs 和 on
- Vue3 的虚拟 DOM 结构更加简洁,减少了内存占用
2. Diff 算法核心流程
Vue2 的 Diff 算法
Vue2 的 Diff 算法采用双端比较的方式:
function updateChildren(parentElm, oldCh, newCh) {
let oldStartIdx = 0
let oldEndIdx = oldCh.length - 1
let newStartIdx = 0
let newEndIdx = newCh.length - 1
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (sameVnode(oldCh[oldStartIdx], newCh[newStartIdx])) {
patchVnode(oldCh[oldStartIdx], newCh[newStartIdx])
oldStartIdx++
newStartIdx++
} else if (sameVnode(oldCh[oldEndIdx], newCh[newEndIdx])) {
patchVnode(oldCh[oldEndIdx], newCh[newEndIdx])
oldEndIdx--
newEndIdx--
} else if (sameVnode(oldCh[oldStartIdx], newCh[newEndIdx])) {
patchVnode(oldCh[oldStartIdx], newCh[newEndIdx])
parentElm.insertBefore(oldCh[oldStartIdx].elm, oldCh[oldEndIdx].elm.nextSibling)
oldStartIdx++
newEndIdx--
} else if (sameVnode(oldCh[oldEndIdx], newCh[newStartIdx])) {
patchVnode(oldCh[oldEndIdx], newCh[newStartIdx])
parentElm.insertBefore(oldCh[oldEndIdx].elm, oldCh[oldStartIdx].elm)
oldEndIdx--
newStartIdx++
} else {
// 创建 key 到 index 的映射
const idxInOld = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
const idxInNew = newCh[newStartIdx].key
if (idxInOld[idxInNew]) {
// 移动节点
const vnodeToMove = oldCh[idxInOld[idxInNew]]
patchVnode(vnodeToMove, newCh[newStartIdx])
oldCh[idxInOld[idxInNew]] = undefined
parentElm.insertBefore(vnodeToMove.elm, oldCh[oldStartIdx].elm)
} else {
// 创建新节点
createElm(newCh[newStartIdx], parentElm, oldCh[oldStartIdx].elm)
}
newStartIdx++
}
}
}
Vue3 的 Diff 算法
Vue3 采用快速 Diff 算法,主要优化点:
function patchKeyedChildren(c1, c2, container) {
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
// 1. 从头部开始比较
while (i <= e1 && i <= e2 && c1[i].key === c2[i].key) {
patch(c1[i], c2[i], container)
i++
}
// 2. 从尾部开始比较
while (i <= e1 && i <= e2 && c1[e1].key === c2[e2].key) {
patch(c1[e1], c2[e2], container)
e1--
e2--
}
// 3. 处理新增和删除的节点
if (i > e1) {
// 新增节点
const nextPos = e2 + 1
const anchor = nextPos < c2.length ? c2[nextPos].el : null
while (i <= e2) {
patch(null, c2[i], container, anchor)
i++
}
} else if (i > e2) {
// 删除节点
while (i <= e1) {
unmount(c1[i])
i++
}
} else {
// 4. 处理未知序列
const s1 = i
const s2 = i
const keyToNewIndexMap = new Map()
// 建立新子节点的 key 到 index 的映射
for (let i = s2; i <= e2; i++) {
keyToNewIndexMap.set(c2[i].key, i)
}
// 更新和移动节点
let moved = false
let maxNewIndexSoFar = 0
const toBePatched = e2 - s2 + 1
const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
for (let i = s1; i <= e1; i++) {
const oldVNode = c1[i]
const newIndex = keyToNewIndexMap.get(oldVNode.key)
if (newIndex === undefined) {
unmount(oldVNode)
} else {
newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
patch(oldVNode, c2[newIndex], container)
}
}
// 移动节点
if (moved) {
const seq = getSequence(newIndexToOldIndexMap)
let j = seq.length - 1
for (let i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex]
const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null
if (newIndexToOldIndexMap[i] === 0) {
patch(null, nextChild, container, anchor)
} else if (moved) {
if (j < 0 || i !== seq[j]) {
move(nextChild, container, anchor)
} else {
j--
}
}
}
}
}
}
3. 主要优化点
静态标记
Vue3 引入了静态标记(PatchFlag),用于标记动态内容:
// Vue3 的静态标记示例
const vnode = {
type: 'div',
props: {
id: 'app',
class: 'container'
},
children: [
{
type: 'span',
props: { class: 'text' },
children: ['Hello'],
patchFlag: 1 // 表示文本内容可能变化
}
]
}
静态提升
Vue3 将静态节点提升到渲染函数之外:
// Vue3 的静态提升
const hoisted = createVNode('div', null, '静态内容')
function render() {
return createVNode('div', null, [
hoisted,
createVNode('span', null, state.message)
])
}
事件监听器缓存
Vue3 缓存事件监听器,避免重复创建:
// Vue3 的事件监听器缓存
const render = (ctx) => {
return createVNode('button', {
onClick: ctx.onClick // 事件监听器被缓存
}, '点击')
}
4. 性能对比
时间复杂度
- Vue2: O(n²)
- Vue3: O(n)
内存占用
- Vue2: 较大的内存占用
- Vue3: 显著减少的内存占用
实际性能提升
- 首次渲染性能提升约 50%
- 更新性能提升约 100%
- 内存占用减少约 50%
5. 最佳实践
合理使用 key
<!-- 推荐:使用唯一且稳定的 key -->
<template v-for="item in items" :key="item.id">
<div>{{ item.name }}</div>
</template>
<!-- 不推荐:使用索引作为 key -->
<template v-for="(item, index) in items" :key="index">
<div>{{ item.name }}</div>
</template>
避免不必要的更新
// 使用 shallowRef 减少响应式开销
const state = shallowRef({
count: 0,
user: {
name: 'John'
}
})
// 使用 markRaw 标记不需要响应式的对象
const staticData = markRaw({
config: {
theme: 'dark'
}
})
合理使用组件
<!-- 使用异步组件减少初始加载时间 -->
const AsyncComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
<!-- 使用 keep-alive 缓存组件状态 -->
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
6. 总结
Vue3 的 Diff 算法相比 Vue2 有了显著的改进:
- 采用快速 Diff 算法,时间复杂度从 O(n²) 降低到 O(n)
- 引入静态标记和静态提升,减少不必要的更新
- 优化事件监听器,减少内存占用
- 提供更多性能优化 API,如 shallowRef、markRaw 等
这些优化使得 Vue3 在性能上有了质的飞跃,特别是在大型应用中,性能提升更为明显。开发者应该充分了解这些优化,并在实际开发中合理运用,以充分发挥 Vue3 的性能优势。
Vue2源码学习
· 5 min read
Vue.js 是一个渐进式JavaScript框架,其源码设计精巧,包含了响应式系统、虚拟DOM、模板编译等核心功能。本文将从源码角度深入分析Vue2的核心实现原理。
CocosCreator3.x学习记录
· 7 min read
CocosCreator 是一个功能强大的游戏开发引擎,它提供了完整的 3D 和 2D 游戏开发解决方案。本文记录我在学习 CocosCreator 过程中的重要知识点和相关实践。
Vue的模板编译
· 9 min read
1.元素挂载顺序
vue元素挂载的顺序之分
- 如果存在render函数,就用render函数渲染组件;
- 如果没有render函数,但是存在template,就将template中的内容编译成render函数,最后做渲染;
- 如果既没有render函数也没有template函数,就获取el里的内容作为template,同样编译成render函数。
Vue.prototype.$mount = function (el) {
// 挂载
const vm = this;
const options = vm.$options;
el = document.querySelector(el);
vm.$el = el;
if(!options.render) { // 没有render方法
let template = options.template;
if(!template && el){ // 没有template 但是有el 获取el中的内容
template = el.outerHTML
}
// 将模板编译成render函数
const render = compileToFunctions(template)
options.render = render;
}
// 渲染时用的都是render函数
// 挂载组件
mountComponent(vm,el);
}
前端的错误监控和上报
· 6 min read
Vue3与Vite项目记录
· 5 min read
Node.js学习
· 12 min read
数据结构和算法学习笔记
· 11 min read
时间复杂度
一般看的都是 O(1) ~ O(n^2) 范围,其它都是需要优化的
O(1)
- if(i==1)
- res = 100 1000、res = n 200
- push(2)、pop()
- map.set(1,1)、map.get(1)
- 计算复杂度时,O(1)一般会被忽略
O(n)
- for循环,while循环(不使用二分搜索)
O(log n)
- 二分搜索