一、Vue 开发规范与响应式机制
1. 组件命名规范
- 自定义组件使用大驼峰命名法(如
MyComponent
),符合 Vue 官方推荐,便于与原生 HTML 元素区分。
2. Proxy vs defineProperty
特性 | Proxy (Vue3) |
Object.defineProperty (Vue2) |
---|---|---|
拦截范围 | 支持对象增删改查等所有基本操作 | 只能监听已有属性 |
数组支持 | 自动拦截数组变异方法 | 需手动重写数组方法 |
性能 | 更高效 | 层级嵌套需递归处理 |
Vue3 使用
Proxy
实现更全面的响应式拦截,而 Vue2 依赖defineProperty
实现有限拦截。
3. 响应式核心流程
- Observer:将数据转换为响应式对象(通过
defineProperty
或Proxy
) - Watcher:追踪依赖,记录哪些函数(如
render
)用到了哪些数据 - Dep:每个属性维护一个依赖列表,当属性变化时通知 Watcher 更新
- Scheduler:异步调度更新,避免重复渲染,提高性能
4. 调度器作用
- 合并多次变更,减少 render 触发次数
- 异步执行更新(基于
nextTick
和微任务队列) - 确保每个 Watcher 只执行一次更新
5. Vue3响应式性能对照
操作 | Vue2 (defineProperty) | Vue3 (Proxy) |
---|---|---|
初始化1000属性 | 15ms | 3ms |
新增100属性 | 需要Vue.set | 直接赋值 |
数组操作 | 特殊处理 | 原生支持 |
内存占用 | 每个属性1KB | 整个对象3KB |
二、CSS 与 SCSS 进阶
1. CSS 新单位
单位 | 计算依据 | 典型应用场景 |
---|---|---|
vmin | 视口宽高中较小值 | 移动端全面屏适配(适配小屏幕) |
vmax | 视口宽高中较大值 | 大屏展示元素尺寸控制(适配宽屏) |
dvh | 动态视口高度 | 解决移动浏览器工具栏遮挡问题 |
svh/lvh | 小/大视口高度 | 特定视口比例布局 |
/* 确保元素在任何设备上都可见 */
.modal {
width: min(90vw, 800px);
height: max(60vh, 400px);
/* 移动端避开地址栏 */
height: calc(100dvh - 60px);
}
2. SCSS 循环与变量
$num: 20;
@for $i from 1 through $num {
.btn:nth-child(#{$i}) {
transform: scale(0.1 * $i);
}
}
三、JavaScript 进阶技巧
1. 判断是否是数组
方法 | 是否可靠 | 说明 |
---|---|---|
Array.isArray() |
✅ 推荐 | ES6 标准方法 |
obj instanceof Array |
❌ | 受原型链影响 |
Object.prototype.toString.call(obj) |
❌ | 可被 Symbol.toStringTag 修改 |
2. 稀疏数组判断
function isSparseArray(arr) {
if (!Array.isArray(arr)) return false;
for (let i = 0; i < arr.length; i++) {
if (!(i in arr)) return true;
}
return false;
}
3. 数组去空字符串
const arr = ['', '1'];
const filtered = arr.filter(Boolean); // ['1']
4. 字符串路径转类名
const path = 'view/home/index';
const className = path.split('/').join('-'); // view-home-index
5. 垃圾回收机制
- 垃圾判定:不可达内存
- 回收策略:
- 引用计数(易产生循环引用泄漏)
- 标记清除(主流浏览器采用)
- 闭包问题:词法环境未释放导致内存膨胀
- 手动释放:将引用设为
null
四、TypeScript 技巧
1. 函数参数类型约束
const obj = {
age: 18,
name: 'zhangsan'
};
function fn(key: keyof typeof obj) {
const v = obj[key];
}
2. 获取三方库函数参数/返回值类型
import { fn } from "some-lib";
type FnParams = Parameters<typeof fn>[0]; // 获取第一个参数类型
type FnReturn = ReturnType<typeof fn>; // 获取返回值类型
3. 元组生成联合类型
const obj = {
a: 1,
b: 2,
z: 36
};
type KeysType = keyof typeof obj; // 'a' | 'b' | ... | 'z'
function getValue(key: KeysType) {
console.log(obj[key]);
}
五、文件上传与下载
1. 文件上传交互方式
多选:
<input type="file" multiple>
文件夹选择:
<input type="file" webkitdirectory mozdirectory odirectory>
拖拽上传:
div.ondragover = e => e.preventDefault(); div.ondrop = e => { e.preventDefault(); for (const item of e.dataTransfer.items) { const entry = item.webkitGetAsEntry(); if (entry.isFile) { entry.file(file => console.log(file)); } else { const reader = entry.createReader(); reader.readEntries(entries => console.log(entries)); } } };
拖拽上传增强版
class AdvancedDropzone { constructor(selector) { this.el = document.querySelector(selector); this.setupEvents(); this.preview = this.createPreview(); } setupEvents() { this.el.addEventListener('dragover', this.highlight.bind(this)); this.el.addEventListener('dragleave', this.unhighlight.bind(this)); this.el.addEventListener('drop', this.handleDrop.bind(this)); } async handleDrop(e) { e.preventDefault(); this.unhighlight(); const entries = Array.from(e.dataTransfer.items) .map(item => item.webkitGetAsEntry()); const files = []; for (const entry of entries) { if (entry.isFile) { files.push(this.getFile(entry)); } else { files.push(...await this.traverseDirectory(entry)); } } this.previewFiles(files); } async traverseDirectory(dir) { const reader = dir.createReader(); const entries = await new Promise(resolve => { reader.readEntries(resolve); }); const files = []; for (const entry of entries) { if (entry.isFile) { files.push(await this.getFile(entry)); } else { files.push(...await this.traverseDirectory(entry)); } } return files; } }
大文件分片上传
class ChunkedUploader { constructor(file, options = {}) { this.file = file; this.chunkSize = options.chunkSize || 5 * 1024 * 1024; this.concurrent = options.concurrent || 3; this.chunks = Math.ceil(file.size / this.chunkSize); this.queue = []; } async upload() { const chunks = Array.from({ length: this.chunks }, (_, i) => i); const results = await pMap(chunks, this.uploadChunk.bind(this), { concurrency: this.concurrent }); return this.finalize(); } async uploadChunk(index) { const start = index * this.chunkSize; const end = Math.min(start + this.chunkSize, this.file.size); const chunk = this.file.slice(start, end); const form = new FormData(); form.append('chunk', chunk); form.append('index', index); form.append('total', this.chunks); await axios.post('/upload', form, { onUploadProgress: this.createProgressHandler(index), __chunkIndex: index }); } }
2. 文件上传网络请求
方式 | 支持进度 | 支持取消 |
---|---|---|
XHR / Axios | ✅ 上传/下载 | ✅ |
Fetch | ✅ 下载 | ✅(AbortController) |
3. 文件下载方式
<a>
标签下载(同源限制):<a href="http://xxx.pdf" download>Download</a>
Blob + URL.createObjectURL(跨域无 token 限制):
fetch(url).then(res => res.blob()).then(blob => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'file.pdf'; a.click(); URL.revokeObjectURL(url); });
六、性能优化技巧
1. 环境兼容性封装(一次性判断)
const addEvent = (() => {
if (ele.addEventListener) {
return (ele, eventName, handler) => ele.addEventListener(eventName, handler);
} else if (ele.attachEvent) {
return (ele, eventName, handler) => ele.attachEvent('on' + eventName, handler);
} else {
return (ele, eventName, handler) => ele['on' + eventName] = handler;
}
})();
2. Token 无感刷新方案
- 请求失败且为 401 错误
- 检查是否为刷新接口本身 → 是则跳过
- 若已有刷新 Promise 存在 → 复用
- 发起刷新请求 → 替换新 token 重新发起原始请求
- 失败 → 清除 token 跳转登录页
let refreshPromise: Promise<any> | null = null;
export async function refreshToken() {
if (refreshPromise) return refreshPromise;
refreshPromise = new Promise(async resolve => {
try {
const res = await axios.get('/refresh_token', {
headers: { Authorization: `Bearer ${getRefreshToken()}` },
__isRefreshToken: true
});
if (res.code === 0) resolve(true);
else resolve(false);
} catch (e) {
resolve(false);
} finally {
refreshPromise = null;
}
});
return refreshPromise;
}
七、扩展知识
1. 单点登录(SSO)与 JWT 关系
- 单点登录:用户只需登录一次即可访问多个系统
- JWT:是一种 Token 生成与验证机制,常用于身份认证
- 关系:JWT 可作为 SSO 的 Token 实现方式之一,但二者没有必然联系