1.談談垃圾回收機制方式及內存管理
垃圾回收機制(Garbage Collection, GC)是現代編程語言(如 Java、C#、Go 等)中自動管理內存的核心機制,其主要目標是自動釋放不再使用的內存資源,避免內存泄漏和手動內存管理帶來的錯誤。
下面從垃圾回收機制的方式和內存管理策略兩個方面詳細闡述。
一、垃圾回收機制的常見方式
1. 引用計數(Reference Counting)
- 原理:每個對象維護一個計數器,記錄有多少個引用指向它。當引用數為 0 時,立即回收。
- 優點:
- 回收及時,延遲低。
- 不需要暫停整個程序(可實時釋放)。 - 缺點:
- 無法處理循環引用(A 引用 B,B 引用 A,計數器永遠不為 0)。
- 每次引用變化都要更新計數,開銷大。 - 典型應用:Python(早期)、Objective-C(手動+自動混合)、PHP(部分)。
⚠️ 由於循環引用問題,現代語言基本不單獨使用此方式。
2. 可達性分析(Tracing GC)
- 原理:從“根對象”(如棧中的局部變量、靜態變量、寄存器等)出發,通過引用鏈遍歷所有可達對象。未被訪問到的對象即為“垃圾”,可回收。
- 優點:
- 可以解決循環引用問題。
- 回收效率高,適合大規模堆內存。 - 常見算法:
- 標記-清除(Mark-Sweep)
- 標記所有可達對象 → 清除未標記對象。
- 缺點:會產生內存碎片。
- 標記-整理(Mark-Compact)
- 標記後,將存活對象向一端移動,壓縮內存,消除碎片。
- 適合長期存活對象多的場景。
- 複製算法(Copying)
- 將內存分為兩塊,只使用一塊。當一塊滿時,將存活對象複製到另一塊,然後清空原塊。
- 優點:無碎片;缺點:只能用一半內存。
- 適用於新生代(短期存活對象多)。
✅ Java 中的 HotSpot 虛擬機採用的是 分代收集(Generational GC),結合了上述算法:
| 代 | 適用算法 | 説明 |
|---|---|---|
| 新生代(Young Generation) | 複製算法 | 分 Eden + Survivor 區,對象先放入 Eden,滿後複製到 Survivor,多次存活後晉升到老年代。 |
| 老年代(Old Generation) | 標記-清除/標記-整理 | 存放長期存活對象,回收頻率低,但單次耗時長。 |
3. 增量式 GC(Incremental GC)
- 原理:將垃圾回收過程拆分成多個小階段,穿插在程序運行中執行,減少單次停頓時間。
- 適用場景:對響應時間敏感的應用(如 GUI、Web 服務)。
- 代表:CMS(Concurrent Mark Sweep)垃圾收集器。
⚠️ 注意:CMS 已在 JDK 9+ 被廢棄,被 G1(Garbage First)取代。
4. 併發與並行 GC
- 並行 GC:多個線程同時執行 GC,加快回收速度(如 Parallel Scavenge)。
- 併發 GC:GC 線程與應用線程併發執行,減少停頓時間(如 CMS、G1、ZGC、Shenandoah)。
| 收集器 | 類型 | 特點 |
|---|---|---|
| Serial GC | 單線程 | 簡單,適合單核或小型應用 |
| Parallel GC | 並行 | 吞吐量優先,適合後台計算 |
| CMS | 併發 | 低停頓,但有“浮動垃圾”問題 |
| G1 | 併發 + 分區 | 按區域回收,可預測停頓時間 |
| ZGC / Shenandoah | 併發 + 低延遲 | 停頓時間 < 10ms,適合大堆 |
二、內存管理策略
1. 分代假設(Generational Hypothesis)
- 核心思想:
1. 大多數對象生命週期很短(如臨時變量)。
2. 少數對象會長期存活(如緩存、單例)。 - 結果:將堆分為新生代(Young)和老年代(Old),對不同代採用不同 GC 策略。
2. 內存區域劃分(以 JVM 為例)
| 區域 | 作用 |
|---|---|
| 方法區(Metaspace) | 存放類元數據、常量池、靜態變量等 |
| 堆(Heap) | 所有對象實例分配的地方,分為 Eden、Survivor、Old |
| 棧(Stack) | 存放局部變量、方法調用幀(線程私有) |
| 本地方法棧(Native Method Stack) | 本地方法調用棧 |
| 程序計數器 | 記錄當前執行指令地址 |
✅ 程序員無需手動管理棧和方法區,主要關注堆內存。
3. 內存分配策略
- TLAB(Thread Local Allocation Buffer):每個線程在 Eden 區分配一塊私有緩衝區,減少鎖競爭。
- 對象晉升(Promotion):對象在 Survivor 區經歷多次 GC 後仍存活 → 晉升到老年代。
- 大對象直接進入老年代:避免在年輕代頻繁複制。
- 長期存活對象進入老年代:通過“年齡閾值”控制(默認 15 次 GC 後晉升)。
4. GC 觸發條件
- Eden 區滿(新生代 GC)
- 老年代空間不足
- 系統調用
System.gc()(建議不依賴) - 晉升失敗(晉升對象太大)
- Full GC(老年代 + 方法區)
三、最佳實踐建議
- 避免大對象頻繁創建:減少 Full GC。
- 合理設置堆大小:
-Xms和-Xmx避免動態擴展。 - 選擇合適的 GC 收集器:
- 高吞吐:Parallel GC
- 低延遲:G1或ZGC
- 超大堆(>100GB):ZGC或Shenandoah - 監控 GC 日誌:
`bash
-XX:+PrintGCDetails -Xloggc:/path/to/gc.log
`
使用jstat、VisualVM、GCViewer等工具分析 GC 性能。
總結
| 機制 | 説明 |
|---|---|
| 垃圾回收方式 | 引用計數(已淘汰)、可達性分析(主流)、分代收集(JVM 核心) |
| 回收算法 | 標記-清除、標記-整理、複製、分代 |
| GC 類型 | 並行、併發、增量、低延遲(ZGC) |
| 內存管理 | 分代假設、TLAB、對象晉升、大對象優化 |
| 關鍵目標 | 減少停頓時間、提升吞吐量、避免內存泄漏 |
📌 一句話總結:
垃圾回收機制通過“可達性分析”識別無用對象,結合“分代+算法組合”實現高效內存管理,核心目標是在性能與資源之間取得平衡。
2.簡述登錄原理
前端登錄的原理和實現是現代 Web 應用開發中的核心環節之一。下面從原理和實現方式兩個方面進行簡要闡述。
一、前端登錄的基本原理
1. 用户身份認證流程
前端登錄的本質是:用户提交賬號密碼 → 後端驗證身份 → 後端返回認證憑證(Token)→ 前端保存並後續攜帶該憑證訪問受保護資源。
2. 典型流程
1. 用户在登錄頁輸入用户名和密碼
2. 前端將數據通過 HTTPS 發送到後端登錄接口(如 /api/login)
3. 後端校驗用户名密碼是否正確(通常連接數據庫或認證服務)
4. 若校驗成功,後端生成一個安全的認證令牌(Token,如 JWT)
5. 後端將 Token 返回給前端
6. 前端將 Token 存儲在本地(如 localStorage、sessionStorage 或 Cookie)
7. 前端在後續請求中,將 Token 放入請求頭(如 `Authorization: Bearer <token>`)
8. 後端收到請求後,驗證 Token 的有效性,決定是否允許訪問
9. 若 Token 失效或無效,返回 401 未授權,前端跳轉至登錄頁
二、前端登錄的實現方式(以 Vue + Axios 為例)
✅ 1. 登錄頁面(Login.vue)
<template>
<div class="login">
<form @submit.prevent="handleLogin">
<input v-model="form.username" placeholder="用户名" required />
<input v-model="form.password" type="password" placeholder="密碼" required />
<button type="submit">登錄</button>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import axios from 'axios'
const form = ref({ username: '', password: '' })
const handleLogin = async () => {
try {
const res = await axios.post('/api/login', form.value)
const token = res.data.token // 假設後端返回 token
// 存儲 token(推薦使用 localStorage)
localStorage.setItem('authToken', token)
// 跳轉到首頁或主頁
window.location.href = '/'
} catch (err) {
alert('登錄失敗:' + (err.response?.data?.message || err.message))
}
}
</script>
[path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/views/Login.vue"]
✅ 2. 全局請求攔截器(axios.interceptors.js)
確保每次請求都帶上 Token。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/utils/axiosInterceptors.js"]
import axios from 'axios'
// 請求攔截器:添加 token 到 header
axios.interceptors.request.use(config => {
const token = localStorage.getItem('authToken')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, error => {
return Promise.reject(error)
})
// 響應攔截器:處理 401 未授權
axios.interceptors.response.use(
response => response,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('authToken')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
export default axios
✅ 3. 登錄狀態管理(可選,使用 Pinia 或 Vuex)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/stores/authStore.js"]
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
token: localStorage.getItem('authToken') || '',
isAuthenticated: !!localStorage.getItem('authToken')
}),
actions: {
setToken(token) {
this.token = token
this.isAuthenticated = true
localStorage.setItem('authToken', token)
},
clearToken() {
this.token = ''
this.isAuthenticated = false
localStorage.removeItem('authToken')
}
}
})
三、安全建議
| 項目 | 建議 |
|---|---|
| Token 存儲 | 優先使用 HttpOnly Cookie(防止 XSS 攻擊) |
| 使用 HTTPS | 必須,防止中間人攻擊 |
| Token 過期 | 設置合理過期時間(如 JWT 15 分鐘~2 小時) |
| Token 刷新 | 使用 Refresh Token 機制實現無感續期 |
| 密碼傳輸 | 前端加密(如前端加鹽哈希)或僅傳輸明文但走 HTTPS |
四、補充:後端配合(簡要説明)
後端需提供 /api/login 接口,返回 JWT 示例:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xxx.xxx",
"expiresIn": 1800
}
JWT 解碼後可包含用户 ID、角色等信息。
總結
| 步驟 | 説明 |
|---|---|
| 1. 前端提交登錄 | 用户輸入 → 發送 POST 請求 |
| 2. 後端驗證 | 校驗賬號密碼 → 生成 Token |
| 3. 前端存儲 Token | localStorage / Cookie |
| 4. 請求攜帶 Token | 攔截器自動添加 |
| 5. 後端校驗 Token | 拒絕非法請求 |
| 6. 登出處理 | 清除 Token,跳轉登錄頁 |
✅ 最佳實踐推薦:
- 使用 JWT + HttpOnly Cookie 存儲 Token(更安全)
- 前端使用 Axios 攔截器 統一處理 Token
- 配合 Pinia/Vuex 管理登錄狀態
- 後端實現 Token 刷新機制
3.一個小項目讓你自己負責搭建底層一些架構
在接手一個小項目時,我會從 技術選型、架構設計、開發效率、可維護性、團隊協作 等多個維度綜合考慮,確保項目“輕量高效、快速落地、可持續演進”。
🎯 一、項目定位明確(前提)
首先,我們需要明確“小項目”的定義,比如:
- 是一個內部工具(如:數據看板、審批流程、配置管理)
- 還是面向用户的輕量級 H5 應用(如:活動頁、問卷調查)
- 是否需要跨端(H5 + 小程序 + App)
- 是否有複雜交互或實時性要求(如:音視頻、WebSocket)
👉 假設這是一個內部使用的輕量級管理後台系統(如:用户管理、權限配置、日誌查看),我們以此為例展開。
🛠️ 二、技術選型建議(前端)
| 維度 | 推薦方案 | 理由 |
|---|---|---|
| 框架 | Vue 3 + Vite | 輕量、性能好、熱更新快、生態成熟,適合中小型項目 |
| UI 組件庫 | Element Plus 或 Naive UI | Element Plus 更適合傳統後台系統,Naive UI 更現代,TypeScript 支持好 |
| 狀態管理 | Pinia | 比 Vuex 更簡潔,天然支持 TypeScript,適合小項目 |
| 路由 | Vue Router 4 | 與 Vue 3 完美集成,支持懶加載 |
| 構建工具 | Vite | 啓動快,HMR 極快,開箱即用 |
| TypeScript | ✅ 強烈建議開啓 | 提升代碼質量,減少運行時錯誤,利於長期維護 |
| API 請求 | Axios + 封裝請求層 | 簡單可靠,可統一處理攔截器、錯誤碼等 |
| 樣式方案 | SCSS + CSS Modules / 命名規範 | 支持變量、嵌套,避免全局污染 |
| 代碼規範 | ESLint + Prettier + Husky + lint-staged | 自動格式化與檢查,保證團隊代碼風格一致 |
| 文檔與協作 | Git + Markdown + 阿里雲文檔 / Confluence | 保持文檔同步,便於交接 |
✅ 如果是極簡項目(如:靜態頁面+簡單表單),可考慮 Vue 3 + Vite + 單文件組件,無需複雜架構。
🏗️ 三、項目架構設計(推薦結構)
src/
├── assets/ # 靜態資源(圖片、圖標等)
├── components/ # 公共組件(如:FormModal, TableWithPagination)
├── views/ # 頁面視圖(按功能劃分,如:UserManageView.vue)
├── router/ # 路由配置
│ └── index.ts
├── store/ # Pinia 狀態管理
│ └── index.ts
├── api/ # 所有 API 請求封裝
│ └── request.ts
│ └── user.ts
├── utils/ # 工具函數(如:formatDate, debounce)
├── types/ # 全局類型定義(接口定義、泛型等)
├── plugins/ # 插件(如:Element Plus 全局註冊)
├── App.vue
└── main.ts
✅ 架構亮點:
- 模塊化清晰:職責分離,便於維護和複用。
- 統一 API 管理:避免請求分散,方便統一處理 Token、錯誤碼。
- TypeScript 支持:接口定義明確,IDE 提示強大。
- 可擴展性強:未來可輕鬆接入權限系統、多語言、主題切換等。
📦 四、開發流程建議
- 初始化項目
`bash
npm create vue@latest my-project
cd my-project
npm install
` - 安裝依賴
`bash
npm install element-plus pinia axios
npm install -D sass sass-loader
npm install -D eslint prettier husky lint-staged
` - 配置 Vite + TypeScript + ESLint + Prettier
-vite.config.ts
-.eslintrc.cjs
-.prettierrc
-package.json中添加lint-staged鈎子 - 快速搭建基礎頁面
- 創建views/DashboardView.vue
- 配置路由
- 使用 Element Plus 組件快速搭建表單/表格 - API 封裝示例
`ts
// src/api/request.ts
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
})
service.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = Bearer ${token}
return config
})
service.interceptors.response.use(
(res) => res.data,
(err) => {
console.error('API Error:', err)
return Promise.reject(err)
}
)
export default service
`
- 狀態管理示例(Pinia)
`ts
// store/userStore.ts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: '',
}),
actions: {
setToken(token: string) {
this.token = token
localStorage.setItem('token', token)
},
logout() {
this.$reset()
localStorage.removeItem('token')
},
},
})
`
🔄 五、後續演進建議
| 階段 | 建議 |
|---|---|
| 項目中期 | 加入權限控制(路由守衞 + 按鈕級權限) |
| 項目後期 | 引入 Mock 數據(vite-plugin-mock)、多語言支持、主題切換 |
| 團隊協作 | 使用 Git Flow + Pull Request 流程,配合 Code Review |
✅ 總結:我的核心思路
“小項目 ≠ 簡單開發”,而是“用最小成本,構建最可持續的架構”。
| 原則 | 説明 |
|---|---|
| ✅ 輕量但不隨意 | 不堆框架,但關鍵點(TypeScript、規範、可維護性)不能省 |
| ✅ 快速驗證 MVP | 1 天內搭建出可運行的原型 |
| ✅ 可擴展性優先 | 代碼結構清晰,方便後期加功能 |
| ✅ 團隊友好 | 有規範、有文檔、有協作流程 |
📌 附加:生成一個簡單項目骨架(可直接使用)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/views/DashboardView.vue"]
<template>
<div class="dashboard">
<h1>歡迎使用後台管理系統</h1>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>今日數據概覽</span>
</div>
</template>
<div class="text item">
<el-statistic :value="1234" title="用户總數" />
</div>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ElCard, ElStatistic } from 'element-plus'
</script>
<style scoped>
.dashboard {
padding: 20px;
}
.box-card {
width: 100%;
}
</style>
4.説一下了解的js 設計模式,解釋一下單例、工廠、觀察者
JavaScript 中常見的三種設計模式:單例模式、工廠模式 和 觀察者模式。這些模式在前端開發、框架設計和複雜業務系統中非常常見。
✅ 一、單例模式(Singleton Pattern)
📌 什麼是單例?
確保一個類只有一個實例,並提供一個全局訪問點。
🎯 使用場景
- 全局配置對象(如
config.js) - 消息中心、日誌記錄器、數據庫連接池
- 需要唯一控制資源的模塊
💡 JavaScript 實現示例
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/utils/Singleton.js"]
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this.data = {};
Singleton.instance = this;
}
setData(key, value) {
this.data[key] = value;
}
getData(key) {
return this.data[key];
}
}
// 使用:無論創建多少次,都返回同一個實例
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
⚠️ 注意事項
- JavaScript 中沒有類的私有屬性,可通過閉包或 Symbol 實現更嚴格的單例。
- 適合懶加載、全局狀態管理。
✅ 二、工廠模式(Factory Pattern)
📌 什麼是工廠?
定義一個創建對象的接口,但讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到子類。
🎯 使用場景
- 創建複雜對象或有多種類型的產品(如不同類型的圖形、不同平台的 API 客户端)
- 避免
if-else或switch判斷具體類型
💡 JavaScript 實現示例
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/factory/ShapeFactory.js"]
class Shape {
draw() {
throw new Error("子類必須實現 draw 方法");
}
}
class Circle extends Shape {
draw() {
console.log("繪製圓形");
}
}
class Rectangle extends Shape {
draw() {
console.log("繪製矩形");
}
}
class ShapeFactory {
createShape(type) {
switch (type) {
case 'circle':
return new Circle();
case 'rectangle':
return new Rectangle();
default:
throw new Error(`未知形狀: ${type}`);
}
}
}
// 使用
const factory = new ShapeFactory();
const shape = factory.createShape('circle');
shape.draw(); // 輸出:繪製圓形
🌟 進階:抽象工廠(Abstract Factory)
當需要創建一系列相關或依賴對象時使用,比如不同主題的 UI 組件。
✅ 三、觀察者模式(Observer Pattern)
📌 什麼是觀察者?
定義對象間的一對多依賴關係,當一個對象改變狀態時,所有依賴它的對象都會自動收到通知並更新。
🎯 使用場景
- 事件系統(如 DOM 事件)
- 狀態管理(如 Redux、Vue 的響應式系統)
- 發佈/訂閲消息機制
💡 JavaScript 實現示例
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/observer/EventEmitter.js"]
class EventEmitter {
constructor() {
this.events = {};
}
// 訂閲事件
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
// 觸發事件
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
// 取消訂閲
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 使用示例
const emitter = new EventEmitter();
emitter.on('userLogin', (user) => {
console.log(`用户 ${user.name} 登錄了!`);
});
emitter.on('userLogin', (user) => {
console.log(`發送歡迎郵件給 ${user.email}`);
});
emitter.emit('userLogin', { name: 'Alice', email: 'alice@example.com' });
// 輸出:
// 用户 Alice 登錄了!
// 發送歡迎郵件給 alice@example.com
🔁 類比現代框架
- Vue 的
watch/computed - React 的
useEffect(本質也是觀察者思想) - RxJS 的 Observable
📊 總結對比
| 模式 | 核心思想 | 適用場景 | 優點 |
|---|---|---|---|
| 單例 | 保證唯一實例 | 配置、日誌、連接池 | 全局唯一、節省資源 |
| 工廠 | 封裝創建邏輯 | 多類型對象創建 | 解耦、易擴展 |
| 觀察者 | 一對多通知機制 | 事件系統、狀態同步 | 鬆耦合、靈活擴展 |
✅ 推薦學習路徑
- 先掌握 觀察者模式:理解現代前端“響應式”思想的核心。
- 再學 工廠模式:用於構建複雜組件或插件系統。
- 最後用 單例:僅在必要時使用,避免濫用全局狀態。
5.説一下git和svn的區別
Git 和 SVN 是兩種主流的版本控制系統,它們在設計理念、工作方式、性能和協作模式上有顯著區別。以下是它們的主要區別對比:
1. 架構模式
| 項目 | Git | SVN |
|---|---|---|
| 類型 | 分佈式版本控制系統(DVCS) | 集中式版本控制系統(CVCS) |
| 倉庫結構 | 每個開發者本地都有完整的代碼倉庫(包括歷史記錄) | 所有代碼和歷史集中存儲在中央服務器上 |
✅ Git 優勢:本地操作無需聯網,支持離線提交、分支、查看歷史等;
✅ SVN 優勢:結構簡單,適合對權限和集中管理要求高的團隊。
2. 數據存儲與性能
| 項目 | Git | SVN |
|---|---|---|
| 存儲方式 | 以快照方式存儲(文件內容的哈希值) | 以差異方式存儲(每次提交只記錄變更) |
| 性能表現 | 本地操作極快,分支/合併高效 | 遠程操作較慢,依賴網絡連接 |
💡 舉例:在 Git 中創建和切換分支幾乎是瞬時的;而 SVN 的分支操作需要在服務器上創建新目錄,較慢。
3. 分支與合併
| 項目 | Git | SVN |
|---|---|---|
| 分支創建 | 極其輕量,幾乎無成本 | 相對重,需在服務器上覆制整個目錄 |
| 合併支持 | 強大,支持多種合併策略(如 rebase、merge) | 支持合併,但不如 Git 靈活,易產生衝突 |
✅ Git 推薦使用分支進行特性開發、修復 bug,是現代 DevOps 的核心實踐。
4. 網絡依賴
| 項目 | Git | SVN |
|---|---|---|
| 本地操作 | 完全離線可用(提交、查看歷史、切換分支等) | 必須連接服務器才能提交或獲取最新代碼 |
| 協作方式 | 可以先在本地提交,再統一推送 | 必須實時連接服務器,適合“在線協作” |
⚠️ 如果網絡不穩定,SVN 使用受限;而 Git 在斷網環境下依然可用。
5. 權限與安全性
| 項目 | Git | SVN |
|---|---|---|
| 權限控制 | 依賴外部工具(如 GitLab、GitHub、Gitee)或 SSH + 倉庫權限 | 原生支持細粒度權限控制(路徑級權限) |
| 安全性 | 依賴託管平台或私有服務器配置 | 服務器端可精細控制用户訪問權限 |
🏢 企業內部系統若需嚴格權限管理,SVN 有優勢;但現代 Git 平台也已提供完善的權限體系。
6. 使用場景建議
| 場景 | 推薦工具 |
|---|---|
| 小團隊敏捷開發、持續集成/部署(CI/CD) | ✅ Git(如 GitHub、GitLab、Gitee) |
| 大型企業、需嚴格權限控制、文件版本歷史審計 | ✅ SVN(尤其對文檔類項目) |
| 開源項目、跨地域協作、頻繁分支 | ✅ Git |
| 僅需簡單版本管理,不想學習複雜概念 | ✅ SVN |
總結對比表
| 特性 | Git | SVN |
|---|---|---|
| 架構 | 分佈式 | 集中式 |
| 分支效率 | 極高(本地操作) | 較低(需服務器操作) |
| 離線能力 | 強 | 弱 |
| 存儲方式 | 快照(哈希) | 差異(增量) |
| 合併能力 | 強大、靈活 | 一般 |
| 權限管理 | 依賴平台 | 原生支持 |
| 學習曲線 | 中等偏高 | 較低 |
結論:
🟢 現代軟件開發推薦使用 Git,因其靈活性、高性能和對分佈式協作的完美支持。
🟡 SVN 仍有適用場景,如傳統企業文檔管理、對權限有嚴格要求的項目。
如果你正在選擇版本控制工具,除非有特殊需求(如嚴格權限控制或歷史審計),否則應優先選擇 Git。
6.何為同源策略及如何處理跨域問題
什麼是同源策略(Same-Origin Policy)?
同源策略是瀏覽器的一項核心安全機制,用於限制一個源(origin)的文檔或腳本如何與另一個源的資源進行交互。它旨在防止惡意網站讀取其他網站的敏感數據。
什麼是“同源”?
兩個URL被認為是“同源”的,當且僅當它們滿足以下三個條件:
- 協議相同(如都是
http或https) - 域名相同(如
example.com) - 端口相同(如
8080)
✅ 示例:https://api.example.com:8080/data與https://api.example.com:8080/user是同源的。
❌ 示例:http://api.example.com:8080/data與https://api.example.com:8080/data不同源(協議不同)。
同源策略的限制
在同源策略下,瀏覽器會阻止以下行為:
- 使用
XMLHttpRequest或fetch請求跨域資源; - 讀取跨域
iframe中的內容; - 訪問跨域的
localStorage、cookies等。
如何處理跨域問題?
跨域問題常見於前後端分離架構中(如前端在 http://localhost:3000,後端在 http://localhost:8080)。以下是常用的解決方案:
✅ 常見跨域解決方案
1. CORS(Cross-Origin Resource Sharing,跨域資源共享)
這是目前最推薦、最標準的跨域解決方案。
後端配置(以 Spring Boot 為例):
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/java/com/paas/plus/web/config/CorsConfig.java"]
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允許攜帶 cookie
config.addAllowedOrigin("http://localhost:3000"); // 允許的前端地址
config.addAllowedHeader("*"); // 允許所有頭
config.addAllowedMethod("*"); // 允許所有方法(GET, POST, etc.)
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
✅ 優點:標準、安全、支持複雜請求(如帶認證的請求)
⚠️ 注意:CORS 是服務端控制,前端無需額外配置
2. 代理(Proxy)——開發環境常用
在開發階段,前端可通過構建工具(如 Webpack、Vite)設置代理,將請求轉發到後端,從而繞過同源策略。
示例:Vite 配置(vite.config.js)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/vite.config.js"]
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
}
✅ 使用場景:開發環境(生產環境通常用 CORS)
✅ 前端請求/api/users→ 實際請求http://localhost:8080/api/users
3. JSONP(僅支持 GET)
一種早期的跨域方案,通過動態創建 <script> 標籤加載數據。
前端代碼:
<script>
function handleResponse(data) {
console.log(data);
}
const script = document.createElement('script');
script.src = 'http://api.example.com/data?callback=handleResponse';
document.head.appendChild(script);
</script>
❌ 缺點:僅支持 GET,不安全(易受 XSS 攻擊),不推薦使用
4. Nginx 反向代理(生產環境推薦)
將前端和後端部署在同一個域名下,通過 Nginx 做反向代理。
Nginx 配置示例:
server {
listen 80;
server_name localhost;
location / {
root /path/to/frontend/dist;
index index.html;
}
location /api/ {
proxy_pass http://localhost:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
✅ 優點:生產環境穩定,避免跨域,性能好
✅ 前端請求/api/users→ 實際由 Nginx 轉發到後端
5. PostMessage(用於 iframe 通信)
當兩個頁面通過 <iframe> 嵌套且不同源時,可使用 window.postMessage 安全通信。
示例:
// 向 iframe 發送消息
iframe.contentWindow.postMessage('Hello', 'https://trusted-site.com');
// 接收消息
window.addEventListener('message', (event) => {
if (event.origin !== 'https://trusted-site.com') return;
console.log('Received:', event.data);
});
✅ 適用場景:跨域 iframe 通信,如支付頁面嵌入
✅ 總結:如何選擇?
| 方案 | 適用場景 | 推薦度 |
|---|---|---|
| CORS | 後端可控,生產環境 | ⭐⭐⭐⭐⭐ |
| 代理(開發) | 前端開發調試 | ⭐⭐⭐⭐⭐ |
| Nginx 反向代理 | 生產環境統一部署 | ⭐⭐⭐⭐⭐ |
| JSONP | 老項目、僅 GET | ⭐⭐ |
| postMessage | iframe 通信 | ⭐⭐⭐ |
✅ 最佳實踐建議:
- 開發階段:使用 代理(Vite/Webpack)
- 生產階段:使用 CORS + Nginx 反向代理 或 統一域名部署
- 避免使用 JSONP,除非無法控制後端
7.CSS 怎麼實現水平和垂直居中?
在 CSS 中,實現元素的水平和垂直居中有多種方法,適用於不同的場景。以下是幾種常用且有效的實現方式:
✅ 方法一:使用 flexbox(推薦,現代佈局首選)
.container {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100vh; /* 容器高度佔滿視口 */
}
<div class="container">
<div class="centered">我居中了!</div>
</div>
✅ 優點:簡潔、靈活、兼容性好(現代瀏覽器支持)
📌 適用:父容器已知尺寸或需要響應式居中
✅ 方法二:使用 grid 佈局(推薦,新項目首選)
.container {
display: grid;
place-items: center; /* 水平 + 垂直居中 */
height: 100vh;
}
✅ 優點:一行代碼搞定,語義清晰
📌 適用:支持 CSS Grid 的現代瀏覽器
✅ 方法三:使用 absolute + transform(經典方法,適用於固定寬高)
.container {
position: relative;
height: 100vh;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
✅ 優點:兼容性極好(IE9+)
❗ 注意:需要知道子元素的寬高,否則transform會偏移
✅ 方法四:使用 text-align + line-height(僅適用於單行文本)
.container {
text-align: center;
line-height: 100vh;
height: 100vh;
white-space: nowrap;
}
.centered {
display: inline-block;
vertical-align: middle;
line-height: normal;
}
✅ 優點:簡單,適合純文本
❌ 缺點:僅限單行文本,不支持複雜內容
✅ 方法五:使用 table-cell(舊方案,較少使用)
.container {
display: table-cell;
text-align: center;
vertical-align: middle;
width: 100%;
height: 100vh;
}
.centered {
display: inline-block;
}
⚠️ 已過時,不推薦新項目使用
📌 總結建議
| 方法 | 推薦程度 | 適用場景 |
|---|---|---|
flexbox |
⭐⭐⭐⭐⭐ | 通用、推薦 |
grid |
⭐⭐⭐⭐⭐ | 新項目首選 |
absolute + transform |
⭐⭐⭐⭐ | 需兼容舊瀏覽器 |
text-align + line-height |
⭐⭐ | 單行文本 |
table-cell |
⭐ | 已淘汰,避免使用 |
示例代碼(推薦使用 Flexbox)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/resources/static/css/center.css"]
.container {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
.centered {
padding: 20px;
background-color: #007bff;
color: white;
border-radius: 8px;
font-size: 18px;
}
<!-- [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/resources/static/index.html"] -->
<div class="container">
<div class="centered">我是水平垂直居中的內容</div>
</div>
8.預編譯語言相比於CSS的優勢
預編譯語言(如 Sass、Less、Stylus)相比於原生 CSS 有諸多優勢,尤其在大型項目或複雜樣式管理中表現突出。以下是主要優勢的詳細説明:
✅ 1. 變量支持(Variables)
- 原生 CSS:不支持變量,重複樣式需手動修改。
- 預編譯語言:支持變量定義,便於統一管理主題色、間距等。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/variables.scss"]
$primary-color: #007bff;
$font-size-base: 14px;
.button {
background-color: $primary-color;
font-size: $font-size-base;
}
✔️ 修改變量即可全局更新樣式,提升維護性。
✅ 2. 嵌套(Nesting)
- 原生 CSS:選擇器層級需手動寫全,冗長易錯。
- 預編譯語言:支持嵌套語法,結構更清晰。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/components.scss"]
.navbar {
background: #333;
.nav-item {
color: white;
&:hover {
background: #555;
}
}
}
✔️ 生成的 CSS 會自動扁平化,結構更直觀。
✅ 3. 混合(Mixins)與函數
- 原生 CSS:無法複用複雜樣式邏輯。
- 預編譯語言:支持
mixin和函數,實現高度複用。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/mixins.scss"]
@mixin border-radius($radius) {
-webkit-border-radius: $radius;
-moz-border-radius: $radius;
border-radius: $radius;
}
.button {
@include border-radius(5px);
}
✔️ 可封裝響應式佈局、陰影、動畫等通用樣式。
✅ 4. 繼承(Extend/Inheritance)
- 預編譯語言:支持樣式繼承,減少冗餘代碼。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/extends.scss"]
.base-button {
padding: 10px 20px;
border: none;
border-radius: 4px;
}
.success-button {
@extend .base-button;
background-color: green;
}
.error-button {
@extend .base-button;
background-color: red;
}
✔️ 生成的 CSS 會合並公共樣式,體積更小。
✅ 5. 運算能力
- 預編譯語言:支持數學運算、顏色運算。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/utils.scss"]
$container-width: 960px;
$gutter: 20px;
.container {
width: $container-width - $gutter * 2;
margin: 0 auto;
}
// 顏色運算
$base-color: #333;
$lighter: lighten($base-color, 20%);
✔️ 動態計算佈局、顏色,提升靈活性。
✅ 6. 條件與循環
- 預編譯語言:支持
if、for、each等控制語句。
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/loops.scss"]
@for $i from 1 through 5 {
.item-#{$i} {
width: 20px * $i;
}
}
✔️ 生成 5 個不同寬度的類,適合柵格系統或圖標庫。
✅ 7. 模塊化與組織
- 預編譯語言:支持
@import和模塊化開發,便於拆分維護。
// 主入口文件
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/styles/main.scss"]
@import "variables";
@import "mixins";
@import "components/button";
@import "layout";
✔️ 項目結構清晰,團隊協作更高效。
❌ 原生 CSS 的侷限(對比)
| 功能 | 原生 CSS | 預編譯語言 |
|---|---|---|
| 變量 | ❌ 不支持 | ✅ 支持 |
| 嵌套 | ❌ 僅限寫法 | ✅ 支持 |
| 複用 | ❌ 需複製粘貼 | ✅ Mixins/Extend |
| 運算 | ❌ 無 | ✅ 支持 |
| 條件/循環 | ❌ 無 | ✅ 支持 |
| 模塊化 | ❌ 依賴構建工具 | ✅ 原生支持 |
✅ 總結:預編譯語言的優勢
| 優勢 | 説明 |
|---|---|
| 提高開發效率 | 代碼更簡潔,減少重複 |
| 增強可維護性 | 統一管理變量、樣式 |
| 支持複雜邏輯 | 條件、循環、函數 |
| 利於團隊協作 | 模塊化、結構清晰 |
| 適配大型項目 | 適合組件化、UI 庫開發 |
🔧 推薦使用場景
- 大型前端項目(如 SPA、後台管理系統)
- UI 組件庫開發(如 Ant Design、Element Plus)
- 需要主題切換、響應式佈局的系統
📌 常用預編譯語言
| 語言 | 特點 |
|---|---|
| Sass | 功能最強大,支持 SCSS 和 Indented Syntax |
| Less | 語法接近 CSS,易上手 |
| Stylus | 極簡語法,支持 JS 表達式 |
在 paas-plus-web 項目中,推薦使用 Sass(SCSS),並配合構建工具(如 Webpack、Vite)進行編譯。
9.設計一個小方案,獲取指定瀏覽器指定版本的localStorage的近似最大值
為了獲取指定瀏覽器指定版本的 localStorage 近似最大存儲容量,我們可以設計一個輕量級的 “動態探測方案”,通過逐步寫入數據並檢測寫入是否成功,來近似估算 localStorage 的容量上限。
✅ 方案目標
獲取某瀏覽器(如 Chrome 120)的 localStorage 近似最大容量(單位:字節),用於兼容性評估或存儲策略設計。
🎯 方案設計思路
- 基於瀏覽器的
localStorage特性:
-localStorage有容量限制(通常 5MB~10MB,因瀏覽器和版本而異)。
- 寫入超過容量時,會拋出QuotaExceededError(或DOMException)。 - 採用二分法探測:
- 從一個合理的起始值(如 1MB)開始,逐步嘗試寫入更大數據。
- 當寫入失敗時,説明已超過容量,回退並逼近真實上限。 - 使用字符串模擬數據:
- 用JSON.stringify的JSON字符串模擬真實數據(避免結構複雜影響估算)。
- 使用Array(1000).fill('a').join('')生成可控制長度的字符串。 - 返回近似最大值(單位:字節)
✅ 實現代碼(JavaScript)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/utils/localStorageCapacity.js"]
/**
* 獲取指定瀏覽器版本的 localStorage 近似最大容量(單位:字節)
* @param {number} minSize - 最小探測起始值(字節)
* @param {number} maxSize - 最大探測上限(字節)
* @param {number} step - 二分步長(建議 1024 或 10240)
* @returns {Promise<number>} 近似最大容量(字節)
*/
async function estimateLocalStorageCapacity(minSize = 1024 * 1024, maxSize = 1024 * 1024 * 10, step = 1024 * 10) {
const key = '__localStorage_capacity_test__';
let left = minSize;
let right = maxSize;
let best = minSize;
// 生成指定長度的字符串
function generateString(len) {
return 'a'.repeat(len);
}
// 二分法探測
while (left <= right) {
const mid = Math.floor((left + right) / 2);
const data = generateString(mid);
const payload = {
timestamp: Date.now(),
data: data,
size: data.length
};
try {
localStorage.setItem(key, JSON.stringify(payload));
best = mid; // 成功,嘗試更大
left = mid + step;
} catch (e) {
if (e.name === 'QuotaExceededError' || e.name === 'DOMException') {
right = mid - step; // 失敗,縮小範圍
} else {
console.warn('Unexpected error during localStorage test:', e);
break;
}
}
}
// 清理測試數據
localStorage.removeItem(key);
return best;
}
// 使用示例
// estimateLocalStorageCapacity().then(capacity => {
// console.log(`Estimated localStorage capacity: ${capacity} bytes (~${(capacity / 1024 / 1024).toFixed(2)} MB)`);
// });
// 導出函數
export default estimateLocalStorageCapacity;
🧪 使用建議
- 在瀏覽器控制枱中運行:
`js
import estimateLocalStorageCapacity from './src/utils/localStorageCapacity.js';
estimateLocalStorageCapacity().then(cap => {
console.log(Local storage max capacity: ${cap} bytes);
});
` - 不同瀏覽器版本差異:
- Chrome 120+:通常 ~5MB~10MB
- Firefox:約 5MB~10MB
- Safari:約 5MB(iOS 15+ 有嚴格限制)
- Edge:同 Chrome - 注意:
-localStorage的實際容量受用户設置、隱私模式、擴展影響。
- 此方案為“近似”值,非絕對精確。
📌 擴展建議
| 功能 | 建議 |
|---|---|
| 多版本瀏覽器測試 | 結合 Puppeteer 或 Selenium 自動化測試不同瀏覽器版本 |
| 記錄測試結果 | 將結果寫入 localStorage 或發送到監控系統 |
| 支持異步檢測 | 用於 Web 應用啓動時自動探測 |
✅ 總結
| 特性 | 説明 |
|---|---|
| 方法 | 二分法 + 逐步寫入探測 |
| 精度 | 近似值,誤差 ≤ 步長(如 10KB) |
| 適用性 | 所有支持 localStorage 的瀏覽器 |
| 安全性 | 僅寫入臨時測試 key,自動清理 |
✅ 推薦:將該腳本集成到前端應用啓動流程中,用於動態判斷存儲策略(如是否啓用離線緩存、是否壓縮數據等)。
1.This 理解,如何改變this 指向:apply/call/bind/箭頭函數
在 JavaScript 中,this 是一個非常重要的概念,它的指向取決於函數的調用方式。理解 this 的綁定機制以及如何改變其指向,是掌握 JavaScript 高級編程的關鍵。
🔍 一、this 的基本理解
this 指向的是函數執行時所在的上下文對象,不是定義時的上下文,而是調用時的上下文。
常見 this 指向情況:
| 調用方式 | this 指向 |
|---|---|
普通函數調用(fn()) |
window(嚴格模式下為 undefined) |
對象方法調用(obj.fn()) |
該對象(obj) |
構造函數調用(new Fn()) |
新創建的實例對象 |
事件綁定(btn.onclick = fn) |
觸發事件的 DOM 元素 |
| 箭頭函數 | 繼承外層作用域的 this |
✨ 二、如何改變 this 指向
JavaScript 提供了三種內置方法來顯式改變 this 的指向:call、apply、bind。另外,箭頭函數也會影響 this 的綁定。
1. call(thisArg, arg1, arg2, ...)
- 立即調用函數,並將
this指向thisArg - 參數逐個傳入
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello', '!'); // Hello, Alice!
✅ 適用於需要立即執行函數並傳參。
2. apply(thisArg, [argsArray])
- 立即調用函數,
this指向thisArg - 參數以數組形式傳入
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: 'Bob' };
greet.apply(person, ['Hi', '.']); // Hi, Bob.
✅ 適用於參數是數組的情況(如 Math.max.apply(null, arr))
3. bind(thisArg, arg1, arg2, ...)
- 不立即執行,返回一個新函數
- 新函數的
this被永久綁定到thisArg - 可以預設部分參數
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const person = { name: 'Charlie' };
const boundGreet = greet.bind(person, 'Hey');
boundGreet('!'); // Hey, Charlie!
✅ 適用於需要複用綁定 this 的場景,如事件處理、回調函數。
4. 箭頭函數(Arrow Function)——不綁定 this
- 箭頭函數沒有自己的
this - 它的
this繼承自外層作用域 - 不能用
call/apply/bind改變this
const obj = {
name: 'Diana',
regularFn: function() {
console.log(this.name); // Diana
},
arrowFn: () => {
console.log(this.name); // undefined(外層作用域沒有 name)
}
};
obj.regularFn(); // Diana
obj.arrowFn(); // undefined
❗注意:箭頭函數中的this是靜態綁定的,無法通過call、apply、bind改變。
🎯 三、總結對比
| 方法 | 是否立即執行 | 能否改變 this |
是否可預設參數 | 是否可被 bind 改變 |
|---|---|---|---|---|
call |
✅ 是 | ✅ 可以 | ✅ 可以 | ❌ 不適用 |
apply |
✅ 是 | ✅ 可以 | ✅ 可以(數組) | ❌ 不適用 |
bind |
❌ 否 | ✅ 可以 | ✅ 可以 | ✅ 本身是綁定 |
| 箭頭函數 | ❌ 不適用 | ❌ 不能改變 | ✅ 預設參數 | ❌ 不能改變 |
🧠 四、實用場景舉例
場景 1:借用數組方法
function sum() {
return Array.prototype.slice.call(arguments).reduce((a, b) => a + b, 0);
}
// 或
function sum() {
return [...arguments].reduce((a, b) => a + b, 0);
}
場景 2:事件處理器綁定 this
const button = document.getElementById('btn');
const obj = {
name: 'User',
handleClick() {
alert(`Hello, ${this.name}`);
}
};
// 正確綁定
button.addEventListener('click', obj.handleClick.bind(obj));
場景 3:箭頭函數避免 this 丟失
const obj = {
name: 'Alice',
delayGreet() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // ✅ Alice
}, 1000);
}
};
obj.delayGreet();
若用普通函數,this會變成window或undefined。
📌 五、記憶口訣
“誰調用,this 就指向誰”
但箭頭函數例外:“誰外層,this 就是誰”
✅ 總結一句話:
call/apply:立即調用,改變thisbind:返回綁定this的新函數,可複用- 箭頭函數:
this由外層決定,無法通過call/apply/bind改變
2.原型鏈繼承方式有哪一些,ES6的繼承實現
在 JavaScript 中,原型鏈繼承是實現對象之間繼承關係的核心機制。以下是常見的原型鏈繼承方式,以及 ES6 中 class 語法如何實現繼承。
一、常見的原型鏈繼承方式
1. 構造函數繼承(借用構造函數)
通過 call / apply 在子類構造函數中調用父類構造函數。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
function Child(name) {
Parent.call(this, name); // 借用父類構造函數
}
const child1 = new Child('Tom');
console.log(child1.name); // Tom
console.log(child1.colors); // ['red', 'blue']
✅ 優點:
- 可以傳參給父構造函數
- 不會共享父類的引用類型屬性
❌ 缺點:
- 父類原型上的方法無法繼承
- 每個實例都重新創建方法,無法複用
2. 原型鏈繼承(原型式繼承)
讓子類的原型指向父類的實例。
function Parent() {
this.name = 'Parent';
this.colors = ['red', 'blue'];
}
function Child() {}
Child.prototype = new Parent(); // 原型鏈繼承
const child1 = new Child();
const child2 = new Child();
console.log(child1.name); // Parent
console.log(child1.colors); // ['red', 'blue']
child1.colors.push('green');
console.log(child2.colors); // ['red', 'blue', 'green'] ❌ 共享引用
✅ 優點:
- 原型上的方法可以被繼承
- 簡單直觀
❌ 缺點:
- 父類實例屬性(引用類型)被所有子類共享
- 創建子類實例時無法向父類構造函數傳參
3. 組合繼承(經典繼承)
結合構造函數繼承 + 原型鏈繼承。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 構造函數繼承(傳參、獨立屬性)
this.age = age;
}
Child.prototype = new Parent(); // 原型鏈繼承(繼承方法)
Child.prototype.constructor = Child; // 修正構造函數指向
Child.prototype.sayAge = function () {
console.log(this.age);
};
const child1 = new Child('Tom', 18);
child1.colors.push('yellow');
console.log(child1.colors); // ['red', 'blue', 'yellow']
const child2 = new Child('Jerry', 20);
console.log(child2.colors); // ['red', 'blue'] ✅ 獨立
✅ 優點:
- 可以傳參
- 屬性不共享
- 方法可複用
❌ 缺點:
- 父類構造函數被調用兩次(一次在
Parent.call,一次在new Parent())
4. 寄生組合繼承(推薦)
解決組合繼承中父類構造函數被調用兩次的問題。
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
Parent.prototype.sayName = function () {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 只調用一次
this.age = age;
}
// 關鍵:使用 Object.create 創建一箇中間對象,避免調用 Parent
function inheritPrototype(Child, Parent) {
const prototype = Object.create(Parent.prototype); // 創建父原型的副本
prototype.constructor = Child; // 修正構造函數
Child.prototype = prototype;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
console.log(this.age);
};
const child1 = new Child('Tom', 18);
console.log(child1.name); // Tom
✅ 優點:
- 只調用父類構造函數一次
- 屬性獨立
- 方法可複用
- 最佳實踐
二、ES6 的繼承實現(class 語法)
ES6 引入了 class 語法,底層仍是基於原型鏈,但語法更清晰,語義更強。
1. 基本語法
class Parent {
constructor(name) {
this.name = name;
this.colors = ['red', 'blue'];
}
sayName() {
console.log(this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 調用父類構造函數
this.age = age;
}
sayAge() {
console.log(this.age);
}
}
const child = new Child('Tom', 18);
child.sayName(); // Tom
child.sayAge(); // 18
2. 內部原理(底層原型鏈)
Child.prototype指向一個對象,其__proto__指向Parent.prototypeChild.prototype.constructor指向ChildChild.__proto__指向ParentChild.prototype.__proto__指向Parent.prototype
也就是説:Child是Parent的子類,繼承了Parent的原型方法。
3. super 的作用
super():調用父類構造函數super.method():調用父類原型上的方法
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
sayName() {
console.log('Hello, I am ' + this.name);
super.sayName(); // 調用父類方法
}
}
三、總結對比
| 繼承方式 | 是否傳參 | 屬性共享 | 方法複用 | 推薦度 |
|---|---|---|---|---|
| 構造函數繼承 | ✅ | ❌ | ❌ | ⭐⭐ |
| 原型鏈繼承 | ❌ | ✅ | ✅ | ⭐⭐ |
| 組合繼承 | ✅ | ❌ | ✅ | ⭐⭐⭐⭐ |
| 寄生組合繼承 | ✅ | ❌ | ✅ | ⭐⭐⭐⭐⭐ |
ES6 class + extends |
✅ | ❌ | ✅ | ⭐⭐⭐⭐⭐ |
✅ 推薦使用方式
- 現代開發中,優先使用 ES6 的
class和extends,語法簡潔、可讀性強。 - 如果需要兼容老環境,可使用“寄生組合繼承”模式。
📌 附:ES6 繼承的底層原型鏈示意圖(簡化)
Child.prototype
└── __proto__ → Parent.prototype
└── __proto__ → Object.prototype
Child.__proto__ → Parent
Parent.__proto__ → Object
3.事件委託、冒泡
在前端開發中,事件委託和事件冒泡是兩個非常重要的概念,它們共同作用於 DOM 事件處理機制。下面我將從原理、區別、應用場景等方面進行詳細説明,並提供代碼示例。
一、事件冒泡(Event Bubbling)
✅ 定義:
當一個元素上的事件被觸發時,事件會從該元素開始,逐級向父級元素傳播,直到文檔根節點(document)為止。這個過程稱為事件冒泡。
🔍 示例:
<div id="parent">
<button id="child">點擊我</button>
</div>
// 綁定事件監聽器
document.getElementById('child').addEventListener('click', function () {
console.log('子元素被點擊');
});
document.getElementById('parent').addEventListener('click', function () {
console.log('父元素被點擊');
});
document.addEventListener('click', function () {
console.log('document 被點擊');
});
📌 輸出順序:
子元素被點擊
父元素被點擊
document 被點擊
説明:點擊button後,事件從button開始,冒泡到div,再到document。
二、事件委託(Event Delegation)
✅ 定義:
利用事件冒泡機制,將事件監聽器綁定在父元素上,通過判斷 event.target 來確定是哪個子元素觸發了事件。
✅ 優勢:
- 減少事件監聽器數量,提升性能。
- 動態添加的子元素也能響應事件(無需重新綁定)。
- 適合處理大量相似子元素的事件(如列表項、表格行等)。
📌 代碼示例:動態列表的點擊事件委託
<ul id="list">
<li>項目 1</li>
<li>項目 2</li>
<li>項目 3</li>
</ul>
<button id="addBtn">添加新項目</button>
// 事件委託:監聽 ul 的點擊事件
document.getElementById('list').addEventListener('click', function (e) {
// 判斷點擊的是不是 li 元素
if (e.target.tagName === 'LI') {
console.log('點擊了項目:', e.target.textContent);
}
});
// 動態添加項目
document.getElementById('addBtn').addEventListener('click', function () {
const li = document.createElement('li');
li.textContent = `項目 ${Math.floor(Math.random() * 100)}`;
document.getElementById('list').appendChild(li);
});
✅ 即使新添加的li沒有綁定事件,也能響應點擊 —— 因為事件冒泡到了ul上,由委託處理。
三、事件冒泡 vs 事件委託
| 特性 | 事件冒泡 | 事件委託 |
|---|---|---|
| 觸發順序 | 從子到父 | 由 event.target 判斷來源 |
| 是否必須有子元素 | 是 | 可以沒有(動態添加也有效) |
| 性能影響 | 多個監聽器性能差 | 僅需一個監聽器,性能好 |
| 適用場景 | 簡單事件綁定 | 大量動態子元素、性能敏感場景 |
四、注意事項
- 使用
event.target而非event.currentTarget
-event.target:觸發事件的原始元素(最內層)。
-event.currentTarget:綁定事件的元素(通常是父元素)。 - 避免冒泡到不需要的層級
- 可使用event.stopPropagation()阻止冒泡(謹慎使用)。 - 兼容性
- 所有現代瀏覽器都支持事件冒泡和委託。
五、進階:如何判斷事件是否屬於某個子元素?
// 通用事件委託函數
function delegate(parentSelector, childSelector, eventType, callback) {
const parent = document.querySelector(parentSelector);
parent.addEventListener(eventType, function (e) {
if (e.target.matches(childSelector)) {
callback(e);
}
});
}
// 使用
delegate('#list', 'li', 'click', function (e) {
console.log('點擊了:', e.target.textContent);
});
✅ 使用 Element.matches() 可以更安全地判斷元素類型。
✅ 總結
- 事件冒泡是事件傳播的機制。
- 事件委託是利用冒泡機制實現高效事件管理的策略。
- 推薦在處理列表、按鈕組、動態內容時使用事件委託。
4.Promise 基本用法
Promise 是 JavaScript 中用於處理異步操作的一種強大機制,它代表一個異步操作的最終完成(或失敗)及其結果值。Promise 提供了更清晰、更可讀的異步編程方式,避免了“回調地獄”(Callback Hell)。
✅ 一、Promise 的三種狀態
pending:初始狀態,既沒有完成,也沒有失敗。fulfilled(成功):操作成功完成。rejected(失敗):操作失敗。
狀態一旦改變,就不可逆。
✅ 二、基本語法
const myPromise = new Promise((resolve, reject) => {
// 異步操作(如:網絡請求、文件讀取等)
setTimeout(() => {
const success = true;
if (success) {
resolve("操作成功!");
} else {
reject("操作失敗!");
}
}, 1000);
});
✅ 三、使用 .then() 和 .catch()
myPromise
.then((result) => {
console.log("成功:", result); // 輸出: 成功: 操作成功!
return result.toUpperCase(); // 可以繼續鏈式調用
})
.then((upperResult) => {
console.log("大寫:", upperResult);
})
.catch((error) => {
console.error("失敗:", error); // 處理錯誤
});
✅.then()處理成功結果,.catch()捕獲錯誤,也可以用.finally()做收尾操作。
myPromise
.then((res) => console.log(res))
.catch((err) => console.error(err))
.finally(() => console.log("操作完成"));
✅ 四、常用靜態方法
| 方法 | 説明 |
|---|---|
Promise.resolve(value) |
返回一個已成功的 Promise |
Promise.reject(reason) |
返回一個已失敗的 Promise |
Promise.all(promises) |
所有 Promise 都成功時才成功,任一失敗則整體失敗 |
Promise.allSettled(promises) |
等待所有 Promise 結束(成功或失敗),返回結果數組 |
Promise.race(promises) |
第一個完成(成功或失敗)的 Promise 決定結果 |
示例:Promise.all
const promise1 = Promise.resolve("第一個完成");
const promise2 = new Promise((resolve) => setTimeout(() => resolve("第二個完成"), 2000));
const promise3 = Promise.reject("失敗了");
Promise.all([promise1, promise2, promise3])
.then(results => console.log(results))
.catch(err => console.error("任意失敗:", err)); // 輸出: 任意失敗: 失敗了
✅ 五、與 async/await 的結合(推薦寫法)
async function fetchData() {
try {
const result = await myPromise;
console.log("結果:", result);
return result;
} catch (error) {
console.error("錯誤:", error);
}
}
fetchData();
async/await 是對 Promise 的語法糖,讓異步代碼更像同步代碼。
✅ 六、總結
| 特性 | 説明 |
|---|---|
| 鏈式調用 | .then().then().catch() |
| 錯誤捕獲 | .catch() 可以捕獲前面所有錯誤 |
| 並行執行 | Promise.all() 等方法 |
| 可讀性強 | 比嵌套回調清晰很多 |
📌 常見場景
- 發送 HTTP 請求(如
fetch返回 Promise) - 讀取文件
- 數據庫操作
- 多個異步任務並行執行
5.函數節流、防抖
函數節流(Throttle)和防抖(Debounce)是前端開發中常用的優化技術,主要用於控制函數的執行頻率,避免因高頻觸發導致性能問題(如頻繁的 DOM 操作、網絡請求等)。
✅ 一、函數防抖(Debounce)
定義:在事件被觸發後,延遲一段時間再執行函數;如果在這段時間內再次觸發,則重新計時。
適用場景:搜索框輸入、窗口大小調整(resize)、表單驗證等。
📌 示例:防抖函數實現
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/utils/debounce.js"]
/**
* 防抖函數:延遲執行,如果在延遲時間內再次觸發,則重置定時器
* @param {Function} fn - 要執行的函數
* @param {number} delay - 延遲時間(毫秒)
* @returns {Function}
*/
function debounce(fn, delay) {
let timerId;
return function (...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用示例
const searchHandler = debounce((keyword) => {
console.log('搜索關鍵詞:', keyword);
// 發送請求或處理邏輯
}, 500);
// 模擬輸入事件
// searchHandler('a');
// searchHandler('ab');
// searchHandler('abc'); // 只有最後一次會執行
✅ 二、函數節流(Throttle)
定義:在規定時間內最多執行一次函數,無論事件觸發多少次。
適用場景:滾動事件(scroll)、鼠標移動(mousemove)、按鈕點擊防重複提交等。
📌 示例:節流函數實現(時間戳版)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/utils/throttle.js"]
/**
* 節流函數:規定時間內最多執行一次
* @param {Function} fn - 要執行的函數
* @param {number} delay - 節流時間(毫秒)
* @returns {Function}
*/
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用示例
const scrollHandler = throttle(() => {
console.log('頁面滾動中...');
}, 100);
// 模擬滾動事件頻繁觸發
// window.addEventListener('scroll', scrollHandler);
✅ 三、進階:使用 requestAnimationFrame 實現更平滑的節流(適合動畫場景)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/utils/throttleRaf.js"]
/**
* 使用 requestAnimationFrame 實現節流(適用於動畫類場景)
* @param {Function} fn
* @returns {Function}
*/
function throttleRaf(fn) {
let isThrottled = false;
return function (...args) {
if (!isThrottled) {
isThrottled = true;
requestAnimationFrame(() => {
fn.apply(this, args);
isThrottled = false;
});
}
};
}
// 使用示例
const animateHandler = throttleRaf(() => {
console.log('動畫幀執行');
});
✅ 四、對比總結
| 特性 | 防抖(Debounce) | 節流(Throttle) |
|---|---|---|
| 觸發頻率 | 最後一次觸發才執行 | 按固定頻率執行(如每 100ms) |
| 適用場景 | 搜索、表單校驗、窗口縮放 | 滾動、鼠標移動、動畫 |
| 是否延遲執行 | 是(延遲執行最後一次) | 否(第一次或每隔一段時間執行) |
| 是否重置定時器 | 是(每次觸發都重置) | 否(基於時間戳判斷) |
✅ 五、推薦使用方式
- 搜索框輸入 → 使用
debounce - 滾動/鼠標移動 → 使用
throttle - 高頻按鈕點擊 → 使用
throttle或debounce防重複提交
如需封裝為通用工具庫,可將上述函數放入 utils 目錄下,按需引入使用。
1.事件委託是什麼?意義
事件委託(Event Delegation) 是一種利用 事件冒泡機制 來處理事件的技術,其核心思想是:將事件監聽器綁定在父元素上,而不是每個子元素上,通過判斷事件源(event.target)來決定是否響應。
✅ 一、事件委託的原理
在 DOM 事件中,事件會從最內層的元素開始,逐級向上傳播(冒泡),直到頂層(如 document 或 window)。
利用這一機制,我們可以只在父元素上註冊一個事件監聽器,然後通過 event.target 判斷事件實際來自哪個子元素,從而做出相應處理。
📌 示例(JavaScript):
// 傳統方式:給每個按鈕綁定事件
// const buttons = document.querySelectorAll('.btn');
// buttons.forEach(btn => {
// btn.addEventListener('click', () => console.log('按鈕被點擊'));
// });
// 事件委託方式:只在父元素上綁定一次
document.querySelector('.container').addEventListener('click', function (e) {
if (e.target.matches('.btn')) {
console.log('按鈕被點擊:', e.target.textContent);
}
});
✅ 二、事件委託的意義(優勢)
| 優勢 | 説明 |
|---|---|
| 🚀 性能優化 | 減少事件監聽器數量,降低內存佔用。尤其在動態生成大量元素時(如列表、表格行),優勢明顯。 |
| 🔄 動態元素支持 | 無需為新增的子元素重新綁定事件。例如:動態添加的 <li> 元素也能響應事件。 |
| 🧩 簡化管理 | 事件邏輯集中管理,易於維護和修改。 |
| 🔍 降低耦合度 | 子元素不需要關心事件處理,只負責觸發事件。 |
✅ 三、適用場景
- 動態生成的元素(如列表、分頁、表格行)
- 大量相似元素(如按鈕組、菜單項)
- 需要統一處理多個子元素行為的場景
⚠️ 注意事項
- 使用
event.target判斷事件來源,而不是event.currentTarget。 - 避免在
document上監聽太多事件,可能影響性能。 - 選擇合適的父元素,避免事件冒泡到不相關的層級。
✅ 總結
事件委託 = 利用事件冒泡機制 + 父元素統一監聽 + 通過 target 判斷真實觸發源
它是一種高效、優雅的事件處理模式,在現代前端開發中被廣泛使用(如 Vue、React 中的事件處理也借鑑了這一思想)。
2.選型Vue、Angularjs、React的依據
在選型前端框架時,Vue、AngularJS 和 React 是三種主流技術方案,它們各有優勢和適用場景。以下是基於項目需求、團隊能力、生態支持、性能表現等多個維度的選型依據分析,幫助你做出合理決策。
一、核心對比維度
| 維度 | Vue | AngularJS (1.x) | React |
|---|---|---|---|
| 學習成本 | 低(漸進式) | 中高(完整框架) | 中(需理解 JSX、虛擬 DOM) |
| 開發效率 | 高(模板 + 組件化 + CLI) | 中(雙向綁定 + 模塊化) | 高(JSX + 組件化 + 生態豐富) |
| 性能表現 | 優秀(響應式系統優化) | 一般(髒檢查機制) | 優秀(虛擬 DOM + Fiber) |
| 生態系統 | 豐富(Vue CLI, Vite, Vuex, Pinia, Vue Router) | 逐漸衰退(已進入維護模式) | 極其龐大(Redux, Zustand, Next.js, React Native) |
| 社區活躍度 | 高(全球廣泛使用) | 低(已停止更新) | 極高(Facebook 支持,社區活躍) |
| 適用場景 | 中小型項目、快速迭代、漸進式引入 | 老項目維護、大型企業級應用(歷史遺留) | 複雜交互、大型 SPA、跨平台應用(RN) |
| 類型支持 | 支持 TypeScript | 有限支持 | 優秀(原生支持 TS) |
二、選型依據詳解
✅ 選擇 Vue 的依據:
- 項目需要快速上手和高效開發
- Vue 的模板語法直觀,學習曲線平緩,適合新手或跨領域開發人員快速上手。
- 支持漸進式引入,可逐步替換舊系統。 - 團隊規模較小或技術棧不統一
- 不強制使用複雜架構(如 Redux、MobX),可輕鬆搭配 Pinia(現代狀態管理)和 Vue Router。 - 追求開發體驗與性能平衡
- Vue 3 + Vite 組合提供極快的熱更新和構建速度。
- 響應式系統優化良好,適合中大型單頁應用。 - 希望有良好的 TypeScript 支持
- Vue 3 完全原生支持 TypeScript,類型推斷強大。
🎯 推薦場景:中小型項目、創業公司 MVP、內部管理系統、需要快速交付的 Web 應用。
✅ 選擇 React 的依據:
- 需要構建複雜交互 UI
- React 的組件化思想更靈活,適合構建高度動態、交互複雜的界面(如 Dashboard、編輯器、可視化工具)。 - 團隊已有 JavaScript/TypeScript 基礎
- React 本身只關注視圖層,配合 Redux/Zustand 等狀態管理工具可靈活搭建架構。 - 計劃做跨平台開發(React Native)
- React Native 可複用大部分代碼,實現 iOS/Android/Web 三端統一。 - 項目長期維護且對生態要求高
- Next.js、Gatsby、Remix 等框架支持 SSR、靜態生成、SEO 優化,適合內容型網站或電商。 - 希望擁有強大社區支持和第三方庫
- React 生態龐大,幾乎任何功能都能找到成熟庫。
🎯 推薦場景:大型企業級應用、複雜後台系統、多端協同項目、高併發高交互產品。
❌ 不推薦 AngularJS (1.x) 的依據:
- 已進入維護模式:AngularJS 1.x 官方已於 2022 年停止維護,不再更新安全補丁。
- 性能瓶頸明顯:使用髒檢查機制,數據量大時卡頓嚴重。
- 學習成本高:強制使用 MVC 模式、依賴注入、指令系統,開發效率低。
- 現代開發不兼容:不支持 TypeScript 默認語法、無原生支持模塊化、難以集成新工具鏈。
⚠️ 僅建議:用於維護舊項目,絕不建議新項目採用。
三、綜合選型建議
| 項目特徵 | 推薦框架 |
|---|---|
| 快速開發、小團隊、中等複雜度 | ✅ Vue |
| 複雜交互、高可維護性、長期演進 | ✅ React |
| 舊系統遷移、僅做維護 | ⚠️ AngularJS(僅限維護) |
| 需要跨平台(iOS/Android/Web) | ✅ React(React Native) |
| 重視 SSR/SEO/靜態生成 | ✅ React + Next.js 或 Vue + Nuxt.js |
| 團隊熟悉 TypeScript,追求類型安全 | ✅ Vue 3 / React + TS |
四、附加建議
- 若使用 Vue,建議使用 Vue 3 + Vite + Pinia + Vue Router 的現代組合。
- 若使用 React,推薦 React 18 + TypeScript + Next.js 構建全棧應用。
- 避免使用 AngularJS 新項目,除非是特殊遺留系統遷移需求。
✅ 總結一句話:
“新項目優先選 Vue 或 React,Vue 更易上手,React 更靈活強大;AngularJS 已淘汰,不推薦使用。”
3.對 MVC、MVP 、MVVM 的理解
MVC、MVP 和 MVVM 是三種常見的軟件架構設計模式,主要用於分離關注點(Separation of Concerns),提高代碼的可維護性、可測試性和可擴展性。它們廣泛應用於前端開發、桌面應用和移動應用開發中。
下面是對這三種模式的詳細理解與對比:
一、MVC(Model-View-Controller)
1. 核心思想
將應用程序分為三個核心組件:
- Model(模型):負責數據邏輯和業務邏輯,管理數據狀態。
- View(視圖):負責展示數據,是用户看到的界面。
- Controller(控制器):處理用户輸入,更新模型或視圖。
2. 工作流程
用户操作 → Controller → 修改 Model → Model 通知 View 更新 → View 重新渲染
3. 特點
- 優點:
- 模塊清晰,職責分明。
- 適合 Web 應用(如 Spring MVC、Ruby on Rails)。 - 缺點:
- Controller 可能變得臃腫,承擔過多邏輯。
- View 和 Model 之間耦合較強(通過觀察者模式解耦,但不夠靈活)。
4. 典型應用
- Java Web:Spring MVC
- iOS:UIKit + MVC(Apple 原生推薦)
- Web 前端:早期的 Backbone.js
二、MVP(Model-View-Presenter)
1. 核心思想
在 MVC 的基礎上,引入 Presenter 作為中介,使 View 和 Model 完全解耦。
- Model:數據與業務邏輯。
- View:只負責顯示和用户交互,不包含業務邏輯。
- Presenter:接收用户操作 → 調用 Model 處理 → 更新 View。
2. 工作流程
用户操作 → View → Presenter → Model → Presenter → 更新 View
3. 特點
- 優點:
- View 與 Model 完全解耦,便於單元測試。
- Presenter 承擔全部業務邏輯,View 更輕量。 - 缺點:
- View 需要定義接口(View Interface),代碼量增加。
- 邏輯在 Presenter 中集中,可能變複雜。
4. 典型應用
- Android 開發(早期推薦使用 MVP)
- WinForms、WPF(部分場景)
- 使用 Dagger、Butterknife 等框架配合 MVP
三、MVVM(Model-View-ViewModel)
1. 核心思想
通過數據綁定(Data Binding)實現 View 和 ViewModel 的自動同步,極大減少手動更新 UI 的代碼。
- Model:數據和業務邏輯。
- View:UI 層,不包含邏輯。
- ViewModel:暴露數據和命令給 View,使用數據綁定機制與 View 綁定。
2. 工作流程
用户操作 → View → ViewModel(命令執行) → Model → ViewModel(數據變更) → View(自動更新)
✅ 關鍵:雙向數據綁定(如 Angular、Vue、WPF、Android Jetpack Compose)
3. 特點
- 優點:
- View 與 ViewModel 通過綁定自動同步,開發效率高。
- 邏輯集中在 ViewModel,易於測試。
- 適合複雜 UI 場景(如表單、動態列表)。 - 缺點:
- 學習成本高(需要理解數據綁定、依賴注入等)。
- 綁定表達式複雜時,調試困難。
- 性能開銷略大(尤其在大量綁定時)。
4. 典型應用
- 前端:Vue.js、Angular、React + Zustand/Jotai(類 MVVM)
- Android:Android Jetpack(ViewModel + LiveData + DataBinding)
- WPF:XAML + MVVM(微軟官方推薦)
四、三者對比總結
| 特性 | MVC | MVP | MVVM |
|---|---|---|---|
| View 與 Model 解耦 | 一般(通過 Controller) | 強(通過 Presenter) | 極強(通過數據綁定) |
| 可測試性 | 低 | 高 | 非常高 |
| 開發效率 | 中等 | 中等 | 高(尤其 UI 複雜時) |
| 代碼複雜度 | 中 | 中高 | 中高(綁定機制複雜) |
| 適合場景 | Web、簡單應用 | Android 早期、需要高可測性 | 複雜 UI、現代前端/移動端 |
五、選擇建議
| 場景 | 推薦模式 |
|---|---|
| 傳統 Web 應用(如 Spring MVC) | ✅ MVC |
| Android 項目,強調可測試性 | ✅ MVP |
| 現代 Android(Jetpack)、複雜 UI、響應式頁面 | ✅ MVVM |
| 前端 SPA(Vue、React) | ✅ MVVM(或類 MVVM) |
| 需要高度解耦、自動化 UI 更新 | ✅ MVVM |
六、補充説明:MVVM 在現代框架中的體現
- Vue.js:通過
v-model實現雙向綁定,本質是 MVVM。 - Angular:使用
ngModel、@Input/@Output實現 ViewModel 與 View 的通信。 - Android Jetpack:
ViewModel+LiveData+DataBinding構成 MVVM 核心。
總結一句話:
MVC 是“指揮官”,MVP 是“傳話員”,MVVM 是“自動同步機器人”。
選擇哪種模式,取決於項目複雜度、團隊熟悉度、性能要求和長期可維護性。
4.説下對 Virtual DOM 算法的理解
Virtual DOM(虛擬 DOM)是現代前端框架(如 React、Vue 等)中用於高效更新 UI 的核心技術之一。它通過在內存中維護一個輕量級的 DOM 樹的 JavaScript 表示,來避免頻繁直接操作真實 DOM 所帶來的性能開銷。
一、核心思想
用 JavaScript 對象模擬真實 DOM,當數據變化時,先在虛擬 DOM 上進行 diff(差異對比)計算,找出需要更新的部分,再批量、高效地更新到真實 DOM 上。
✅ 目標:減少直接操作真實 DOM 的次數,提升渲染性能。
二、工作流程
- 初始化階段
- 根據組件的render函數生成一棵 Virtual DOM 樹(JS 對象)。
- 例如:
`js
const virtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Hello World'] },
{ type: 'p', props: {}, children: ['This is a paragraph.'] }
]
};
` - 更新階段
- 當狀態改變時,重新執行render函數,生成新的 Virtual DOM。
- 使用 Diff 算法 比較新舊 Virtual DOM 的差異。 - Diff 算法(核心)
- 採用 同層比較 + key 優化 的策略,提升效率。
- 常見策略:
- 樹級別的 Diff:只比較同層級節點。
- 組件級別的 Diff:若組件類型不同,直接替換整個子樹。
- Key 的作用:通過key屬性標識節點,幫助算法識別哪些節點被插入、刪除、移動或更新,避免不必要的重渲染。 - 應用更新
- 將 diff 結果生成最小的 DOM 操作集合。
- 批量更新真實 DOM,提高性能。
三、Diff 算法的優化點
| 優化策略 | 説明 |
|---|---|
| 同層比較 | 不跨層級比較,只比較同一層級的節點,複雜度從 O(n³) 降到 O(n)。 |
| key 的使用 | 通過 key 唯一標識元素,避免頻繁重排,提高複用率。 |
| 分層更新 | 只更新變化的部分,而不是整個 DOM 樹重新渲染。 |
⚠️ 注意:如果不使用 key,React/Vue 會默認使用索引作為 key,可能導致性能下降或狀態錯亂。
四、優勢與適用場景
| 優勢 | 説明 |
|---|---|
| ✅ 高性能 | 減少真實 DOM 操作,避免重排重繪。 |
| ✅ 跨平台支持 | Virtual DOM 可以映射到不同環境(Web、Native、小程序)。 |
| ✅ 便於調試 | 以 JS 對象形式存在,便於打印、分析、測試。 |
| 適用場景 | 説明 |
|---|---|
| 複雜交互 UI | 如表格、列表、表單等頻繁更新的場景。 |
| 多平台開發 | 如 React Native、WeChat Mini Program。 |
五、侷限性
- 並非萬能:對於簡單、靜態頁面,直接操作 DOM 可能更快。
- 內存佔用:虛擬 DOM 是 JS 對象,會佔用額外內存。
- 性能開銷:頻繁 diff 本身也有計算成本,適合中等及以上覆雜度的應用。
六、總結
Virtual DOM = 虛擬樹 + Diff 算法 + 批量更新
它是一種“以空間換時間”的設計模式,通過在內存中進行高效對比,最終只對真實 DOM 執行最少的操作,從而在複雜應用中實現高性能的 UI 更新。
舉個例子(React 中的使用)
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
- 每次點擊按鈕,React 會:
1. 生成新的 Virtual DOM;
2. 與舊的 Virtual DOM 做 diff;
3. 只更新<h1>內容,不重新渲染整個<div>。
✅ 一句話總結:
Virtual DOM 是通過在內存中維護一個輕量 DOM 樹副本,利用 Diff 算法找出最小變更集,從而高效更新真實 DOM 的機制。
1.組件間通信方式有哪些
組件間通信是前端開發(尤其是現代框架如 React、Vue、Angular 等)中的核心概念。以下是常見的組件間通信方式,按適用場景分類説明:
一、父子組件通信(最常見)
1. Props(屬性傳遞)
- 用途:父組件向子組件傳遞數據或回調函數。
- 特點:單向數據流,只讀。
- 示例(React):
`jsx
// 父組件
function Parent() {
const [name, setName] = useState("Alice");
const handleClick = () => setName("Bob");
return (
<Child name={name} onNameChange={handleClick} />
);
}
// 子組件
function Child({ name, onNameChange }) {
return (
<div>
<p>Hello, {name}</p>
<button onClick={onNameChange}>Change Name</button>
</div>
);
}
`
2. Callback 函數(回調)
- 用途:子組件通過調用父組件傳入的函數來通知父組件狀態變化。
- 常用於:子組件觸發事件、修改數據。
二、兄弟組件通信
1. 通過共同父組件中轉
- 方式:利用父組件管理共享狀態,通過 props 傳遞給兄弟組件。
- 優點:簡單直觀,適合中小型應用。
2. 使用狀態管理庫(推薦)
- 常用工具:Redux、MobX、Zustand、Vuex(Vue)、Pinia(Vue 3)
- 適用場景:多個組件需要共享狀態,且關係複雜。
- 優勢:集中管理狀態,解耦組件。
三、跨層級組件通信(非父子關係)
1. 事件總線(Event Bus)
- 原理:創建一個全局事件中心,組件通過發佈/訂閲模式通信。
- 示例(Vue):
`js
// eventBus.js
import { createApp } from 'vue'
const eventBus = createApp({})
export default eventBus
// 組件 A 發送事件
eventBus.config.globalProperties.$emit('user-login', { name: 'Alice' })
// 組件 B 接收事件
eventBus.config.globalProperties.$on('user-login', (user) => {
console.log('User logged in:', user)
})
`
2. Context API(React)
- 用途:在組件樹中跨多層傳遞數據,避免“props drilling”。
- 適用場景:主題、用户登錄狀態、語言等全局上下文。
- 示例:
`jsx
const ThemeContext = React.createContext()
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
)
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
)
}
function ThemedButton() {
const theme = useContext(ThemeContext)
return <button style={{ background: theme === 'dark' ? 'black' : 'white' }}>Button</button>
}
`
3. 發佈-訂閲模式(Pub/Sub)
- 實現方式:自定義事件中心(類似 Event Bus),適用於複雜系統。
- 優勢:解耦程度高,適合大型項目。
四、其他通信方式
1. LocalStorage / SessionStorage
- 用途:持久化存儲數據,組件通過讀寫存儲進行通信。
- 注意:僅適用於同源、同域下的組件通信,且需手動監聽變化(如
storage事件)。 - 示例:
`js
// 寫入
localStorage.setItem('user', JSON.stringify({ name: 'Alice' }))
// 監聽變化
window.addEventListener('storage', (e) => {
if (e.key === 'user') {
console.log('User updated:', e.newValue)
}
})
`
2. URL 參數(Query Params)
- 用途:通過路由參數傳遞數據,適合頁面跳轉時的通信。
- 適用框架:React Router、Vue Router。
3. 消息通信(PostMessage)
- 用途:用於 iframe 之間 或 主頁面與子頁面 通信。
- 示例:
`js
// 主頁面發送消息
window.postMessage({ type: 'LOGIN', data: 'Alice' }, '*')
// iframe 接收消息
window.addEventListener('message', (event) => {
console.log(event.data)
})
`
五、總結對比表
| 通信方式 | 適用場景 | 是否解耦 | 是否跨層級 | 是否支持異步 |
|---|---|---|---|---|
| Props | 父子組件 | 否 | 否 | 是 |
| Callback | 子向父通信 | 否 | 否 | 是 |
| Context API | 跨多層組件,全局狀態 | 是 | 是 | 是 |
| Event Bus / Pub/Sub | 多組件、無層級依賴 | 是 | 是 | 是 |
| Redux / Zustand | 複雜狀態管理 | 高 | 是 | 是 |
| localStorage | 持久化數據,同域通信 | 中 | 是 | 否(需監聽) |
| PostMessage | iframe 或跨域通信 | 高 | 是 | 是 |
| URL 參數 | 頁面跳轉傳遞參數 | 否 | 是 | 是 |
✅ 推薦實踐建議:
- 小項目:用
Props + Callback+Context。 - 中大型項目:使用
Zustand或Redux管理狀態。 - 跨層級/鬆耦合:優先考慮
Event Bus或Context。 - 跨域/iframe:使用
PostMessage。 - 持久化:
localStorage+storage事件監聽。
闡述 vuex/vue-router 工作機制
Vue.js 是一個用於構建用户界面的漸進式框架,而 Vuex 和 Vue Router 是其生態中兩個核心的官方插件,分別用於狀態管理和路由管理。下面從工作機制的角度詳細闡述它們的工作原理。
一、Vuex:狀態管理機制
1. 核心概念
Vuex 是專門為 Vue.js 應用設計的狀態管理模式。它採用 集中式存儲(單狀態樹)的方式管理所有組件共享的狀態。
核心概念組成:
| 概念 | 説明 |
|---|---|
State |
應用的唯一數據源,所有狀態都集中在此。 |
Getters |
類似於計算屬性,用於從 state 中派生出某些狀態。 |
Mutations |
同步修改 state 的唯一方式。必須是同步函數。 |
Actions |
異步操作的入口,可以包含任意異步操作,通過 commit 提交 mutation。 |
Modules |
支持將 store 分割成模塊,每個模塊擁有自己的 state、getters、mutations、actions。 |
2. 工作機制詳解
(1)數據流(單向數據流)
Vuex 的核心是 單向數據流,流程如下:
[組件] → (dispatch action) → [Actions] → (commit mutation) → [Mutations] → [State] → (getters) → [組件]
- 組件通過
this.$store.dispatch('actionName')觸發一個異步操作。 - Actions 中調用
commit('mutationName')來提交一個 mutation。 - Mutation 直接修改 state(必須是同步的)。
- State 的變化會觸發響應式更新,自動同步到所有依賴它的組件。
- 組件通過
this.$store.getters.xxx或computed訪問派生狀態。
✅ 優點:狀態變化可追蹤、可調試,便於開發工具(如 Vue Devtools)觀察。
(2)響應式機制
Vuex 使用 Vue 的響應式系統來實現狀態的響應式更新:
state是通過Vue.observable()或new Vue()創建的響應式對象。- 當 state 變化時,所有依賴它的組件(通過
mapState或computed訪問)會自動重新渲染。
(3)模塊化(Modules)
// store/modules/user.js
export default {
namespaced: true,
state: () => ({ name: '' }),
mutations: { SET_NAME(state, name) { state.name = name } },
actions: { setName({ commit }, name) { commit('SET_NAME', name) } },
getters: { fullName: state => `User: ${state.name}` }
}
// store/index.js
import { createStore } from 'vuex'
import userModule from './modules/user'
export default createStore({
modules: {
user: userModule
}
})
模塊命名空間(namespaced: true)避免命名衝突,支持this.$store.state.user.name。
二、Vue Router:路由管理機制
1. 核心功能
Vue Router 是 Vue.js 的官方路由庫,實現單頁應用(SPA)中的頁面跳轉與組件切換。
核心功能:
- 聲明式路由(
<router-link>) - 嵌套路由(Nested Routes)
- 動態路由匹配
- 路由守衞(Guard)
- 懶加載(Lazy Loading)
- 歷史模式(History Mode)與 Hash 模式
2. 工作機制詳解
(1)路由映射機制
Vue Router 將 URL 路徑與組件進行映射,通過配置 routes 數組:
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User }
]
當 URL 改變時,Vue Router 會解析路徑,匹配對應組件,並渲染到 <router-view> 中。
(2)組件渲染機制
<router-view>是路由出口組件,用於渲染匹配到的組件。- 路由切換時,Vue 會動態替換
<router-view>中的內容,不會刷新頁面。 - 組件的生命週期鈎子(如
mounted)在每次進入時都會觸發。
(3)路由守衞(Guard)
用於控制路由跳轉的權限或邏輯:
| 類型 | 觸發時機 | 作用 |
|---|---|---|
beforeEach |
全局前置守衞 | 路由跳轉前執行,可進行權限校驗 |
beforeResolve |
解析守衞 | 在導航確認前,等待異步組件解析 |
beforeEnter |
路由獨享守衞 | 在路由配置中定義 |
beforeRouteEnter |
組件內守衞 | 組件創建前執行,不能訪問 this |
beforeRouteUpdate |
組件內守衞 | 路由參數變化時觸發 |
beforeRouteLeave |
組件內守衞 | 離開路由前執行 |
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
(4)模式選擇
| 模式 | 特點 | 適用場景 |
|---|---|---|
| Hash 模式 | 使用 #,如 /home#about,兼容性好 |
無需服務器支持 |
| History 模式 | 使用 pushState,如 /home/about,URL 更美觀 |
需服務器支持(需配置 fallback) |
⚠️ History 模式需在服務端配置,否則刷新頁面會 404。
(5)懶加載(Lazy Loading)
const routes = [
{
path: '/about',
component: () => import('./views/About.vue')
}
]
- 使用
import()動態導入,實現代碼分割。 - 提升首屏加載性能。
三、Vuex 與 Vue Router 的協同工作
在大型項目中,兩者常配合使用:
- 路由守衞中訪問狀態:
`js
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.user.isAuthenticated) {
next('/login')
} else {
next()
}
})
` - 路由跳轉後更新狀態:
- 在actions中發起請求獲取數據。
- 在beforeRouteEnter或mounted中觸發 action。 - 狀態持久化:
- 使用vuex-persistedstate插件,將 state 持久化到localStorage,結合路由恢復登錄狀態。
四、總結對比
| 項目 | Vuex | Vue Router |
|---|---|---|
| 作用 | 狀態管理 | 路由管理 |
| 數據源 | 單一狀態樹 | 路由配置表 |
| 數據流 | 單向數據流 | 路由映射 → 組件渲染 |
| 響應式 | 依賴 Vue 響應式系統 | 依賴 Vue 組件更新機制 |
| 生命週期 | state → mutation → action → view | route → component → view |
| 典型使用 | 用户登錄狀態、全局消息、購物車 | 頁面跳轉、權限控制、嵌套路由 |
五、最佳實踐建議
- Vuex:
- 使用modules拆分狀態。
- Mutation 保持同步,Action 處理異步。
- 使用getters封裝複雜邏輯。 - Vue Router:
- 使用meta字段標記路由權限。
- 合理使用懶加載。
- 配置fallback支持 History 模式。
✅ 結語:
Vuex 和 Vue Router 是 Vue 生態中不可或缺的兩大支柱。Vuex 保證了狀態的可預測性與可維護性,Vue Router 實現了 SPA 的平滑導航體驗。掌握它們的機制,是構建大型 Vue 應用的關鍵。
Vue 如何實現按需加載
在 Vue 項目中實現按需加載(也稱懶加載、動態導入),可以顯著提升應用的首屏加載性能,避免一次性加載所有組件和路由,尤其適用於大型單頁應用(SPA)。
以下是 Vue 2 和 Vue 3 中實現按需加載的常見方式:
✅ 一、Vue 2 中按需加載(推薦使用 import())
1. 路由按需加載(懶加載)
// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home.vue') // 動態導入
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
}
]
})
✅ import('@/views/About.vue') 會自動打包成獨立 chunk,只有訪問該路由時才會加載。
2. 組件按需加載(非路由組件)
<!-- MyComponent.vue -->
<template>
<div>
<lazy-component />
</div>
</template>
<script>
export default {
components: {
LazyComponent: () => import('@/components/LazyComponent.vue')
}
}
</script>
⚠️ 注意:這種方式僅適用於組件註冊階段,且需確保 import() 返回 Promise。
✅ 二、Vue 3 中按需加載(推薦使用 defineAsyncComponent)
1. 路由按需加載
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { defineAsyncComponent } from 'vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/home',
name: 'Home',
component: defineAsyncComponent(() => import('@/views/Home.vue'))
},
{
path: '/about',
name: 'About',
component: defineAsyncComponent(() => import('@/views/About.vue'))
}
]
})
export default router
✅ defineAsyncComponent 是 Vue 3 提供的官方方法,支持加載狀態(如 loading)、錯誤處理等。
2. 組件按需加載(支持 loading 和 error)
<!-- AsyncComponent.vue -->
<template>
<div>
<async-component />
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('@/components/LazyComponent.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200, // 加載延遲(ms),避免閃屏
timeout: 3000 // 超時時間
})
</script>
✅ 支持loadingComponent(加載中)、errorComponent(錯誤時顯示)、delay、timeout等高級配置。
✅ 三、結合 Webpack / Vite 打包優化
1. Webpack(Vue CLI)
- 自動支持
import()的代碼分割。 - 生成獨立的
.jschunk 文件(如chunk-vendors.js,home.js)。
2. Vite(推薦)
- 原生支持
import()動態導入,無需額外配置。 - 通過
vite build自動拆分代碼。
✅ 四、路徑建議(按你的環境)
根據你提供的環境信息:
Current Working Directory: /Users/hejuan903/Documents/BackUp/program/paas-plus-web- 項目結構常見為:
src/views/,src/components/
示例路徑(確保使用絕對路徑):
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/router/index.js"]
import { createRouter, createWebHistory } from 'vue-router'
import { defineAsyncComponent } from 'vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/home',
name: 'Home',
component: defineAsyncComponent(() => import('@/views/Home.vue'))
}
]
})
export default router
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/components/AsyncButton.vue"]
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncButton = defineAsyncComponent({
loader: () => import('@/components/Button.vue'),
loadingComponent: () => import('@/components/LoadingSpinner.vue'),
delay: 300,
timeout: 5000
})
</script>
<template>
<AsyncButton />
</template>
✅ 總結:按需加載的核心要點
| 方案 | 適用版本 | 優點 |
|---|---|---|
() => import(...) |
Vue 2 & 3 | 簡潔,Webpack/Vite 原生支持 |
defineAsyncComponent |
Vue 3 | 更強控制(loading/error/timeout) |
lazy 指令(第三方) |
Vue 2/3 | 可用於圖片/組件懶加載 |
✅ 建議:
- Vue 2 項目使用
import()實現懶加載。 - Vue 3 項目使用
defineAsyncComponent,並搭配loadingComponent提升用户體驗。
vuex數據流?為什麼要遵循這個數據流?假如在頁面中直接修改state,而不是通過mutation 的commit方式修改,會怎麼樣?
在 Vue.js 中,Vuex 是官方推薦的狀態管理庫,用於集中管理應用中所有組件的共享狀態。它的核心思想是 單向數據流(Unidirectional Data Flow),即數據的流動遵循特定的規則,以保證狀態的可預測性和可維護性。
一、Vuex 的數據流(Data Flow)
Vuex 的數據流遵循以下流程:
View (組件) → Action → Mutation → State → View (組件)
具體流程如下:
- 組件(View) 通過
dispatch觸發一個 Action。 - Action 可以包含異步操作(如 API 調用),完成後通過
commit提交一個 Mutation。 - Mutation 是唯一可以直接修改 State 的地方,它接收一個
state作為第一個參數,並進行同步更新。 - State 更新後,所有依賴該狀態的組件會自動重新渲染。
✅ 關鍵點:
- 只有 Mutation 才能修改 State。
- Action 不能直接修改 State,只能通過
commit觸發 Mutation。- 組件不能直接修改 State,只能通過
dispatch觸發 Action。
二、為什麼要遵循這個數據流?
遵循這個數據流有以下幾個核心優勢:
1. 狀態變化可追蹤
- 所有狀態的變更都必須通過
Mutation,並且是 同步的、可記錄的。 - 可以輕鬆使用 DevTools(如 Vue DevTools)查看每一步的狀態變化,便於調試。
2. 防止意外狀態修改
- 如果組件直接修改
state,會導致:
- 狀態變化不可預測;
- 多個組件同時修改state時出現競態;
- 無法回溯歷史操作(如時間旅行調試)。
3. 支持異步操作和業務邏輯分離
Action負責處理異步邏輯(如請求 API),Mutation只負責同步更新狀態。- 邏輯清晰,職責分明。
4. 便於測試和維護
- 所有狀態更新都有“日誌”可查;
- 可以通過
commit模擬各種操作進行單元測試; - 項目越大,越能體現其價值。
三、如果在頁面中直接修改 state,會怎樣?
❌ 錯誤做法示例:
// ❌ 錯誤:組件中直接修改 state
this.$store.state.count += 1
⚠️ 會帶來以下問題:
| 問題 | 説明 |
|---|---|
| 1. 狀態變化不可追蹤 | DevTools 無法記錄這次修改,無法調試。 |
| 2. 無法實現時間旅行調試 | 無法回放狀態變化過程。 |
| 3. 多處修改易出錯 | 多個組件同時修改 state,可能造成數據不一致。 |
| 4. 無法保證同步性 | 如果在異步操作中直接改 state,可能引發競態。 |
| 5. 不符合 Vuex 設計原則 | 破壞了單向數據流,違背“單一數據源”原則。 |
🛠️ Vue 會警告你:
在開發模式下,Vue 會檢測到你直接修改state,並拋出警告:[Vuex] Do not mutate vuex store state outside of mutation handlers.
四、正確做法:通過 Mutation 修改 State
// ✅ 正確:通過 commit 觸發 Mutation
this.$store.commit('increment')
// store/mutations.js
mutations: {
increment(state) {
state.count += 1
}
}
如果需要異步操作,使用 Action:
// store/actions.js
actions: {
async incrementAsync({ commit }) {
await new Promise(resolve => setTimeout(resolve, 1000))
commit('increment')
}
}
// 組件中
this.$store.dispatch('incrementAsync')
五、總結
| 項目 | 正確方式 | 錯誤方式 |
|---|---|---|
| 修改 state | 通過 commit 調用 Mutation |
直接 this.$store.state.xxx 修改 |
| 異步操作 | 使用 Action | 在組件中直接寫異步邏輯 |
| 可調試性 | ✅ 支持 DevTools | ❌ 無法追蹤 |
| 可維護性 | ✅ 職責清晰 | ❌ 易出錯、難維護 |
✅ 結論:
必須遵循 Vuex 的單向數據流。
直接修改state雖然在技術上“能運行”,但會破壞狀態管理的完整性和可維護性,強烈不推薦。
如果你正在使用 Vue 3 + Vuex 4(或 Pinia),建議考慮使用 Pinia,它更簡潔、更靈活,但仍推薦遵循“狀態更新通過 Action/Mutation”的原則。
vue性能優化
Vue 性能優化是一個系統性工程,涉及代碼編寫、構建配置、運行時優化等多個方面。以下是針對 Vue 3(主流推薦)的全面性能優化建議,適用於大多數中大型項目。
✅ 一、基礎優化:減少不必要的渲染
1. 使用 v-memo 緩存複雜組件
對頻繁渲染但內容不變的組件使用 v-memo,避免重複 diff。
<!-- [path="/src/components/ExpensiveItem.vue"] -->
<template>
<div v-memo="[item.id, item.status]">
<p>{{ item.name }}</p>
<span v-if="item.status === 'active'">✅</span>
</div>
</template>
<script setup>
defineProps(['item'])
</script>
💡 v-memo 僅在依賴數組變化時才重新渲染,極大提升列表性能。
2. 合理使用 key 屬性
確保 v-for 中的 key 是唯一且穩定的,避免 DOM 重排。
<!-- ❌ 錯誤:使用 index 作為 key -->
<li v-for="(item, index) in list" :key="index">...</li>
<!-- ✅ 正確:使用唯一 id -->
<li v-for="item in list" :key="item.id">...</li>
3. 避免在模板中寫複雜表達式
將複雜邏輯提取到 computed 或 methods 中。
<!-- ❌ 避免 -->
<div>{{ user.name && user.profile && user.profile.avatar ? user.profile.avatar : '/default.png' }}</div>
<!-- ✅ 推薦 -->
<template>
<img :src="avatarUrl" alt="Avatar" />
</template>
<script setup>
const avatarUrl = computed(() => {
return user.value.profile?.avatar || '/default.png'
})
</script>
✅ 二、組件與模塊拆分優化
1. 使用 defineAsyncComponent 異步加載組件
懶加載非首屏組件(如模態框、詳情頁),減小首屏 JS 包體積。
// [path="/src/router/index.js"]
import { defineAsyncComponent } from 'vue'
const LazyModal = defineAsyncComponent(() => import('@/components/LazyModal.vue'))
// 在路由或模板中使用
const routes = [
{
path: '/profile',
component: defineAsyncComponent(() => import('@/views/ProfileView.vue'))
}
]
⚠️ 注意:defineAsyncComponent可搭配loadingComponent和errorComponent提升體驗。
2. 使用 keep-alive 緩存組件狀態
對切換頻繁但內容不變的組件(如 Tab 切換),使用 keep-alive。
<template>
<keep-alive include="UserProfile,Settings">
<router-view />
</keep-alive>
</template>
✅ 優點:組件不被銷燬,保留數據和狀態
⚠️ 注意:避免緩存過多組件,防止內存泄漏
✅ 三、構建與打包優化
1. 使用 vite 替代 webpack(推薦)
Vite 基於 ES Module,支持 按需編譯 和 熱更新快,顯著提升開發體驗。
# 安裝 Vite
npm create vite@latest my-vue-app -- --template vue
# 啓動
npm run dev
2. 啓用代碼分割(Code Splitting)
確保路由和組件自動拆包。
// vite.config.js
export default {
build: {
chunkSizeWarningLimit: 1000, // 警告閾值
rollupOptions: {
output: {
manualChunks: undefined, // 可自定義分包邏輯
}
}
}
}
3. 壓縮資源(生產環境)
使用 vite-plugin-compression 壓縮 JS/CSS。
npm install vite-plugin-compression --save-dev
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import compression from 'vite-plugin-compression'
export default defineConfig({
plugins: [
vue(),
compression({ ext: '.gz' })
]
})
✅ 四、運行時優化
1. 禁用生產環境提示(devtools 和 warning)
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App)
.config.productionTip = false // 禁用提示
.mount('#app')
2. 使用 shallowRef 和 shallowReactive(淺響應)
對嵌套對象/數組,如果不需要深層響應,用 shallowRef。
import { shallowRef } from 'vue'
const data = shallowRef({
list: [1, 2, 3],
meta: { total: 100 }
})
// 修改 list 不觸發更新,僅當 data.value 被替換時才更新
適用於數據結構複雜但不常變動的場景。
3. 使用 markRaw 避免響應式污染
對大型對象或第三方庫對象,避免被 Vue 轉換為響應式。
import { markRaw } from 'vue'
const bigObject = { /* 很大,且不需響應式 */ }
const safeObject = markRaw(bigObject)
// Vue 不會劫持該對象,性能提升明顯
✅ 五、其他高級技巧
| 優化點 | 建議 |
|---|---|
| 圖片懶加載 | 使用 loading="lazy" 或 vue-lazyload |
| 防抖/節流 | 對 input、scroll 等事件使用防抖 |
使用 onBeforeUnmount 清理資源 |
如 setInterval、addEventListener |
減少 watch 使用 |
優先用 computed,避免副作用 |
使用 teleport |
將模態框掛載到 body,避免 DOM 層級過深 |
✅ 六、性能監控工具
- Vue DevTools:查看組件渲染次數、響應式依賴
- Lighthouse(Chrome):檢查首屏加載時間、CLS、FCP
- Web Vitals:關注
LCP,FID,CLS - Chrome Performance Tab:錄製頁面交互,分析耗時
✅ 總結:Vue 性能優化 Checklist
| 項目 | 是否完成 |
|---|---|
使用 v-memo 緩存複雜項 |
✅ |
v-for 使用穩定 key |
✅ |
拆分組件 + defineAsyncComponent |
✅ |
使用 keep-alive 緩存狀態 |
✅ |
| 啓用代碼分割 + Vite 構建 | ✅ |
| 壓縮資源(gzip) | ✅ |
| 禁用生產提示 | ✅ |
合理使用 shallowRef / markRaw |
✅ |
| 圖片/資源懶加載 | ✅ |
| 監控 Lighthouse 指標 | ✅ |
vue-router如何做用户登錄權限等?
在 Vue 3 + Vue Router 項目中實現用户登錄權限控制,通常通過 路由守衞(Navigation Guards) 結合 用户登錄狀態管理(如 Pinia 或 Vuex)來完成。以下是完整的實現方案:
✅ 一、核心思路
- 登錄狀態管理:使用 Pinia 管理用户登錄狀態(如
isLogin、userInfo)。 - 路由守衞:在
router/index.js中使用beforeEach守衞,判斷用户是否登錄,決定是否允許訪問路由。 - 路由元信息(meta):為需要權限保護的路由添加
meta: { requiresAuth: true }。
✅ 二、具體實現步驟
1. 安裝依賴(如未安裝)
npm install vue-router@4
npm install pinia
2. 創建 Pinia Store(管理登錄狀態)
// stores/authStore.js
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({
isLogin: false,
userInfo: null,
}),
actions: {
login(userData) {
this.isLogin = true
this.userInfo = userData
},
logout() {
this.isLogin = false
this.userInfo = null
},
// 檢查是否登錄
checkLogin() {
return this.isLogin
}
}
})
3. 配置路由 + 權限控制
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import Dashboard from '../views/Dashboard.vue'
import NotFound from '../views/NotFound.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { requiresAuth: false }
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true } // 需要登錄才能訪問
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound,
meta: { requiresAuth: false }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守衞
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
// 檢查目標路由是否需要登錄
if (to.meta.requiresAuth) {
if (authStore.isLogin) {
next() // 有登錄狀態,允許訪問
} else {
// 無登錄狀態,跳轉到登錄頁
next({ path: '/login', query: { redirect: to.fullPath } })
}
} else {
// 不需要登錄的頁面,直接放行
next()
}
})
export default router
4. 登錄頁面示例(Login.vue)
<!-- views/Login.vue -->
<template>
<div>
<h2>登錄</h2>
<input v-model="username" placeholder="用户名" />
<button @click="handleLogin">登錄</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore'
const router = useRouter()
const authStore = useAuthStore()
const username = ref('')
const handleLogin = () => {
if (username.value) {
authStore.login({ name: username.value })
const redirect = router.currentRoute.value.query.redirect || '/'
router.push(redirect)
}
}
</script>
5. 保護頁面示例(Dashboard.vue)
<!-- views/Dashboard.vue -->
<template>
<div>
<h2>儀表盤</h2>
<p>歡迎,{{ userInfo.name }}!</p>
<button @click="logout">退出登錄</button>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore'
const router = useRouter()
const authStore = useAuthStore()
const userInfo = computed(() => authStore.userInfo)
const logout = () => {
authStore.logout()
router.push('/login')
}
</script>
✅ 三、進階建議
| 功能 | 建議實現方式 |
|---|---|
| 角色權限控制 | 在 meta 中添加 roles: ['admin', 'user'],在守衞中判斷用户角色 |
| 動態路由 | 從後端獲取用户權限,動態添加路由(需配合 router.addRoute) |
| 刷新後狀態保持 | 使用 localStorage 持久化登錄狀態 |
| Token 機制 | 登錄成功後保存 token,在請求頭中攜帶,後端驗證 |
✅ 四、路徑示例(根據你的環境)
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/router/index.js"]
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/stores/authStore.js"]
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/views/Login.vue"]
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/views/Dashboard.vue"]
✅ 總結
| 技術點 | 説明 |
|---|---|
beforeEach |
全局路由守衞,用於權限攔截 |
meta.requiresAuth |
標記路由是否需要登錄 |
Pinia |
管理登錄狀態,跨組件共享 |
router.push |
跳轉時攜帶 redirect 參數,登錄後跳回原頁面 |
vue-router如何做歷史返回提示?
在 Vue Router 中實現“歷史返回提示”(例如:用户點擊返回時,如果當前頁面有未保存的修改,提示用户是否確認離開),可以通過以下方式實現:
✅ 一、使用 beforeRouteLeave 守衞(推薦)
這是 Vue Router 提供的路由守衞,用於在組件即將離開時攔截導航。
示例代碼(在 Vue 組件中):
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/views/MyForm.vue"]
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
// 假設你有一個表單數據,用於判斷是否修改過
const formData = ref({
title: '',
content: ''
})
// 檢查是否有未保存的數據
const hasUnsavedChanges = computed(() => {
return formData.value.title !== '' || formData.value.content !== ''
})
// 路由離開前的守衞
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const confirmLeave = window.confirm(
'你有未保存的修改,確定要離開嗎?'
)
next(confirmLeave) // true: 允許離開,false: 阻止離開
} else {
next() // 無修改,直接離開
}
})
</script>
<template>
<div>
<h2>編輯表單</h2>
<input v-model="formData.title" placeholder="標題" />
<textarea v-model="formData.content" placeholder="內容" />
</div>
</template>
✅ 二、更高級的提示(如使用 Modal 組件)
如果你使用的是 Element Plus、Naive UI 或 Ant Design Vue 等 UI 框架,可以用組件封裝提示:
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/views/MyForm.vue"]
<script setup>
import { ref } from 'vue'
import { ElMessageBox } from 'element-plus'
const formData = ref({ title: '', content: '' })
const hasUnsavedChanges = computed(() => {
return formData.value.title !== '' || formData.value.content !== ''
})
onBeforeRouteLeave(async (to, from, next) => {
if (hasUnsavedChanges.value) {
try {
await ElMessageBox.confirm(
'你有未保存的修改,確定要離開嗎?',
'提示',
{
confirmButtonText: '確認離開',
cancelButtonText: '取消',
type: 'warning'
}
)
next() // 確認後離開
} catch {
next(false) // 攔截導航,不離開
}
} else {
next()
}
})
</script>
✅ 三、全局路由守衞(可選)
如果你希望在所有頁面都統一處理未保存提示,可以使用全局守衞 beforeRouteLeave 的全局邏輯,但一般不推薦,因為每個頁面的邏輯不同,建議還是在組件內處理。
🔔 注意事項
onBeforeRouteLeave是 組件內守衞,只能在<script setup>或setup()中使用。next(false)會阻止導航,next()允許。window.confirm()是同步的,會阻塞頁面,適合簡單提示。- 使用
ElMessageBox等組件時,await是必須的,否則無法正確攔截。
✅ 總結
| 方式 | 適用場景 |
|---|---|
onBeforeRouteLeave + confirm() |
簡單提示 |
onBeforeRouteLeave + ElMessageBox |
專業 UI 提示 |
| 全局守衞 | 全局統一邏輯(不推薦通用) |
如你使用的是 Vue 2,語法略有不同:
export default {
beforeRouteLeave(to, from, next) {
if (hasUnsavedChanges) {
if (confirm('有未保存的修改,確定離開?')) {
next()
} else {
next(false)
}
} else {
next()
}
}
}
移動端調試方式:測試環境調試/生產代碼debug
在移動端開發中,測試環境調試與生產代碼調試(Production Debug) 是兩個不同場景下的調試策略,需根據安全、性能和可維護性進行合理設計。以下是詳細的調試方式説明及最佳實踐。
✅ 一、測試環境調試(Test Environment Debug)
適用於開發、測試階段,允許開啓完整的調試功能。
1. 調試方式
✅ 1.1 使用開發者工具(DevTools)
- React Native: 使用
adb reverse tcp:8081 tcp:8081+ 開啓 Metro Bundler,通過Cmd+D打開開發者菜單。 - Flutter: 使用
flutter run --enable-software-rendering,通過flutter doctor檢查連接,使用DevTools進行性能分析。 - 原生 Android: 使用 Android Studio 的 Logcat + Debug 模式(
debuggable=true)。 - 原生 iOS: 使用 Xcode 調試器(LLDB)、Console 日誌、
NSLog或print()輸出。
✅ 1.2 啓用調試標誌(Debug Flag)
在 BuildConfig 或 BuildSettings 中啓用 debug 模式:
// Android: build.gradle
android {
buildTypes {
debug {
debuggable true
minifyEnabled false
applicationIdSuffix ".debug"
}
}
}
// iOS: Info.plist
<key>DEBUG</key>
<true/>
✅ 1.3 開啓日誌輸出
// Kotlin/Android
if (BuildConfig.DEBUG) {
Log.d("MyApp", "Debug message")
}
// Swift/iOS
#if DEBUG
print("Debug: This is a debug message")
#endif
✅ 1.4 使用本地 Mock 數據
- 通過配置文件或
BuildConfig.DEBUG切換到 Mock 數據源。 - 避免調用真實 API,提升測試效率。
✅ 二、生產代碼調試(Production Debug)
生產環境嚴禁開啓完整調試功能,但可通過安全、可控的方式進行問題排查。
1. 調試方式(安全可控)
✅ 1.1 日誌分級與過濾
- 僅輸出
WARN和ERROR級別日誌,避免敏感信息泄露。 - 使用日誌上報服務(如 Sentry、Bugly、Firebase Crashlytics)。
// Android 示例:僅在非 debug 環境上報 error
if (!BuildConfig.DEBUG) {
Crashlytics.logException(e);
}
✅ 1.2 使用“Debug Build”但不暴露調試接口
- 發佈版本中保持
debuggable=false,禁止遠程調試。 - 可通過 內部測試渠道(如 TestFlight、Google Play Internal Testing)發佈帶調試能力的版本。
✅ 1.3 通過埋點/監控系統排查問題
- 使用埋點 SDK(如神策、GrowingIO)記錄用户行為。
- 通過鏈路追蹤(如 OpenTelemetry)分析請求鏈路。
✅ 1.4 開啓“開發者模式”開關(非默認開啓)
- 在 App 內提供一個隱藏的“開發者模式”入口(如連點 7 次 logo)。
- 該模式僅對內部人員開放,可臨時啓用日誌、性能監控等。
// 示例:隱藏的調試開關
if (isDeveloperModeEnabled()) {
Log.d("Debug", "Developer mode active");
enablePerformanceMonitor();
}
✅ 1.5 使用 APM 工具(Application Performance Monitoring)
- 推薦工具:
- Sentry(支持 JS/Flutter/原生)
- Bugly(騰訊,Android/iOS)
- Firebase Performance Monitoring
- New Relic / Datadog
⚠️ 注意:APM 工具需配置為僅在生產環境上報錯誤和性能數據,且不收集用户隱私數據。
✅ 三、安全建議(生產環境必須遵守)
| 項目 | 測試環境 | 生產環境 |
|---|---|---|
debuggable |
true |
false |
minifyEnabled |
false |
true |
proguard / R8 |
關閉 | 開啓 |
Log.d / NSLog |
允許 | 限制或移除 |
| 遠程調試(如 Chrome DevTools) | 允許 | 禁止 |
| 崩潰上報 | 允許 | 必須開啓 |
| 用户數據日誌 | 禁止 | 必須脱敏 |
✅ 四、推薦架構設計
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/java/com/pingan/DebugConfig.java"]
public class DebugConfig {
// 僅在測試環境啓用
public static boolean isDebug() {
return BuildConfig.DEBUG || isInternalBuild();
}
// 可通過 BuildConfig 或配置文件控制
private static boolean isInternalBuild() {
return "internal".equals(BuildConfig.BUILD_TYPE);
}
// 安全日誌輸出
public static void log(String tag, String msg) {
if (isDebug()) {
android.util.Log.d(tag, msg);
}
}
// 生產環境禁止打印敏感信息
public static void safeLog(String tag, String msg) {
if (isDebug()) {
android.util.Log.d(tag, msg);
} else {
// 上報到監控系統
Crashlytics.log(tag + ": " + maskSensitiveInfo(msg));
}
}
private static String maskSensitiveInfo(String msg) {
// 實現敏感信息脱敏邏輯
return msg.replaceAll("\\d{4}-\\d{4}-\\d{4}-\\d{4}", "XXXX-XXXX-XXXX-XXXX");
}
}
✅ 五、總結
| 場景 | 推薦做法 |
|---|---|
| 開發/測試環境 | 開啓完整調試、日誌、遠程調試、Mock 數據 |
| 生產環境 | 關閉調試、啓用日誌上報、使用 APM、限制日誌級別、隱藏調試入口 |
💡 核心原則:
測試環境要“方便調試”,生產環境要“安全可控”。
代理使用:fiddler/charles、node server 代理
在移動端開發中,測試環境調試與生產代碼調試(Production Debug) 是兩個不同場景下的調試策略,需根據安全、性能和可維護性進行合理設計。以下是詳細的調試方式説明及最佳實踐。
✅ 一、測試環境調試(Test Environment Debug)
適用於開發、測試階段,允許開啓完整的調試功能。
1. 調試方式
✅ 1.1 使用開發者工具(DevTools)
- React Native: 使用
adb reverse tcp:8081 tcp:8081+ 開啓 Metro Bundler,通過Cmd+D打開開發者菜單。 - Flutter: 使用
flutter run --enable-software-rendering,通過flutter doctor檢查連接,使用DevTools進行性能分析。 - 原生 Android: 使用 Android Studio 的 Logcat + Debug 模式(
debuggable=true)。 - 原生 iOS: 使用 Xcode 調試器(LLDB)、Console 日誌、
NSLog或print()輸出。
✅ 1.2 啓用調試標誌(Debug Flag)
在 BuildConfig 或 BuildSettings 中啓用 debug 模式:
// Android: build.gradle
android {
buildTypes {
debug {
debuggable true
minifyEnabled false
applicationIdSuffix ".debug"
}
}
}
// iOS: Info.plist
<key>DEBUG</key>
<true/>
✅ 1.3 開啓日誌輸出
// Kotlin/Android
if (BuildConfig.DEBUG) {
Log.d("MyApp", "Debug message")
}
// Swift/iOS
#if DEBUG
print("Debug: This is a debug message")
#endif
✅ 1.4 使用本地 Mock 數據
- 通過配置文件或
BuildConfig.DEBUG切換到 Mock 數據源。 - 避免調用真實 API,提升測試效率。
✅ 二、生產代碼調試(Production Debug)
生產環境嚴禁開啓完整調試功能,但可通過安全、可控的方式進行問題排查。
1. 調試方式(安全可控)
✅ 1.1 日誌分級與過濾
- 僅輸出
WARN和ERROR級別日誌,避免敏感信息泄露。 - 使用日誌上報服務(如 Sentry、Bugly、Firebase Crashlytics)。
// Android 示例:僅在非 debug 環境上報 error
if (!BuildConfig.DEBUG) {
Crashlytics.logException(e);
}
✅ 1.2 使用“Debug Build”但不暴露調試接口
- 發佈版本中保持
debuggable=false,禁止遠程調試。 - 可通過 內部測試渠道(如 TestFlight、Google Play Internal Testing)發佈帶調試能力的版本。
✅ 1.3 通過埋點/監控系統排查問題
- 使用埋點 SDK(如神策、GrowingIO)記錄用户行為。
- 通過鏈路追蹤(如 OpenTelemetry)分析請求鏈路。
✅ 1.4 開啓“開發者模式”開關(非默認開啓)
- 在 App 內提供一個隱藏的“開發者模式”入口(如連點 7 次 logo)。
- 該模式僅對內部人員開放,可臨時啓用日誌、性能監控等。
// 示例:隱藏的調試開關
if (isDeveloperModeEnabled()) {
Log.d("Debug", "Developer mode active");
enablePerformanceMonitor();
}
✅ 1.5 使用 APM 工具(Application Performance Monitoring)
- 推薦工具:
- Sentry(支持 JS/Flutter/原生)
- Bugly(騰訊,Android/iOS)
- Firebase Performance Monitoring
- New Relic / Datadog
⚠️ 注意:APM 工具需配置為僅在生產環境上報錯誤和性能數據,且不收集用户隱私數據。
✅ 三、安全建議(生產環境必須遵守)
| 項目 | 測試環境 | 生產環境 |
|---|---|---|
debuggable |
true |
false |
minifyEnabled |
false |
true |
proguard / R8 |
關閉 | 開啓 |
Log.d / NSLog |
允許 | 限制或移除 |
| 遠程調試(如 Chrome DevTools) | 允許 | 禁止 |
| 崩潰上報 | 允許 | 必須開啓 |
| 用户數據日誌 | 禁止 | 必須脱敏 |
✅ 四、推薦架構設計
// [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/java/com/pingan/DebugConfig.java"]
public class DebugConfig {
// 僅在測試環境啓用
public static boolean isDebug() {
return BuildConfig.DEBUG || isInternalBuild();
}
// 可通過 BuildConfig 或配置文件控制
private static boolean isInternalBuild() {
return "internal".equals(BuildConfig.BUILD_TYPE);
}
// 安全日誌輸出
public static void log(String tag, String msg) {
if (isDebug()) {
android.util.Log.d(tag, msg);
}
}
// 生產環境禁止打印敏感信息
public static void safeLog(String tag, String msg) {
if (isDebug()) {
android.util.Log.d(tag, msg);
} else {
// 上報到監控系統
Crashlytics.log(tag + ": " + maskSensitiveInfo(msg));
}
}
private static String maskSensitiveInfo(String msg) {
// 實現敏感信息脱敏邏輯
return msg.replaceAll("\\d{4}-\\d{4}-\\d{4}-\\d{4}", "XXXX-XXXX-XXXX-XXXX");
}
}
✅ 五、總結
| 場景 | 推薦做法 |
|---|---|
| 開發/測試環境 | 開啓完整調試、日誌、遠程調試、Mock 數據 |
| 生產環境 | 關閉調試、啓用日誌上報、使用 APM、限制日誌級別、隱藏調試入口 |
💡 核心原則:
測試環境要“方便調試”,生產環境要“安全可控”。
移動端開發碰到過哪些兼容性問題,如何處理的,列舉1~2個
在Web前端移動端開發中,兼容性問題是常見挑戰,尤其在不同操作系統(iOS、Android)、瀏覽器版本、設備屏幕尺寸和交互方式差異較大的情況下。以下是兩個典型的兼容性問題及對應的解決方案:
1. iOS Safari 中 position: fixed 失效或滾動異常
問題描述:
在 iOS Safari 瀏覽器中,使用 position: fixed 定位的元素(如懸浮按鈕、頂部導航欄)在頁面滾動時可能出現位置錯亂、不跟隨視口移動,甚至“消失”的情況。
原因:
iOS Safari 對 fixed 定位的實現與標準存在差異,尤其在頁面縮放、鍵盤彈出/收起、或使用 transform/webkit- 相關屬性時更容易出問題。
解決方案:
- 使用
position: -webkit-sticky作為備選:
`css
.fixed-element {
position: -webkit-sticky;
position: sticky;
top: 0;
}
`
雖然sticky不完全等同於fixed,但在很多場景下可替代。 - 強制啓用硬件加速 + 避免 transform 影響:
`css
.fixed-element {
position: fixed;
top: 0;
left: 0;
transform: translateZ(0); / 觸發 GPU 加速 /
-webkit-transform: translateZ(0);
}
` - 監聽
resize和scroll事件,動態調整位置(適用於複雜場景):
`javascript
window.addEventListener('resize', () => {
const element = document.querySelector('.fixed-element');
element.style.top = '0';
});
`
✅ 建議:在 iOS 上儘量避免在fixed元素上使用transform或z-index過高,且使用transform: translateZ(0)強制啓用硬件加速。
2. Android Chrome 中 input 輸入框聚焦時頁面被頂起(鍵盤彈出導致頁面錯位)
問題描述:
當用户點擊輸入框(如 input 或 textarea)時,軟鍵盤彈出,頁面被向上推,導致佈局錯亂、滾動位置異常,甚至遮擋關鍵內容。
原因:
Android 瀏覽器在彈出鍵盤時,會自動調整視口高度,但部分頁面未正確處理 viewport 或 height: 100vh 的行為。
解決方案:
- 使用
viewport-fit+env()函數適配安全區域:
`css
html, body {
height: 100vh;
margin: 0;
padding: 0;
}
.container {
height: 100vh;
overflow: hidden;
/ 適配劉海屏和鍵盤彈出 /
padding-bottom: env(safe-area-inset-bottom);
}
`
- 動態監聽
focus和blur事件,調整頁面滾動位置:
`javascript
const input = document.querySelector('input');
input.addEventListener('focus', () => {
// 保存當前滾動位置
const scrollY = window.scrollY;
document.body.style.position = 'fixed';
document.body.style.top = -${scrollY}px;
});
input.addEventListener('blur', () => {
const scrollY = parseInt(document.body.style.top) * -1;
document.body.style.position = '';
document.body.style.top = '';
window.scrollTo(0, scrollY);
});
`
- 使用
@media (orientation: portrait)或window.visualViewport監聽鍵盤彈出:
`javascript
if ('visualViewport' in window) {
window.visualViewport.addEventListener('resize', () => {
const height = window.visualViewport.height;
const viewportHeight = window.innerHeight;
if (height < viewportHeight * 0.8) {
// 鍵盤彈出,可做適配處理
}
});
}
`
✅ 建議:優先使用viewport-fit+env()+position: fixed+ 動態滾動控制,實現良好的鍵盤適配體驗。
總結
| 問題 | 核心原因 | 推薦方案 |
|---|---|---|
iOS fixed 失效 |
Safari 渲染差異 | transform: translateZ(0) + position: sticky 備選 |
| Android 鍵盤彈出頁面錯位 | 視口高度變化 + vh 問題 |
env(safe-area-inset-bottom) + 動態 scrollY 控制 |
這些方案已在多個實際項目中驗證有效,建議在移動端項目中統一引入兼容性處理模塊,提升用户體驗一致性。
移動端響應式佈局:rem、flex佈局、media 查詢
在移動端實現響應式佈局,rem、flex 佈局和 media query 是三大核心手段。它們可以協同工作,構建出適配多種屏幕尺寸的靈活界面。
下面我將分別介紹這三種技術,並給出一個完整的實戰示例代碼。
✅ 一、rem 佈局(根字體單位)
rem 是相對於根元素(html)字體大小的單位。通過動態設置 html 的 font-size,可以實現整體縮放。
🎯 作用:
- 實現頁面整體按比例縮放,適配不同屏幕。
- 與
viewport配合使用,實現“設計稿像素 → rem”映射。
✅ 示例:設置 rem 基準
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rem 佈局</title>
<style>
/* 設置默認字體大小 */
html {
font-size: 16px; /* 設計稿以 750px 為基準,1rem = 75px,即 100px = 1.33rem */
}
/* 響應式設置 font-size */
@media (max-width: 375px) {
html {
font-size: 14px;
}
}
@media (min-width: 750px) {
html {
font-size: 18px;
}
}
body {
margin: 0;
padding: 20px;
font-family: Arial, sans-serif;
}
.container {
width: 100%;
background: #f0f0f0;
padding: 20px;
border-radius: 8px;
}
.item {
height: 50px;
background: #007bff;
color: white;
text-align: center;
line-height: 50px;
margin-bottom: 10px;
font-size: 1rem; /* 1rem = html 的 font-size */
}
</style>
</head>
<body>
<div class="container">
<div class="item">項目 1</div>
<div class="item">項目 2</div>
<div class="item">項目 3</div>
</div>
</body>
</html>
🔧 Tips:
- 常見方案:
設計稿 750px→1rem = 75px→1px = 1/75 rem。- 可通過 JS 動態設置
document.documentElement.style.fontSize(如document.documentElement.style.fontSize = (window.innerWidth / 7.5) + 'px')實現精確適配。
✅ 二、flex 佈局(彈性佈局)
flex 佈局是響應式佈局的“骨骼”,用於靈活控制子元素排列、對齊、伸縮。
🎯 作用:
- 實現水平/垂直居中
- 自適應容器內元素分佈
- 多行、多列布局
✅ 示例:flex 佈局實現響應式卡片
/* [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/resources/static/css/flex-layout.css"] */
.flex-container {
display: flex;
flex-wrap: wrap; /* 允許換行 */
gap: 16px;
padding: 20px;
justify-content: center;
}
.flex-item {
flex: 1 1 200px; /* grow, shrink, basis */
min-width: 200px;
height: 100px;
background: #00c853;
color: white;
text-align: center;
line-height: 100px;
border-radius: 8px;
font-size: 1.2rem;
}
/* 移動端:更緊湊 */
@media (max-width: 500px) {
.flex-container {
gap: 10px;
}
.flex-item {
height: 80px;
line-height: 80px;
font-size: 1rem;
}
}
<div class="flex-container">
<div class="flex-item">卡片 1</div>
<div class="flex-item">卡片 2</div>
<div class="flex-item">卡片 3</div>
</div>
✅ 三、media query(媒體查詢)
media query 是實現響應式佈局的“開關”,根據設備特性(如屏幕寬度)應用不同樣式。
🎯 作用:
- 針對不同設備(手機、平板、桌面)定義不同樣式
- 實現斷點控制
✅ 示例:常見斷點設置
/* [path="/Users/hejuan903/Documents/BackUp/program/paas-plus-web/src/main/resources/static/css/media-query.css"] */
/* 超小屏(手機) */
@media (max-width: 480px) {
body {
font-size: 14px;
}
.header {
font-size: 1.2rem;
}
}
/* 小屏(平板) */
@media (min-width: 481px) and (max-width: 768px) {
body {
font-size: 16px;
}
.flex-container {
flex-direction: column;
}
}
/* 中屏及以上(桌面) */
@media (min-width: 769px) {
body {
font-size: 18px;
}
.flex-container {
flex-direction: row;
}
}
✅ 綜合實戰:完整響應式佈局示例
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>移動端響應式佈局</title>
<!-- 引入 CSS 文件 -->
<link rel="stylesheet" href="/css/rem.css" />
<link rel="stylesheet" href="/css/flex-layout.css" />
<link rel="stylesheet" href="/css/media-query.css" />
<!-- 動態設置 rem 基準(JS) -->
<script>
// 根據屏幕寬度動態設置 html font-size
function setRem() {
const width = document.documentElement.clientWidth;
const baseWidth = 750; // 設計稿寬度
const fontSize = (width / baseWidth) * 16; // 16px 為基準
document.documentElement.style.fontSize = fontSize + 'px';
}
setRem();
window.addEventListener('resize', setRem);
</script>
</head>
<body>
<header class="header">響應式頭部</header>
<div class="flex-container">
<div class="flex-item">卡片1</div>
<div class="flex-item">卡片2</div>
<div class="flex-item">卡片3</div>
</div>
</body>
</html>
✅ 總結:三者協同策略
| 技術 | 作用 | 適配策略 |
|---|---|---|
rem |
整體縮放單位 | 動態設置 html 的 font-size |
flex |
彈性佈局控制 | 實現自適應排列與對齊 |
media query |
斷點控制 | 根據屏幕寬度切換佈局邏輯 |
✅ 推薦開發流程
- 設計稿以
750px為標準(如支付寶、微信等)。 - 使用 JS 動態計算
rem基準(1rem = 1/75 vw)。 - 用
flex佈局構建容器結構。 - 用
media query設置關鍵斷點(如 375px、500px、768px、1024px)。 - 測試不同設備(iPhone、Android、iPad)。
瀏覽器緩存機制
瀏覽器緩存機制是 Web 性能優化中的核心機制之一,它通過在客户端(瀏覽器)本地存儲資源(如 HTML、CSS、JS、圖片等),減少重複請求服務器的次數,從而提升頁面加載速度、降低網絡流量和服務器壓力。
一、瀏覽器緩存的類型
瀏覽器緩存主要分為以下幾種類型:
1. 強緩存(Strong Cache)
- 特點:瀏覽器直接從本地緩存讀取資源,不與服務器通信。
- 失效機制:通過
Expires或Cache-Control控制。 - 關鍵響應頭:
-Cache-Control: max-age=3600→ 資源在 1 小時內有效
-Expires: Wed, 20 Nov 2025 15:03:09 GMT→ 過期時間
✅ 優點:最快,完全繞過服務器
❌ 缺點:無法感知資源是否更新
2. 協商緩存(Conditional Cache / Weak Cache)
- 特點:瀏覽器向服務器發送請求,詢問資源是否更新,服務器判斷後決定是否返回新資源。
- 關鍵響應頭:
-ETag(實體標籤,由服務器生成的唯一標識)
-Last-Modified(最後修改時間)
✅ 優點:能感知資源變更,保證數據一致性
❌ 缺點:需要一次 HTTP 請求(但返回 304,無實體 body)
二、緩存工作流程圖解
+-------------------+
| 用户訪問 |
+-------------------+
↓
[檢查是否有緩存?]
↓ 是
+-------------------+
| 強緩存有效? |
+-------------------+
↓ 是
+-------------------+
| 從本地緩存讀取 |
+-------------------+
↓ 否
+-------------------+
| 發送請求給服務器 |
+-------------------+
↓
[服務器返回 304 Not Modified?]
↓ 是
+-------------------+
| 從本地緩存讀取 |
+-------------------+
↓ 否
+-------------------+
| 服務器返回新資源 |
+-------------------+
三、緩存頭詳解
| 頭字段 | 説明 | 是否參與強緩存 | 是否參與協商緩存 |
|---|---|---|---|
Cache-Control |
主要控制緩存行為,支持 max-age, no-cache, no-store, public, private |
✅ 是 | ✅ 是(部分) |
Expires |
HTTP/1.0,指定過期時間 | ✅ 是 | ❌ 否 |
ETag |
實體標籤,用於協商緩存 | ❌ 否 | ✅ 是 |
Last-Modified |
最後修改時間,用於協商緩存 | ❌ 否 | ✅ 是 |
常見 Cache-Control 指令:
max-age=3600:資源在 3600 秒內有效(強緩存)no-cache:必須向服務器驗證(協商緩存,不走強緩存)no-store:禁止緩存(不存本地)public:可被任何中間代理緩存private:僅客户端緩存,不可被代理緩存
四、緩存策略建議(實戰)
| 資源類型 | 推薦緩存策略 |
|---|---|
| 靜態資源(JS/CSS/圖片) | Cache-Control: public, max-age=31536000, immutable(一年,不可變) |
| HTML 頁面 | Cache-Control: no-cache, must-revalidate(必須驗證) |
| API 接口 | Cache-Control: no-cache 或 max-age=60(短時緩存) |
| 動態內容 | Cache-Control: no-store(禁止緩存) |
💡immutable:表示資源不會改變,可長期緩存(配合max-age使用)
五、開發者工具中的緩存查看
在 Chrome DevTools 中:
- 打開 Network 面板
- 刷新頁面
- 查看某個請求的 “Size” 列:
-from disk cache:從磁盤緩存讀取(強緩存)
-from memory cache:從內存緩存讀取(強緩存)
-304 Not Modified:協商緩存命中
-200 OK:未命中緩存,服務器返回新數據
六、常見問題與解決
Q1:緩存導致前端更新不生效?
- 原因:JS/CSS 被長期緩存,新版本未更新
- 解決方案:
- 使用 版本號或哈希:app.abc123.js
- 構建時加hash或chunkhash(Webpack/Vite)
- 服務端配置Cache-Control: max-age=0, no-cache
Q2:為什麼 Cache-Control: max-age=0 仍緩存?
- 可能是
no-cache被誤用,或中間代理緩存了。 - 建議使用
Cache-Control: no-cache, must-revalidate明確要求驗證。
七、補充:Service Worker 緩存(PWA)
現代 Web 應用可通過 Service Worker 實現更靈活的緩存控制,支持離線訪問、精準緩存策略、緩存優先等模式。
示例(JavaScript):
// sw.js
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
服務端需註冊 Service Worker,實現高級緩存邏輯。
總結
| 機制 | 是否請求服務器 | 優點 | 適用場景 |
|---|---|---|---|
| 強緩存(max-age) | ❌ 否 | 極快 | 靜態資源 |
| 協商緩存(ETag/Last-Modified) | ✅ 是(但返回 304) | 保證一致性 | 動態內容 |
| Service Worker | ✅ 可自定義 | 靈活、離線可用 | PWA 應用 |
Http 請求頭、返回頭常見字段及其含義
瀏覽器緩存機制是 Web 性能優化中的核心機制之一,它通過在客户端(瀏覽器)本地存儲資源(如 HTML、CSS、JS、圖片等),減少重複請求服務器的次數,從而提升頁面加載速度、降低網絡流量和服務器壓力。
一、瀏覽器緩存的類型
瀏覽器緩存主要分為以下幾種類型:
1. 強緩存(Strong Cache)
- 特點:瀏覽器直接從本地緩存讀取資源,不與服務器通信。
- 失效機制:通過
Expires或Cache-Control控制。 - 關鍵響應頭:
-Cache-Control: max-age=3600→ 資源在 1 小時內有效
-Expires: Wed, 20 Nov 2025 15:03:09 GMT→ 過期時間
✅ 優點:最快,完全繞過服務器
❌ 缺點:無法感知資源是否更新
2. 協商緩存(Conditional Cache / Weak Cache)
- 特點:瀏覽器向服務器發送請求,詢問資源是否更新,服務器判斷後決定是否返回新資源。
- 關鍵響應頭:
-ETag(實體標籤,由服務器生成的唯一標識)
-Last-Modified(最後修改時間)
✅ 優點:能感知資源變更,保證數據一致性
❌ 缺點:需要一次 HTTP 請求(但返回 304,無實體 body)
二、緩存工作流程圖解
+-------------------+
| 用户訪問 |
+-------------------+
↓
[檢查是否有緩存?]
↓ 是
+-------------------+
| 強緩存有效? |
+-------------------+
↓ 是
+-------------------+
| 從本地緩存讀取 |
+-------------------+
↓ 否
+-------------------+
| 發送請求給服務器 |
+-------------------+
↓
[服務器返回 304 Not Modified?]
↓ 是
+-------------------+
| 從本地緩存讀取 |
+-------------------+
↓ 否
+-------------------+
| 服務器返回新資源 |
+-------------------+
三、緩存頭詳解
| 頭字段 | 説明 | 是否參與強緩存 | 是否參與協商緩存 |
|---|---|---|---|
Cache-Control |
主要控制緩存行為,支持 max-age, no-cache, no-store, public, private |
✅ 是 | ✅ 是(部分) |
Expires |
HTTP/1.0,指定過期時間 | ✅ 是 | ❌ 否 |
ETag |
實體標籤,用於協商緩存 | ❌ 否 | ✅ 是 |
Last-Modified |
最後修改時間,用於協商緩存 | ❌ 否 | ✅ 是 |
常見 Cache-Control 指令:
max-age=3600:資源在 3600 秒內有效(強緩存)no-cache:必須向服務器驗證(協商緩存,不走強緩存)no-store:禁止緩存(不存本地)public:可被任何中間代理緩存private:僅客户端緩存,不可被代理緩存
四、緩存策略建議(實戰)
| 資源類型 | 推薦緩存策略 |
|---|---|
| 靜態資源(JS/CSS/圖片) | Cache-Control: public, max-age=31536000, immutable(一年,不可變) |
| HTML 頁面 | Cache-Control: no-cache, must-revalidate(必須驗證) |
| API 接口 | Cache-Control: no-cache 或 max-age=60(短時緩存) |
| 動態內容 | Cache-Control: no-store(禁止緩存) |
💡immutable:表示資源不會改變,可長期緩存(配合max-age使用)
五、開發者工具中的緩存查看
在 Chrome DevTools 中:
- 打開 Network 面板
- 刷新頁面
- 查看某個請求的 “Size” 列:
-from disk cache:從磁盤緩存讀取(強緩存)
-from memory cache:從內存緩存讀取(強緩存)
-304 Not Modified:協商緩存命中
-200 OK:未命中緩存,服務器返回新數據
六、常見問題與解決
Q1:緩存導致前端更新不生效?
- 原因:JS/CSS 被長期緩存,新版本未更新
- 解決方案:
- 使用 版本號或哈希:app.abc123.js
- 構建時加hash或chunkhash(Webpack/Vite)
- 服務端配置Cache-Control: max-age=0, no-cache
Q2:為什麼 Cache-Control: max-age=0 仍緩存?
- 可能是
no-cache被誤用,或中間代理緩存了。 - 建議使用
Cache-Control: no-cache, must-revalidate明確要求驗證。
七、補充:Service Worker 緩存(PWA)
現代 Web 應用可通過 Service Worker 實現更靈活的緩存控制,支持離線訪問、精準緩存策略、緩存優先等模式。
示例(JavaScript):
// sw.js
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});
服務端需註冊 Service Worker,實現高級緩存邏輯。
總結
| 機制 | 是否請求服務器 | 優點 | 適用場景 |
|---|---|---|---|
| 強緩存(max-age) | ❌ 否 | 極快 | 靜態資源 |
| 協商緩存(ETag/Last-Modified) | ✅ 是(但返回 304) | 保證一致性 | 動態內容 |
| Service Worker | ✅ 可自定義 | 靈活、離線可用 | PWA 應用 |