一、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 實現方式之一,但二者沒有必然聯系