1.父子组件间数据传递
1.1 单项绑定和props
bash
单项绑定:attruName 向 const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
props.foo // string
props.bar // number | undefined
///////////////////////////////////////////////////////////////////////////////////////
const props = defineProps<{
foo: string
bar?: number
}>()
/////////////////////////////////////////////////////////////
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
//////////////////////////////////////////////////////////////////////
传递数据
1.2 provide向inject穿数据
bash
父组件使用:provide('location', {
location,
updateLocation
})
任意子组件使用:inject<string>('location')获得{
location,
updateLocation
}
1.3数据总线
创建数据总线
创建event-bus.js文件
bash
import mitt from 'mitt';
// 创建一个 mitt 实例并导出
const emitter = mitt();
export default emitter;
在vue组件中发布事件
bash
import { ref } from 'vue';
import emitter from './event-bus.js'; // 导入事件总线
const message = ref('Hello from Vue 3!');
const sendMessage = () => {
// 发布一个名为 'messageEvent' 的事件,并携带数据
emitter.emit('messageEvent', message.value);
// 也可以发布没有数据的事件
// emitter.emit('someNotification');
};
在任意vue组件中订阅
bash
import emitter from './event-bus.js'; // 导入事件总线
const receivedMessage = ref('');
// 定义处理事件的函数
const handleMessage = (data) => {
receivedMessage.value = data;
console.log('收到数据:', data);
};
// 组件挂载后订阅事件
onMounted(() => {
emitter.on('messageEvent', handleMessage);
});
// 组件卸载前取消订阅
onUnmounted(() => {
emitter.off('messageEvent', handleMessage);
});
2 Symbol 在 Vue3 中的核心应用场景
2.1 依赖注入(Provide/Inject)
用 Symbol 作为键,避免多个组件提供同名值时产生冲突。
bash
<!-- ParentComponent.vue -->
<script>
// 创建唯一的 Symbol 键
// ThemeSymbol 需要在父组件和子组件中使用,所以需要 export
// 又因为script setup 不能导出,所以另写一个<script>来导出
export const ThemeSymbol = Symbol('theme')
</script>
<script setup>
import { provide } from 'vue'
// 提供值
provide(ThemeSymbol, 'dark')
</script>
<!-- ChildComponent.vue -->
<script setup>
import { inject } from 'vue'
import { ThemeSymbol } from './ParentComponent.vue'
// 注入值
const theme = inject(ThemeSymbol)
console.log(theme) // 'dark'
</script>
2.2 定义全局唯一标识
Vue3 内部使用 Symbol 标记特殊属性(如 Fragment、Teleport 等组件类型)。
bash
import { h, defineComponent } from 'vue'
const CustomType = Symbol('custom-component')
export default defineComponent({
setup() {
return () => h(CustomType, {}, 'Hello Custom Component')
}
})
2.3 避免响应式属性冲突
在 reactive 对象中使用 Symbol 键,避免属性被意外覆盖。
bash
import { reactive } from 'vue'
const PRIVATE_KEY = Symbol('private_data')
const obj = reactive({
[PRIVATE_KEY]: 'secret', // 不会被意外访问到
publicData: 'open'
})
console.log(obj[PRIVATE_KEY]) // 'secret'
3组件注册
3.1全局祖册
bash
import { createApp } from 'vue'
import MyComponent from './App.vue'
const app = createApp({})
app.component(
// 注册的名字
'MyComponent',MyComponent
)
//链式调用
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
3.2局部注册
bash
<script setup>
import ComponentA from './ComponentA.vue'
</script>
<template>
<ComponentA />
</template>
4 响应式
4.1 ref
bash
import { ref } from 'vue'
const count = ref(0)
4.2 reactive()
reactive() 用于创建一个深层响应式对象,对象的所有嵌套属性都会变成响应式的
bash
import { reactive } from 'vue'
// 创建响应式对象
const state = reactive({
count: 0,
user: {
name: 'Alice',
profile: {
age: 25,
email: 'alice@example.com'
}
},
hobbies: ['reading', 'swimming']
})
4.3 refs
refs 是用于直接访问 DOM 元素或子组件实例
html
<div ref="divRef" class="content">这是一个 div 元素</div>
// 声明 ref - 名称必须与模板中的 ref 属性一致
const divRef = ref(null)
4.4 toRef
toRef:用于创建单个属性的引用,保持与源对象的响应式连接
基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值。
bash
import { reactive, ref, toRef } from 'vue'
const state = reactive({ name: 'Alice' })
const ref1 = ref(state.name) // 创建新的独立 ref
const ref2 = toRef(state, 'name') // 保持与源对象的连接
state.name = 'Bob'
console.log(ref1.value) // 'Alice' - 没有变化!
console.log(ref2.value) // 'Bob' - 同步变化
4.5 toRefs
toRefs:用于解构整个响应式对象,让所有属性保持响应式
bash
import { reactive, toRefs } from 'vue'
const state = reactive({
name: 'Alice',
age: 25,
address: {
city: 'Beijing'
}
})
// 使用 toRefs 解构所有属性
const { name, age, address } = toRefs(state)
console.log(name.value) // 'Alice'
console.log(age.value) // 25
// 修改 ref 的值会同步到原始对象
name.value = 'Bob'
console.log(state.name) // 'Bob'
// 修改原始对象也会同步到 ref
state.age = 30
console.log(age.value) // 30
const state = reactive({
name: 'Alice',
age: 25
})
// ❌ 错误:直接解构会失去响应式
const { name, age } = state
4.6 props转换
bash
const props = defineProps({
user: {
type: Object,
required: true
},
age: {
type: Number,
default: 0
}
})
// 将 props 转换为 refs,便于在组合式函数中使用
const { age } = toRefs(props)
// 对于嵌套对象的属性,使用 toRef
const username = toRef(props.user, 'name')
4.7 reactive 和ref区别
| 特性 | ref | reactive |
|---|---|---|
| 数据类型 | 任意类型(基本类型、对象、数组) | 只能是对象(包括数组、Map、Set) |
| 访问方式 | 需要通过 .value.属性名 | 直接访问属性 |
| 模板使用 | 自动解包(不需要 .value) | 直接访问 |
| 响应式原理 | 通过 .value 属性的 getter/setter | 通过 Proxy 代理 |
5响应式进阶
6 计算属性
bash
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// 一个计算属性 ref
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<template>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</template>
computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value。
Vue 的计算属性会自动追踪响应式依赖。它会检测到 publishedBooksMessage 依赖于 author.books,所以当 author.books 改变时,任何依赖于 publishedBooksMessage 的绑定都会同时更新
计算属性与函数的区别
计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 author.books 不改变,无论多少次访问 publishedBooksMessage 都会立即返回先前的计算结果,而不用重复执行 getter 函数。
6.1可写计算属性
bash
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// 注意:我们这里使用的是解构赋值语法
[firstName.value, lastName.value] = newValue.split(' ')
}
})
7Class 与 Style 绑定
bash
const isActive = ref(true)
const hasError = ref(false)
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
渲染的结果是
bash
<div class="static active"></div>
也可以绑定对象
bash
const classObject = reactive({
active: true,
'text-danger': false
})
<div :class="classObject"></div>
绑定数组
bash
const activeClass = ref('active')
const errorClass = ref('text-danger')
<div :class="[activeClass, errorClass]"></div>
三元表达式
bash
<div :class="[isActive ? activeClass : '', errorClass]"></div>
数组中嵌套对象
bash
<div :class="[{ [activeClass]: isActive }, errorClass]"></div>
8 绑定内联样式
:style 支持绑定 JavaScript 对象值,对应的是 HTML 元素的 style 属性:
bash
const activeColor = ref('red')
const fontSize = ref(30)
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
//:style 也支持 kebab-cased 形式的 CSS 属性 key (对应其 CSS 中的实际名称)
<div :style="{ 'font-size': fontSize + 'px' }"></div>
直接绑定一个样式对象
bash
const styleObject = reactive({
color: 'red',
fontSize: '30px'
})
<div :style="styleObject"></div>
style 绑定一个包含多个样式对象的数组。这些对象会被合并后渲染到同一元素上:
bash
<div :style="[baseStyles, overridingStyles]"></div>
9 条件渲染
9.1 v-if vs. v-show区别
v-if 是"真实的"按条件渲染,确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染
v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
9.2 v-if 和 v-for
当 v-if 和 v-for 同时存在于一个元素上的时候,v-if 会首先被执行
10 列表渲染
bash
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
{{ item.message }}
</li>
在 v-for 块中可以完整地访问父作用域内的属性和变量。v-for 也支持使用可选的第二个参数表示当前项的位置索引。
bash
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
10.1 forEachv-for 与对象
使用 v-for 来遍历一个对象的所有属性。遍历的顺序会基于对该对象调用 Object.values() 的返回值来决定
bash
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
通过提供第二个参数表示属性名 (例如 key):
bash
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
第三个参数表示位置索引:
bash
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
10.2 forEach
bash
const parentMessage = 'Parent'
const items = [
/* ... */
]
items.forEach( (item, index) =>{
console.log(parentMessage, item.message, index)
})
11 数组操作方法
| 方法 | 用途 | 示例 |
|---|---|---|
| push() | 末尾添加元素 | array.push(element1, element2, ..., elementN) |
| pop() | 删除末尾元素并返回被移除的元素 | array.pop() |
| shift() | 删除开头元素并返回被移除的元素 | array.shift() |
| unshift() | 开头添加元素 | array.unshift(element1, element2, ..., elementN) |
| splice() | 删除/替换元素 | // 删除:从索引1开始删除2个元素const removed = colors.splice(1, 2); |
| splice() | 删除/替换元素 | // 替换:从索引1开始删除1个元素,插入新元素colors.splice(1, 1, 'purple', 'orange') |
| splice() | 删除/替换元素 | // 插入:从索引1开始删除0个元素,插入新元素colors.splice(1, 0, 'pink') |
| sort() | 数组排序 | const numbers = 3, 1, 4, 1, 5, 9 numbers.sort((a, b) => a - b) // 升序 |
| reverse() | 数组反转 | array.reverse() |
| filter() | 过滤数组元素 | const numbers = 1, 2, 3, 4, 5, 6 const evenNumbers = numbers.filter(num => num % 2 === 0) |
| concat() | 合并数组 /单个元素 | const newArray = array1.concat(array2, array3, ..., item1, item2, ...) |
| slice() | 截取数组片段 | const newArray = array.slice(start, end) |
12 事件处理
12.1 方法事件处理器
bash
<!-- `greet` 是上面定义过的方法名 -->
<button @click="greet">Greet</button>
const name = ref('Vue.js')
function greet(event) {
alert(`Hello ${name.value}!`)
// `event` 是 DOM 原生事件
if (event) {
alert(event.target.tagName)
}
}
12.2 内联事件处理器
bash
const count = ref(0)
<button @click="count++">Add 1</button>
<p>Count is: {{ count }}</p>
12.3 在内联事件处理器中访问事件参数
有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数:
bash
<!-- 使用特殊的 $event 变量 -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- 使用内联箭头函数 -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
12.4 事件修饰符
bash
<!-- 单击事件将停止传递 -->
<a @click.stop="doThis"></a>
<!-- 提交事件将不再重新加载页面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修饰语可以使用链式书写 -->
<a @click.stop.prevent="doThat"></a>
<!-- 也可以只有修饰符 -->
<form @submit.prevent></form>
<!-- 仅当 event.target 是元素本身时才会触发事件处理器 -->
<!-- 例如:事件处理器不来自子元素 -->
<div @click.self="doThat">...</div>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 -->
<!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 -->
<div @click.capture="doThis">...</div>
<!-- 点击事件最多被触发一次 -->
<a @click.once="doThis"></a>
<!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 -->
<!-- 以防其中包含 `event.preventDefault()` -->
<div @scroll.passive="onScroll">...</div>
13 表单
13.1 文本与多行文本
bash
<p style="white-space: pre-line;">{{ message }}</p>
<input v-model= "message" placeholder="edit me">
<textarea v-model="message"> </textarea>
13.2 复选框
13.2.1 单一复选框
单一的复选框,绑定布尔类型值:
bash
<input type="checkbos" v-model="checked">
13.2.2 将多个复选框绑定到同一个数组或集合的值:
bash
const checkedNames = ref([])
<div>Checked names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
Mike中for通过mike与id=mike的input关联起来。for 属性将 标签与表单元素关联起来,通过元素的 id 建立连接。
13.3 单选框
bash
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
13.4 选择器
13.4.1 单项选择器
bash
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
</select>
13.4.2 多项选择器
bash
<select v-model="selected" multiple>
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
</select>
可以使用ABC
13.4.3 v-model修饰符
bash
<!-- 在 "change" 事件后同步更新而不是 "input" -->
<input v-model.lazy="msg" />
输入自动转换为数字,如果该值无法被 parseFloat() 处理,那么将返回原始值,number 修饰符会在输入框有 type="number" 时自动启用。
<input v-model.number="age" />
自动去除用户输入内容中两端的空格
<input v-model.trim="msg" />
14 组件 v-model
父组件
bash
<UserName
v-model:first-name="first"
v-model:last-name="last"
/>
子组件
bash
<script setup>
const firstName =defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<template>
<input type="text" v-model="firstName" />
<input type="text" v-model="lastName" />
</template>
15 浅复制和深复制
| 维度 | 浅复制 (Shallow Copy) | 深复制 (Deep Copy) |
|---|---|---|
| 复制层级 | 只复制第一层属性 | 递归复制所有嵌套层级 |
| 引用关系 | 嵌套对象仍指向原对象的引用 | 完全独立,与原对象无引用关系 |
| 修改影响 | 修改嵌套对象会相互影响 | 修改任何层级互不影响 |
| 性能 | 快,开销较小 | 速度较慢,开销大(尤其是大数据) |
浅复制的常见方法(ES6+)
展开运算符 ...
bash
const original = { name: 'Alice', address: { city: '北京' } };
const copy = { ...original };
copy.name = 'Bob'; // 不影响原对象
copy.address.city = '上海'; // 影响原对象 (嵌套引用)
console.log(original.address.city); // '上海'
Object.assign()
bash
const copy = Object.assign({}, original);
// 效果同展开运算符,只复制第一层
数组的 slice() 和 concat()
bash
const arr = [1, 2, [3, 4]];
const copy = arr.slice(); // 或 arr.concat()
copy[0] = 999; // 不影响原数组
copy[2][0] = 0; // 影响原数组
console.log(arr[2][0]); // 0
数组的展开运算符
bash
const copy = [...arr];
深复制的常见方法
structuredClone()(推荐,ES2022+)
bash
const original = {
name: 'Alice',
address: { city: '北京' },
date: new Date('2026-07-20')
};
const copy = structuredClone(original);
copy.address.city = '上海';
console.log(original.address.city); // '北京'(不受影响)
console.log(copy.date instanceof Date); // true(能正确克隆Date)
优点:原生、快、支持Date/RegExp/Map/Set等
缺点:不支持函数、Symbol、DOM元素
JSON.parse(JSON.stringify())
bash
const copy = JSON.parse(JSON.stringify(original));
lodash 的 cloneDeep
bash
import cloneDeep from 'lodash/cloneDeep';
const copy = cloneDeep(original);
将一个组件转为DOM挂载带指定div标签
使用 createApp(最常用,适合独立组件)
bash
import { createApp } from 'vue'
import MyComponent from './MyComponent.vue'
// 1. 找到目标容器
const container = document.getElementById('my-target')
// 2. 创建应用实例并挂载
const app = createApp(MyComponent, {
// 可选的props数据
title: 'Hello World',
userId: 123
})
// 3. 挂载到指定容器
app.mount(container)
优点:完整组件生命周期、响应式数据、事件监听都正常工作
缺点:每个实例都是独立的应用,无法共享全局配置
使用 createVNode + render(更轻量,适合弹窗/提示),虚拟节点和渲染函数
bash
import { createVNode, render } from 'vue'
import MyComponent from './MyComponent.vue'
// 1. 创建虚拟节点
const vnode = createVNode(MyComponent, {
// props
message: '这是一个提示',
onClose: () => {
// 关闭时销毁
render(null, container)
document.body.removeChild(container)
}
})
// 2. 创建一个容器div
const container = document.createElement('div')
document.body.appendChild(container)
// 3. 渲染虚拟节点到容器
render(vnode, container)
// 4. 获取组件实例(可选)
const instance = vnode.component
优点:灵活、轻量、适合动态组件
缺点:需要手动管理生命周期
使用 (适合将组件传送到指定位置)
如果组件已经在模板中定义,只是想改变它的渲染位置,Vue 3 的 是最好选择。
bash
<template>
<!-- 这里定义组件,但渲染到 body 下 -->
<Teleport to="body">
<MyComponent v-if="show" :message="msg" @close="show = false" />
</Teleport>
<!-- 或者传送到指定ID的容器 -->
<Teleport to="#modal-root">
<MyComponent />
</Teleport>
</template>
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const show = ref(false)
const msg = ref('Hello')
</script>
vnode + appContext(继承全局配置)
动态创建的组件能继承主应用的全局配置(如插件、全局组件等)
bash
import { createVNode, render } from 'vue'
import mainApp from './main' // 导入主应用的app实例
function mountWithContext(component, props, container) {
const vnode = createVNode(component, props)
// 继承主应用的上下文
vnode.appContext = mainApp._context
render(vnode, container)
return vnode
}
对比
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| createApp | 独立功能模块、第三方组件挂载 | 完整生命周期、简单直观 | 无法共享全局配置 |
| createVNode + render | 全局提示、弹窗、动态加载 | 轻量、灵活、可多次创建 | 需手动管理销毁 |
| Teleport | 模态框、下拉菜单、通知 | 声明式、符合Vue风格 | 只能传送到已存在的DOM |
| 继承appContex | 继承appContext | 保持配置一致性 | 实现稍复杂 |
防抖与限流
防抖(Debounce):连续触发时,只执行最后一次。适用于输入框搜索、窗口大小调整。
限流(Throttle):连续触发时,每隔一段时间执行一次。适用于滚动加载、按钮连点、游戏刷新。
原生 JS 实现原理
bash
//防抖
//fn是方法
function debounce(fn,display=300){
let timer = null
return function (...args) {
clearTimeOut(timer)
timer = setTimeOut(()=>{
fn.apply(his,args)
},display)
}
}
//限流
//fn是方法
function throttle(fn,interval = 300){
let lastTime = 0
return function (...args){
const now = Date,now
if(now - lastTime >= interval){
lastTime = now
fn.apply(this,args)
}
}
}
页面渲染大数据限制数据使用内存
虚拟滚动(Virtual Scrolling)适用于长列表场景
实现原理:
- 用一个固定高度的容器作为"视口",并设置滚动条。
- 通过一个"占位元素"撑出与总数据量相匹配的滚动条高度。
- 监听滚动事件,计算出当前应该显示的数据项起始索引 (startIndex),并从完整数据中"切片"取出这部分数据渲染。
效果:无论有10万条还是100万条数据,DOM节点数恒定,内存占用极低,滚动丝滑。
推荐做法:直接使用成熟库,它们帮你处理好了滚动计算、缓冲区和性能优化等复杂细节。
React:react-window(轻量)、react-virtualized(功能更全)、@tanstack/react-virtual(无头库,灵活性高)。
Vue :vue-virtual-scroller。
官网文档:https://vue-virtual-scroller.netlify.app/guide/recycle-scroller.html
bash
<template>
<div class="list-container">
<RecycleScroller
class="scroller"
:items="userList"
:item-size="50"
key-field="id"
@scroll="handleScroll"
v-slot="{ item }">
<!-- 这里是每个列表项的模板,item 就是数据中的每一项 -->
<div class="user-item">
<span>{{ item.name }}</span>
<span>{{ item.email }}</span>
</div>
</RecycleScroller>
</div>
</template>
<script setup lang="ts">
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' // 放在这里
import {ref,onMounted} from 'vue'
// 处理滚动事件
function handleScroll(event: Event) {
const target = event.target as HTMLElement
const scrollTop = target.scrollTop
const scrollHeight = target.scrollHeight
const clientHeight = target.clientHeight
// 当滚动到距离底部 100px 时触发加载
if (scrollHeight - scrollTop - clientHeight < 100 && !loading.value) {
loadMore()
}
}
// 加载更多数据
function loadMore() {
if (loading.value) return
loading.value = true
console.log('加载更多...')
// 模拟异步加载
setTimeout(() => {
const startIndex = userList.value.length
const newItems = Array.from({ length: 1000 }, (_, i) => ({
id: startIndex + i,
name: `用户${startIndex + i}`,
email: `user${startIndex + i}@example.com`,
}))
userList.value = [...userList.value, ...newItems]
loading.value = false
console.log(`加载完成,当前总数: ${userList.value.length}`)
}, 500)
}
const userList: any = ref([])
onMounted(async ()=>{
// 模拟后端返回一万条数据
userList.value = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `用户${i}`,
email: `user${i}@example.com`,
}))
console.log("zzzzzzzz",userList.value[0])
})
</script>
<style scoped>
/* 容器必须有明确的高度,虚拟滚动才能正常工作 */
.list-container {
height: 600px;
width: 1080px;
background-color: yellow;
}
.scroller {
height: 100%;
}
.user-item {
height: 50px; /* 必须与 :item-size 属性值保持一致 */
display: flex;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid #eee;
}
</style>
vue-virtual-scroller的RecycleScroller属性@reach-end="loadMore"和@scroll-end="loadMore",@scroll="loadMore"
1. @reach-end 是 vue-virtual-scroller
提供的一个事件,用于监听当滚动条到达列表底部时触发。这对于实现"无限滚动"功能特别有用
2. @scroll-end 事件在滚动完全停止后触发,不同于 @reach-end,它在滚动停止时触发,无论滚动方向如何
3. @scroll建议使用@scroll前两个有时会触发失败,不执行对应方法
注:vue-virtual-scroller只能减少页面渲染时DOM节点的数量,不能减伤数据占用内存
数据处理与计算下沉
使用场景:数据处理(如排序、过滤、聚合)是CPU密集型任务,如果在浏览器主线程执行,会阻塞页面渲染,导致卡顿。可以考虑把它们移到其他地方。
Web Worker(多线程):创建一个独立的Worker线程,在里面进行复杂的数据计算,完成后将结果(而非原始数据)发回主线程渲染。这样主线程就能专注于UI响应,不会被计算任务"冻住"。
数据降采样:对于图表类数据,可以用算法减少数据点的数量。例如用LTTB算法,可以在保留数据整体趋势的前提下,将10万条数据压缩到1000条以内,在不影响显示效果的同时大幅降低内存和渲染压力。
数组自带方法filter + map + reduce使用
bash
// 1. 用 filter 筛选出活跃用户
const activeUsers = users.filter(user => user.isActive);
// 2. 用 map 提取并拼接全名
const activeFullNames = activeUsers.map(user => `${user.firstName} ${user.lastName}`);
// 3. 用 reduce 计算平均年龄
const averageAge = activeUsers.reduce((acc, user, index, array) => {
// 如果是最后一个元素,就返回最终的平均值;否则只累加
if (index === array.length - 1) {
return (acc + user.age) / array.length;
}
return acc + user.age;
}, 0);
console.log(activeFullNames); // ['张三', '王五', '赵六']
console.log(averageAge); // 25.666...
链式调用
bash
const result = users
.filter(user => user.isActive) // 过滤
.map(user => `${user.firstName} ${user.lastName}`); // 映射
const averageAge = users
.filter(user => user.isActive) // 过滤
.reduce((acc, user, index, array) => { // 归约
const sum = acc + user.age;
return index === array.length - 1 ? sum / array.length : sum;
}, 0);
console.log(result); // ['张三', '王五', '赵六']
console.log(averageAge); // 25.666...
reduce方法说明
核心语法
bash
array.reduce((accumulator, currentValue, currentIndex, array) => {
// ... 返回新的累加器值
}, initialValue);
- accumulator(累加器):这是 reduce
最关键的概念。它是上一次回调函数执行后返回的值,也是"记忆"的载体。第一次执行时,它的值是 initialValue。 - currentValue(当前元素):数组中当前正在被处理的元素。
- currentIndex(当前索引,可选):当前元素在数组中的索引。
- array(原数组,可选):调用 reduce 的数组本身。
- initialValue(初始值,可选):第一次调用 callback 时 accumulator
的值。如果没提供,则会用数组的第一个元素作为初始值。
reduce 的核心就是"把上一次的处理结果,带到下一次处理中"。
Web Worker
核心作用
Web Worker允许在浏览器后台线程中运行脚本,避免阻塞主线程(UI渲染)适合处理以下场景:大数据计算(如百万级数组处理),图像/视频编解码,复杂算法执行(如机器学习推理)
关键限制
无DOM访问权限:Worker线程中无法操作document或window对象
通信机制:通过postMessage和onmessage实现数据交换,采用结构化克隆算法进行深拷贝
文件限制:需通过独立JS文件定义Worker逻辑
Vue3集成Web Worker
在src/workers/目录下新建compute.worker.js,实现计算密集型任务,
使用sleep方法阻塞线程,验证主线程没被阻塞
groovy
// compute.worker.js
self.onmessage = async function (e) {
const { data } = e;
let result = 0;
for (let i = 0; i < data.max; i++) {
result += Math.sqrt(i); // 模拟复杂计算
}
await sleep(5000)
console.log('5秒后执行1')
self.postMessage({ result });
};
function sleep(ms:any) {
return new Promise(resolve => setTimeout(resolve, ms))
}
代码说明:
- self代表Worker全局作用域
- 通过onmessage接收主线程数据
- 计算完成后通过postMessage返回结果
Vue组件中集成Worker
bash
<template>
<div>
<button @click="startCompute">开始计算</button>
<p>结果: {{ result }}</p>
<button @click="startComputed1">开始计算</button>
<p>结果: {{ result2 }}</p>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const result = ref(null);
const result2 = ref(null);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const startCompute = () => {
// 动态加载Worker文件(Vite/Webpack通用方案)
const worker = new Worker(new URL('./workers/compute.worker.ts', import.meta.url));
// 发送数据到Worker
worker.postMessage({ max: 10000000 });
// 接收Worker返回结果
worker.onmessage = (e) => {
result.value = e.data.result;
worker.terminate(); // 计算完成后终止Worker
};
// 错误处理
worker.onerror = (e) => {
console.error('Worker错误:', e.message);
};
console.log("主线程1");
};
const startComputed1 = async () => {
let result1 = 0;
for (let i = 0; i < 10000000; i++) {
result1 += Math.sqrt(i); // 模拟复杂计算
}
await sleep(5000);
console.log('5秒后执行2')
console.log("主线程2");
result2.value = result1;
}
</script>
关键点说明:
new URL()解决Worker文件路径问题
terminate()及时释放资源避免内存泄漏
必须处理onerror事件捕获异常
点击"开始计算"可以看到使用web worker的方法先输出"主线程1"后输出'5秒后执行1'主线程没阻塞;
没使用web worker的方法先输出"5秒后执行2"后输出'主线程1'主线程被阻塞;
性能优化
大任务拆分为多个子任务,通过多个Worker并行处理
- Worker 脚本
bash
// worker.js - 子任务执行器
self.onmessage = function(e) {
const { taskId, data, taskType } = e.data
try {
console.log(`Worker ${taskId} 开始处理,数据量: ${data.length}`)
// 执行具体的计算任务
const result = processTask(data, taskType)
// 返回结果给主线程
self.postMessage({
taskId,
success: true,
result,
timestamp: Date.now()
})
} catch (error) {
// 错误处理
self.postMessage({
taskId,
success: false,
error: error.message
})
}
}
// 任务处理函数(可自定义)
function processTask(data, taskType) {
switch(taskType) {
case 'sort':
// 示例:排序任务
return data.sort((a, b) => a - b)
case 'filter':
// 示例:过滤任务
return data.filter(item => item % 2 === 0)
case 'map':
// 示例:映射任务
return data.map(item => item * 2)
case 'calculate':
// 示例:复杂计算
return data.reduce((sum, item) => sum + item, 0)
case 'matrix':
// 示例:矩阵运算
return processMatrix(data)
default:
return data
}
}
// 矩阵运算示例(CPU 密集型)
function processMatrix(matrix) {
const result = []
for (let i = 0; i < matrix.length; i++) {
result[i] = []
for (let j = 0; j < matrix[i].length; j++) {
// 模拟复杂计算
let value = matrix[i][j]
for (let k = 0; k < 100; k++) {
value = Math.sqrt(value * value + 1)
}
result[i][j] = value
}
}
return result
}
- 主线程 - 管理器
用法:const processor = new ParallelProcessor('worker.js')
bash
// parallelProcessor.js - 并行任务管理器
class ParallelProcessor {
constructor(workerScript, maxWorkers = navigator.hardwareConcurrency || 4) {
this.workerScript = workerScript
this.maxWorkers = Math.min(maxWorkers, navigator.hardwareConcurrency || 4)
this.workers = []
this.taskIdCounter = 0
this.pendingTasks = new Map()
}
/**
* 拆分数据并并行处理
*/
async process(data, taskType, chunkSize = null) {
// 1. 如果没有指定分块大小,根据数据量和 Worker 数量自动计算
if (!chunkSize) {
chunkSize = Math.ceil(data.length / this.maxWorkers)
}
// 2. 将数据拆分成多个块
const chunks = this.splitData(data, chunkSize)
console.log(`数据拆分完成,共 ${chunks.length} 块,每块 ${chunkSize} 条`)
// 3. 创建所需数量的 Worker
const workerCount = Math.min(chunks.length, this.maxWorkers)
const workers = this.createWorkers(workerCount)
// 4. 分配任务给 Worker
const tasks = chunks.map((chunk, index) => {
const worker = workers[index % workers.length]
return this.executeTask(worker, {
taskId: this.taskIdCounter++,
data: chunk,
taskType
})
})
try {
// 5. 等待所有子任务完成
const results = await Promise.all(tasks)
// 6. 汇总结果
const finalResult = this.mergeResults(results, taskType)
// 7. 清理资源
this.terminateAll()
return finalResult
} catch (error) {
// 错误处理
this.terminateAll()
throw error
}
}
/**
* 拆分数据
*/
splitData(data, chunkSize) {
const chunks = []
for (let i = 0; i < data.length; i += chunkSize) {
chunks.push(data.slice(i, i + chunkSize))
}
return chunks
}
/**
* 创建 Worker 池
*/
createWorkers(count) {
const workers = []
for (let i = 0; i < count; i++) {
const worker = new Worker(this.workerScript)
this.workers.push(worker)
workers.push(worker)
}
return workers
}
/**
* 执行单个任务
*/
executeTask(worker, taskData) {
return new Promise((resolve, reject) => {
// 生成唯一任务 ID
const taskId = taskData.taskId
// 监听消息
const messageHandler = (e) => {
const result = e.data
if (result.taskId === taskId) {
worker.removeEventListener('message', messageHandler)
if (result.success) {
resolve(result.result)
} else {
reject(new Error(result.error))
}
}
}
// 监听错误
const errorHandler = (error) => {
worker.removeEventListener('error', errorHandler)
reject(error)
}
worker.addEventListener('message', messageHandler)
worker.addEventListener('error', errorHandler)
// 发送任务
worker.postMessage(taskData)
})
}
/**
* 汇总结果
*/
mergeResults(results, taskType) {
switch(taskType) {
case 'sort':
// 排序任务:合并所有有序数组
return this.mergeSortedArrays(results)
case 'filter':
case 'map':
// 过滤/映射任务:直接合并数组
return results.flat()
case 'calculate':
// 计算任务:求和
return results.reduce((sum, val) => sum + val, 0)
case 'matrix':
// 矩阵任务:按行合并
return results.flat()
default:
return results.flat()
}
}
/**
* 合并多个有序数组(归并排序)
*/
mergeSortedArrays(arrays) {
if (arrays.length === 0) return []
if (arrays.length === 1) return arrays[0]
const result = []
const pointers = new Array(arrays.length).fill(0)
while (true) {
let minValue = Infinity
let minIndex = -1
// 找到当前最小值
for (let i = 0; i < arrays.length; i++) {
if (pointers[i] < arrays[i].length && arrays[i][pointers[i]] < minValue) {
minValue = arrays[i][pointers[i]]
minIndex = i
}
}
if (minIndex === -1) break
result.push(minValue)
pointers[minIndex]++
}
return result
}
/**
* 终止所有 Worker
*/
terminateAll() {
this.workers.forEach(worker => worker.terminate())
this.workers = []
this.pendingTasks.clear()
}
/**
* 获取 Worker 状态
*/
getStatus() {
return {
maxWorkers: this.maxWorkers,
activeWorkers: this.workers.length,
pendingTasks: this.pendingTasks.size
}
}
}
export default ParallelProcessor
ParallelProcessor使用:
bash
const processor = new ParallelProcessor('worker.js')
const sortedData = await processor.process(
data,
'sort',
Math.ceil(data.length / 4)
)
或processor.process(
data,
'sort',
Math.ceil(data.length / 4)
).then(res=>{
const sortedData = res
})
主线程发送两个任务到同一个 Worker,任务1 开始执行(占用 Worker),. 任务2 进入消息队列(等待),任务1 完成后,任务2 开始执行
简单示例
bash
// 主线程代码示例
const promises = [];
const chunkSize = 1000000;
const total = 10000000;
for (let i = 0; i < total; i += chunkSize) {
const worker = new Worker(new URL('./worker.js', import.meta.url));
//先监听,后发送任务
promises.push(new Promise(resolve => {
worker.onmessage = (e) => resolve(e.data);
}));
worker.postMessage({ start: i, end: Math.min(i + chunkSize, total) });
}
Promise.all(promises).then(results => {
console.log('最终结果:', results.reduce((a, b) => a + b, 0));
});
简单示例和前面ParallelProcessor类的executeTask方法效果相同。最后都是在Promise中通过worker的message监听返回的处理结果。
Worker 池(复用 Worker,避免频繁创建)
bash
class WorkerPool {
constructor(workerScript, poolSize) {
this.workerScript = workerScript
this.poolSize = poolSize
this.workers = []
this.taskQueue = []
this.availableWorkers = []
this.taskIdCounter = 0
this.pendingTasks = new Map()
// 初始化 Worker 池
for (let i = 0; i < poolSize; i++) {
const worker = new Worker(workerScript)
this.workers.push(worker)
this.availableWorkers.push(worker)
// 监听消息,worker线程中执行self.postMessage时触发
worker.addEventListener('message', (e) => {
this.handleTaskComplete(e.data)
})
}
}
execute(data, taskType) {
return new Promise((resolve, reject) => {
const taskId = this.taskIdCounter++
const task = { taskId, data, taskType, resolve, reject }
this.pendingTasks.set(taskId, task)
this.taskQueue.push(task)
this.processQueue()
})
}
processQueue() {
if (this.taskQueue.length === 0 || this.availableWorkers.length === 0) {
return
}
const task = this.taskQueue.shift()
const worker = this.availableWorkers.pop()
this.pendingTasks.set(task.taskId, task)
worker.postMessage({
taskId: task.taskId,
data: task.data,
taskType: task.taskType
})
}
handleTaskComplete(result) {
const task = this.pendingTasks.get(result.taskId)
if (!task) return
// 释放 Worker
this.availableWorkers.push(this.workers.find(w =>
!this.availableWorkers.includes(w)
))
if (result.success) {
task.resolve(result.result)
} else {
task.reject(new Error(result.error))
}
this.pendingTasks.delete(result.taskId)
this.processQueue()
}
terminate() {
this.workers.forEach(worker => worker.terminate())
this.workers = []
this.availableWorkers = []
this.taskQueue = []
this.pendingTasks.clear()
}
}
使用worker池
bash
// 使用
const pool = new WorkerPool('worker.js', 4)
// 提交 10 个任务
const tasks = []
for (let i = 0; i < 10; i++) {
tasks.push(pool.execute(data[i], 'sort'))
}
// 最多同时运行 4 个任务
//任务5 会等待任意 Worker 空闲后立即执行
//不会阻塞,充分利用所有 Worker
动态负载均衡
bash
// 动态调整数据块大小,平衡各 Worker 负载
function getOptimalChunkSize(total, workerCount, maxChunkSize = 100000) {
const idealChunk = Math.ceil(total / workerCount)
return Math.min(idealChunk, maxChunkSize)
}
// 使用
const chunkSize = getOptimalChunkSize(data.length, 4)
const chunks = splitData(data, chunkSize)
进度监控
bash
class ProgressParallelProcessor extends ParallelProcessor {
async process(data, taskType, onProgress) {
// ... 同上 ...
// 在 Worker 中添加进度报告
worker.addEventListener('message', (e) => {
if (e.data.type === 'progress') {
const { taskId, progress } = e.data
const task = this.pendingTasks.get(taskId)
if (task && onProgress) {
onProgress(taskId, progress)
}
}
})
}
}
web worker中 worker.addEventListener('message', (e) => {})可以出现在主线程中,也可以出现在 Worker 线程内部,当接收到对方通过 postMessage() 发送的数据时触发。worker.onmessage 只能绑定一个处理函数。后续赋值会覆盖之前的监听器;worker.addEventListener('message', ...)可以绑定多个不同的处理函数,按注册顺序依次执行
先注册 message 监听器,再调用 postMessage。如果 Worker 启动很快而主线程监听注册较慢,早期消息可能会丢失;
//正确顺序
worker.addEventListener('message', handler);
worker.postMessage(data);
Web Worker 的数据复制机制内存优化
Web Worker默认数据传输方式是深拷贝数据会使内存使用量翻倍
bash
worker.postMessage(largeData)//深拷贝数据传入worker中内存使用量翻倍
可转移对象(Transferable Objects)
bash
// 使用转移:数据所有权转移(零拷贝)
const largeData = new ArrayBuffer(1024 * 1024 * 10) // 10MB
worker.postMessage(largeData, [largeData]) // 转移所有权
共享内存(SharedArrayBuffer)
bash
//共享内存:多个线程共享同一块内存
const sharedBuffer = new SharedArrayBuffer(1024 * 1024 * 10) // 10MB
const sharedArray = new Int32Array(sharedBuffer)
// 主线程写入
sharedArray[0] = 42
// Worker 读取同一块内存,传递的是对同一块内存的引用,
//这意味着主线程和Worker操作的都是同一块物理内存。数据不需要被复制,也不//需要经过序列化和反序列化,从而避免了这些开销。
worker.postMessage(sharedBuffer) // 不需要转移,直接共享
内存限制策略
分块处理 + 流式传递
bash
// 主线程:流式分块处理
class StreamingProcessor {
constructor(workerScript, maxChunkSize = 100000) {
this.workerScript = workerScript
this.maxChunkSize = maxChunkSize
this.workers = []
this.currentChunkIndex = 0
}
async processLargeData(data, taskType) {
const totalChunks = Math.ceil(data.length / this.maxChunkSize)
const results = []
// 限制同时运行的 Worker 数量
const maxConcurrent = Math.min(4, navigator.hardwareConcurrency || 4)
const workers = []
for (let i = 0; i < Math.min(totalChunks, maxConcurrent); i++) {
workers.push(this.createWorker())
}
// 流式处理:边处理边释放内存
let completed = 0
const promises = []
for (let i = 0; i < totalChunks; i++) {
const start = i * this.maxChunkSize
const end = Math.min(start + this.maxChunkSize, data.length)
const chunk = data.slice(start, end) // 复制数据块(小尺寸)
// 处理完的块可以立即释放
const worker = workers[i % workers.length]
const promise = this.processChunk(worker, chunk, taskType, i)
.then(result => {
results[i] = result
completed++
// 释放 chunk 引用
chunk.length = 0
return result
})
promises.push(promise)
}
await Promise.all(promises)
// 清理 Worker
workers.forEach(w => w.terminate())
return results.flat()
}
processChunk(worker, chunk, taskType, index) {
return new Promise((resolve, reject) => {
const handler = (e) => {
if (e.data.index === index) {
worker.removeEventListener('message', handler)
resolve(e.data.result)
}
}
worker.addEventListener('message', handler)
// 使用转移对象减少内存
const transferable = this.getTransferable(chunk)
worker.postMessage({
index,
data: chunk,
taskType
}, transferable)
})
}
getTransferable(data) {
// 如果数据是 ArrayBuffer,可以转移
if (data.buffer instanceof ArrayBuffer) {
return [data.buffer]
}
return []
}
createWorker() {
return new Worker(this.workerScript)
}
}
核心代码:
bash
const promise = this.processChunk(worker, chunk, taskType, i)
.then(result => {
results[i] = result
completed++
// 释放 chunk 引用
chunk.length = 0
return result
})
promises.push(promise)
//等待全部worker执行完毕才能清理worker
await Promise.all(promises)
// 清理 Worker
workers.forEach(w => w.terminate())
使用 SharedArrayBuffer
bash
// 共享内存管理器
class SharedMemoryManager {
constructor(size) {
this.size = size
this.buffer = null
this.views = {}
}
allocate() {
try {
// 分配共享内存
this.buffer = new SharedArrayBuffer(this.size)
// 创建不同类型的视图
this.views.int8 = new Int8Array(this.buffer)
this.views.int32 = new Int32Array(this.buffer)
this.views.float64 = new Float64Array(this.buffer)
this.views.uint8 = new Uint8Array(this.buffer)
console.log(`分配共享内存: ${this.size / 1024 / 1024}MB`)
return this.buffer
} catch (error) {
console.error('SharedArrayBuffer 分配失败:', error)
// 降级方案:使用普通 ArrayBuffer
return this.allocateFallback()
}
}
allocateFallback() {
console.warn('使用降级方案:普通 ArrayBuffer')
this.buffer = new ArrayBuffer(this.size)
// 注意:普通 ArrayBuffer 无法在 Worker 间共享
return this.buffer//共享的
}
// 写入数据(主线程)
write(data, offset = 0) {
if (!this.buffer) return
// 1. 创建视图:基于底层 buffer创建一个无符号8位整数视图
const view = new Uint8Array(this.buffer)
// 2. 转换数据:将输入数据也转换为 Uint8Array 视图
// 注意:如果 data 已经是 TypedArray 或 ArrayBuffer,这通常很快;
// 如果 data普通数组,这会创建一个新的临时 TypedArray。
const dataView = new Uint8Array(data)
// 3. 内存复制:将 dataView 的内容复制到 view 中,从 offset位置开始
view.set(dataView, offset)
//开始执行写入完成通知
const signalView = new Int32Array(this.buffer);
// 原子地设置标志位为 1,表示数据已写入
Atomics.store(signalView, 0, 1);
// 通知等待在该位置的 Worker
Atomics.notify(signalView, 0);
}
// 读取数据(Worker 线程)
read(offset = 0, length) {
if (!this.buffer) return null
const view = new Uint8Array(this.buffer)
return view.slice(offset, offset + length)
}
// 释放内存
free() {
this.buffer = null
this.views = {}
// 建议执行垃圾回收
if (window.gc) window.gc()
}
// 内存使用情况
getUsage() {
if (!this.buffer) return 0
return this.buffer.byteLength
}
}
// 使用示例
const memoryManager = new SharedMemoryManager(1024 * 1024 * 50) // 50MB
const sharedBuffer = memoryManager.allocate()
// Worker 代码(worker.js)
self.onmessage = function(e) {
const sharedBuffer = e.data.sharedBuffer
const view = new Int32Array(sharedBuffer)
// 1. 等待主线程写入完成 (阻塞等待,直到 index 0 的值变为 1)
// 注意:Atomics.wait 只能在 Worker 中使用
Atomics.wait(signalView, 0, 0); //index 0 的值为0进入等待
// 直接在共享内存上操作,0的位置作为状态数据使用
for (let i = 1; i < view.length; i++) {
view[i] = view[i] * 2 // 修改共享数据
}
// 使用 Atomics 进行同步
Atomics.store(view, 0, 1)
Atomics.notify(view, 0, 1)
self.postMessage({ completed: true })
}
1. Atomics.store():原子写入
Atomics.store(typedArray, index, value)
参数: typedArray: 必须是 Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array 等整数类型视图
index: 数组索引
value: 要写入的值
2. Atomics.notify():唤醒等待线程
Atomics.notify(typedArray, index, count)
参数 : typedArray: 与 wait 使用的同一个整数类型视图
index: 要通知的位置索引
count: (可选) 要唤醒的线程数量。默认为 Infinity(唤醒所有等待该位置的线程),设为 1 则只唤醒一个。
3. Atomics.load()原子读取
Atomics.load(typedArray, index)
参数 : typedArray: typedArray: 必须是 Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array 等整数类型视图
index:index: 要读取的位置索引
4. Atomics.wait()等待
Atomics.wait(typedArray, index, value, timeout)
**参数:**typedArray: 必须是共享的 Int32Array。
index: 要等待的数组索引。
value: 预期值。只有当 typedArrayindex 等于此值时,线程才会进入等待状态。
timeout (可选): 等待的最大毫秒数。默认为 Infinity(无限等待)。
5. Atomics.add()原子加法
Atomics.add(typedArray,index, value)
参数:
typedArray:支持所有整型 TypedArray
index: 要在 typedArray 中修改的元素索引位置。
value:要加到当前值上的数值,类型是Number(整数)或 BigInt(如果数组是 BigInt64/BigUint64)。。
6. Atomics.compareExchange()比较并交换
Atomics.wait(typedArray, index, expectedValue, replacementValue)
参数:
typedArray: 共享整型数组。
index: 索引。
expectedValue: 预期当前应该存在的值。
replacementValue: 如果匹配,要写入的新值。
7. Atomics.sub()原子减法
Atomics.sub(typedArray, index, value)
参数:
typedArray:支持所有整型 TypedArray
index: 要在 typedArray 中修改的元素索引位置。
value:要被减当前值上要减的数值,类型是Number(整数)或 BigInt(如果数组是 BigInt64/BigUint64)。
Promise用法
Promise 的核心思想,是用一个对象来代表一个尚未完成、但"承诺"将来会完成的操作
基础用法
- 创建实例:用new Promise((resolve, reject) =>
{...}),执行器函数会立即同步执行,调用resolve()将状态改为成功,调用reject()将状态改为失败。 - 消费结果:通过.then()处理成功结果、.catch()捕获错误、.finally()执行无论状态如何都要运行的逻辑
执行器函数是立即同步执行的(在 new Promise 内部就调用),但内部的异步代码(如 setTimeout)会等到时机再触发
常用静态方法
Promise.all():
bash
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 1000));
const p3 = 3; // 普通值自动转为 Promise.resolve
Promise.all([p1, p2, p3]).then(results => {
console.log(results); // [1, 2, 3] (等待 p2 完成后输出)
});
接收一个可迭代对象(通常是数组),里面每个元素可以是 Promise 或普通值。
返回一个新 Promise: 成功:当所有输入Promise 都变为 fulfilled 时,返回一个数组(顺序与输入一致)。 失败:只要有一个输入 Promise 变为rejected,立即返回该失败原因(短路)。
Promise.allSettled():
bash
const promises = [
Promise.resolve(1),
Promise.reject('error'),
Promise.resolve(3)
];
Promise.allSettled(promises).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 3 }
// ]
});
无论每个 Promise 成功还是失败,都会等待所有 Promise 完成。 返回一个数组,每个元素是对象:{ status:
'fulfilled' | 'rejected', value | reason }。
不会短路,非常适合需要所有任务执行结果的场景(如批量上报)。
Promise.race():
bash
const p1 = new Promise(resolve => setTimeout(resolve, 500, '慢'));
const p2 = new Promise(resolve => setTimeout(resolve, 100, '快'));
Promise.race([p1, p2]).then(console.log); // '快' (100ms 后输出)
返回一个新 Promise,其状态和结果由第一个完成(无论成功或失败)的输入 Promise 决定。
Promise.any():
bash
const p1 = Promise.reject('错误1');
const p2 = new Promise(resolve => setTimeout(resolve, 200, '成功'));
const p3 = Promise.reject('错误2');
Promise.any([p1, p2, p3]).then(console.log); // '成功' (p2 完成)
返回一个新 Promise:
成功:当任意一个输入 Promise 变为 fulfilled 时,返回该成功值。
失败:如果所有输入 Promise 都变为 rejected,则返回一个 AggregateError(包含所有失败原因)。
微任务与执行时机
Promise 的 .then、.catch、.finally 回调会被放入微任务队列,在当前宏任务(如主线程代码、setTimeout 等)执行完毕后、下一个宏任务开始前执行。
bash
console.log('1');
Promise.resolve().then(() => console.log('2'));
setTimeout(() => console.log('3'), 0);
console.log('4');
// 输出顺序:1, 4, 2, 3
简版 Promise
bash
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.callbacks = []; // 存储 then 的回调
const resolve = (val) => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = val;
this.callbacks.forEach(cb => cb.onFulfilled(val));
};
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.value = reason;
this.callbacks.forEach(cb => cb.onRejected(reason));
};
//
try { executor(resolve, reject); } catch (e) { reject(e); }
}
then(onFulfilled, onRejected) {
// 透传处理
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; };
return new MyPromise((resolve, reject) => {
const handle = () => {
try {
const result = this.state === 'fulfilled'
? onFulfilled(this.value)
: onRejected(this.value);
resolve(result);
} catch (err) {
reject(err);
}
};
if (this.state === 'pending') {
this.callbacks.push({ onFulfilled: () => handle(), onRejected: () => handle() });
} else {
// 微任务模拟(简化用 setTimeout)
setTimeout(handle, 0);
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
bash
new MyPromise ((resolve, reject) => {
// 这里的 resolve 和 reject
// 就是引擎内部创建的那两个函数!
})
vue中Fontawesome的使用
官方中文文档:https://fontawesome.com.cn/docs/v5/desktop
注:Fontawesome图标的大小继承其父容器的字体大小(font-size),图标的颜色在font-awesome-icon 组件中或父容器中 设置样式属性 style="color: #ff6b6b"。
图标的大小可以跨级继承,图标的颜色只能在font-awesome-icon 组件中设置或继承上一级的标签(不能跨级)
-
下载页面选择web版下载 :https://fontawesome.com.cn/download
-
布置css和fonts:
- 将/css目录下的all.min.css复制到你的项目中对应的css目录。
- 将/webfonts目录复制到你项目中对应的静态文件目录。
- 在你的html中引入all.min.css文件
-
基础使用方法:
bash
<i class="fas fa-camera"></i>
<span class="fas fa-camera"></span>
<a href="" class="fas fa-camera"></a>
注:
- fontawesome主要是通过css引入字体文件实现显示图标。 其中在class中有2个主要内容。
标识图标样式的:用fas,far,fal,fad。
其中 fas: 实体样式 far: 常规样式 fal: 线条样式 fad: 双色样式
fab: 品牌图标
fa-xxxx其中xxxx就是图标的名称,比如:相机图标(fa-camera)
- webfonts文件夹必须和all.min.css所在文件夹同级,all.min.css 内部引用的是 ../webfonts/
- *,
*::before,
*::after { box-sizing: border-box; margin: 0; font-weight: normal; }
这样的样式设置会重置fontawesome样式导致fontawesome样式失效
官方组件vue-fontawesome
添加Vue组件:
官网文档:https://docs.fontawesome.com/web/use-with/vue
bash
npm install @fortawesome/vue-fontawesome@latest-3
添加SVG核心:
bash
npm install @fortawesome/fontawesome-svg-core
添加图标包:
bash
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-brands-svg-icons
注:此处用的免费版,free图标又划分为三个图标库,主要有实心图标(solid)、常规图标(regular)以及品牌图标(brand),fal 需要 Pro 授权,fad 则可以通过 Pro 或 Kit 获取
- 如果你只是用免费图标,可以直接跳到免费套餐说明。
- 如果你用的是Pro+图标,就需要用Kit包。
- 如果你用的是 Pro 图标,可以用 Kit 包或 SVG 包按样式分类。
Vue项目的main.ts中导入整个样式:
bash
//导入核心包
import { library } from '@fortawesome/fontawesome-svg-core'
//导入组件
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
//导入图标库
import { fas } from '@fortawesome/free-solid-svg-icons'
import { far } from '@fortawesome/free-regular-svg-icons'
import { fab } from '@fortawesome/free-brands-svg-icons'
//添加图标到library
library.add(fas,far,fab)
//全局注册组件
app.component('font-awesome-icon', FontAwesomeIcon)
vue文件中使用:
bash
<a><font-awesome-icon icon="fas fa-camera"/></a>
<a><font-awesome-icon icon="far fa-camera"/></a>
<a><font-awesome-icon icon="fab fa-weixin"/></a>
局部和按需使用(Vue3)
bash
<template>
<a><font-awesome-icon icon="fab fa-github"/></a>
<a><font-awesome-icon icon="fab fa-qq"/></a>
<a><font-awesome-icon icon="fab fa-weixin"/></a>
</template>
<script setup lang="ts">
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faWeixin, faQq, faGithub } from '@fortawesome/free-brands-svg-icons'
library.add(faWeixin, faQq, faGithub)
</script
Vue 3 项目中集成 Tailwind CSS
Tailwind官网:https://www.tailwindcss.cn/
- 安装:npm install -D tailwindcss @tailwindcss/vite
- 配置 Vite 插件:vite.config.js 文件中引入 @tailwindcss/vite 插件并将其添加到 plugins
数组中
bash
// vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite' // 引入插件
export default defineConfig({
plugins: [
vue(),
tailwindcss(), // 使用插件
],
// ... 其他配置
})
- 在 CSS 中导入 Tailwind:主 CSS 文件(通常是 src/assets/main.css 或
src/style.css),在文件顶部添加一行代码来导入 Tailwind 的基础样式
bash
@import "tailwindcss";
- 在主入口文件中导入 CSS:在 src/main.ts 中导入主 CSS 文件,使用 Tailwind 的样式对整个应用生效
bash
import './assets/main.css'
vite专用在main.ts中批量注册组件
bash
//导入组件
import App from ./App.vue
//创建实例
const app= createApp(App)
//批量导入指定文件夹下的vue文件,eager: true 参数(立即加载), ignore: ['**/exclude/**'] 忽略指定文件夹下文件,eager和ignore同级别
//{eager:true,ignore: ['**/exclude/**']}
const modules = import.meta.glob('./components/global/*.vue',{eager:true})
//批量注册
Object.entries.forEach(([path, module])=>{
const componentName = path.split('/').pop().replace(/\.\w+$/, '') // 提取文件名
app.component(componentName, module.default)
})
//vue实例挂载带指定div
app.mount("#app")
interface和class
- interface(接口):纯粹用于编译时的类型检查。它只告诉 TS "对象长什么样",编译成 JS时,它会被完全擦除,不占用任何运行内存。
- class(类):JavaScript 的运行时实体。它既是"类型"(告诉 TS 长什么样),又是"值"(能生成实例、包含方法逻辑)。
interface使用场景
- 定义数据结构(DTO/数据模型):比如后端 API 的返回值、请求参数。
- 定义函数形状:约束函数的入参和返回值。
- 跨文件/跨模块扩展:interface 支持声明合并(同名的 Interface 会自动合并),非常适合第三方库的类型补充。
- 轻量级描述:你只需要描述"有什么属性",不需要"怎么做"的时候。
bash
//定义后端返回的用户数据
interface IUser {
id: number;
name: string;
email?: string; // 可选属性
}
// 定义函数类型,接收两个 number,返回 number
interface ISearchFunc {
(source: number, sub: number): number;
}
// 使用该类型
const search: ISearchFunc = (x, y) =>{
return x+y
}
//使用完整函数写法
const search: ISearchFunc = function(x, y) {
return x+y
};```
在对象类型中定义方法
bash
// 1. 方法简写(推荐,更简洁)
interface Person {
name: string;
sayHello(msg: string): void; // 方法简写
}
// 2. 函数属性写法(等价,但较少用)
interface Person {
name: string;
sayHello: (msg: string) => void; // 函数属性
}
// 使用
const tom: Person = {
name: 'Tom',
sayHello(msg) {
console.log(`${this.name} says: ${msg}`);
}
};
tom.sayHello('Hello!');
class使用场景
- 创建对象实例:需要 new ClassName() 生成多个实体。
- 封装业务逻辑:内部包含方法的具体实现(method() { ... })。
- 维护内部状态:构造函数中初始化属性,方法内操作 this.xxx。
- 作为"类型 + 常量":由于 class 既是类型又是值,你可以把它当类型用,也可以把它当变量传参
bash
//封装一个具备完整行为的实体
class Animal {
constructor(public name: string, private age: number) {}
public sayHi(): string {
return `My name is ${this.name}, I'm ${this.age} years old.`;
}
}
const dog = new Animal('旺财', 3);
console.log(dog.sayHi()); // 运行时真正执行了逻辑
Class 也能当 Interface 用
在 TypeScript 中,class 实际上创建了两个东西:
一个类型(可以像 interface 一样用)。
一个构造函数(用于实例化)。
所以使用class定义变量类型不会报错:
bash
class User {
id: number;
name: string;
}
// 这里 User 被当作类型使用,和 interface 效果一样
const user: User = { id: 1, name: 'Tom' };
但反过来不行:interface 只创建了类型,无法被实例化(不能 new)
使用场景
| 场景 | 选择 | 原因 |
|---|---|---|
| 只定义数据结构(API 响应、状态对象) | interface | 轻量、可扩展、无运行时开销 |
| 定义工具函数类型 | interface 或 type | 仅需约束,不需要实例 |
| 需要包含业务方法(如 user.login()) | class | 方法必须附着在实例上 |
| 需要继承复用属性并添加新方法 | class extends | 需要复制父类的逻辑 |
| 需要多个同名类型自动合并(如扩展第三方库) | interface | 独有的声明合并特性 |