1. 請簡述你對 react 的理解
React 是 Facebook 推出的用於構建用户界面的聲明式、組件化 JavaScript 庫,核心思想是“組件化”與“數據驅動視圖”,不直接操作真實 DOM,而是通過虛擬 DOM 優化渲染性能。
核心特點:① 聲明式編程:只需描述 UI 的目標狀態,React 自動處理 DOM 更新邏輯,無需關注過程;② 組件化:將 UI 拆分為獨立可複用的組件,組件內部維護自身狀態與邏輯,便於開發和維護;③ 單向數據流:數據從父組件通過 props 向下傳遞,子組件無法直接修改 props,狀態變更需通過回調函數向上反饋,避免數據混亂;④ 虛擬 DOM 與 Diff 算法:用 JS 對象模擬真實 DOM,通過 Diff 算法對比新舊虛擬 DOM 差異,僅更新變化部分到真實 DOM,減少性能開銷;⑤ 跨平台適配:可通過 React Native 開發原生應用,通過 Next.js 實現服務端渲染(SSR),提升首屏加載速度和 SEO 效果。
2. state 和 props 的區別
| 對比維度 | state(狀態) | props(屬性) |
|---|---|---|
| 數據來源 | 組件內部定義,由組件自身維護 | 父組件傳遞,組件自身無法修改 |
| 可變性 | 可變,通過 setState(類組件)或 useState(函數組件)修改 | 不可變,組件僅能讀取,修改需由父組件更新傳遞 |
| 作用範圍 | 僅作用於當前組件內部 | 可在父組件傳遞給子組件,實現跨組件數據傳遞 |
| 初始化方式 | 類組件:constructor 中初始化;函數組件:useState Hook 初始化 | 組件調用時通過屬性傳入,可設置默認值(defaultProps) |
3. 講下組件之間的數據傳遞
React 組件間數據傳遞需根據組件關係選擇對應方式,核心場景及方案如下:
- 父傳子:通過 props 傳遞。父組件調用子組件時,將數據/方法作為屬性傳入,子組件通過 props 接收使用,適用於直接父子關係,簡單高效。
- 子傳父:通過回調函數。父組件傳遞一個回調函數給子組件,子組件觸發該函數時將數據作為參數傳入,父組件在回調中接收數據。
- 跨層級傳遞(爺孫/遠親):① Context API:創建全局上下文,上層組件通過 Provider 提供數據,下層組件通過 Consumer 或 useContext 獲取數據,無需層層透傳;② 狀態管理庫(Redux/MobX/Recoil):適用於大型項目,集中管理全局狀態,任意組件可通過 API 獲取/修改狀態。
- 兄弟組件傳遞:藉助共同父組件中轉。A 組件將數據傳遞給父組件,父組件再通過 props 傳遞給 B 組件;或直接使用 Context/狀態管理庫。
4. vue 與 react 的區別
| 對比維度 | Vue | React |
|---|---|---|
| 核心思想 | 漸進式框架,兼顧聲明式和命令式,更注重易用性 | 聲明式編程,組件化,更注重函數式思想 |
| 模板語法 | 支持 HTML 模板(主流)+ JSX,模板貼近 HTML,學習成本低 | 推薦使用 JSX,將 HTML 融入 JS,靈活性高,適合複雜 UI 邏輯 |
| 狀態管理 | Vue2 用 Vuex,Vue3 用 Pinia(簡化版 Vuex),內置響應式系統 | 無內置狀態管理,需使用 Redux/MobX/Recoil 等第三方庫 |
| 響應式原理 | Vue2:Object.defineProperty 監聽屬性 get/set;Vue3:Proxy 代理整個對象 | 通過 setState/useState 觸發重新渲染,對比虛擬 DOM 差異更新 |
| 組件通信 | props/emit、Provide/Inject、Vuex/Pinia、EventBus | props/回調、Context API、狀態管理庫、HOC |
| 適用場景 | 中小型項目、快速開發、對易用性要求高的場景 | 大型項目、複雜 UI 邏輯、跨平台開發(React Native) |
5. 請簡述虛擬 dom 和 diff 算法
(1)虛擬 DOM(Virtual DOM)
虛擬 DOM 是用 JavaScript 對象模擬真實 DOM 樹的結構,包含節點類型、屬性、子節點等信息(如{ tag: 'div', props: { id: 'app' }, children: [] })。它是對真實 DOM 的抽象描述,不依賴瀏覽器環境,可在內存中高效操作。
核心作用:① 避免直接操作真實 DOM:真實 DOM 操作開銷大,虛擬 DOM 通過內存對象操作替代真實 DOM 操作,減少性能損耗;② 跨平台兼容:虛擬 DOM 可被渲染為不同平台的視圖(如瀏覽器 DOM、React Native 原生組件)。
(2)Diff 算法
Diff 算法是虛擬 DOM 的核心配套算法,用於對比新舊兩棵虛擬 DOM 樹的差異,計算出“最小更新集”,最終只將差異部分更新到真實 DOM,避免全量渲染。
核心設計思路:① 同層比較:只對比同一層級的節點,不跨層級比較(時間複雜度從 O(n³)優化為 O(n));② 節點類型判斷:若節點類型不同,直接銷燬舊節點並創建新節點;若類型相同,對比屬性差異並更新;③ 列表優化:通過 key 標識列表元素唯一性,快速判斷列表元素的新增、刪除、移動。
6. 調用 setState 之後發生了什麼?
setState 是 React 中更新組件狀態的核心方法,調用後發生以下流程:
- 將傳入的狀態更新對象(或函數返回的對象)加入狀態隊列,並非立即修改 this.state(異步更新)。
- React 觸發“重新渲染”調度,將組件標記為“待更新”。
- React 的調和(Reconciliation)過程:生成新的虛擬 DOM 樹,通過 Diff 算法對比新舊虛擬 DOM 的差異(DOM Diff)。
- 將 Diff 算法計算出的差異(最小更新集)應用到真實 DOM 上,完成頁面更新(Commit 階段)。
注意點:① setState 是異步的,若需獲取更新後的 state,可在 setState 的第二個回調參數中獲取,或使用 useEffect 監聽 state 變化;② 連續多次調用 setState 會被合併,推薦使用函數形式(prev => ({ count: prev.count + 1 }))避免合併問題。
7. react 生命週期函數
React 生命週期分為三個核心階段:掛載(Mounting)、更新(Updating)、卸載(Unmounting),React 16.3+ 後推薦使用函數組件 +Hook,類組件生命週期如下:
- 掛載階段(組件創建並插入 DOM):constructor(初始化 state、綁定 this)→ getDerivedStateFromProps(從 props 派生 state)→ render(生成虛擬 DOM)→ componentDidMount(組件掛載完成,可執行 DOM 操作、請求數據)。
- 更新階段(state/props 變化觸發):getDerivedStateFromProps → shouldComponentUpdate(判斷是否需要重新渲染)→ render → getSnapshotBeforeUpdate(更新 DOM 前獲取快照)→ componentDidUpdate(DOM 更新完成,可做後續操作)。
- 卸載階段(組件從 DOM 移除):componentWillUnmount(組件卸載前執行,清理副作用:清除定時器、解綁事件、取消請求等)。
廢棄鈎子:componentWillMount、componentWillReceiveProps、componentWillUpdate,推薦用其他鈎子替代。
8. 為什麼虛擬 dom 會提高性能
虛擬 DOM 提高性能的核心邏輯是“減少真實 DOM 的操作次數和範圍”,具體原因如下:
- 真實 DOM 操作開銷大:真實 DOM 關聯着瀏覽器的佈局、樣式計算等複雜邏輯,頻繁操作會觸發多次重排(Reflow)和重繪(Repaint),性能損耗嚴重;而虛擬 DOM 是內存中的 JavaScript 對象,操作成本極低。
- 批量更新與最小差異更新:虛擬 DOM 通過 Diff 算法對比新舊樹差異,只將變化的部分同步到真實 DOM,而非全量替換。例如,列表中僅修改一個元素的文本,虛擬 DOM 只會更新該元素的文本節點。
- 避免不必要的 DOM 操作:虛擬 DOM 在內存中整合多次數據變化,批量觸發一次真實 DOM 更新。例如,連續修改兩次 state,虛擬 DOM 會合並兩次變化,只執行一次真實 DOM 更新。
9. shouldComponentUpdate 是做什麼的?
shouldComponentUpdate 是 React 類組件的生命週期鈎子函數,用於“判斷組件是否需要重新渲染”,返回布爾值(默認返回 true)。
核心作用:優化性能,避免不必要的重新渲染。當組件的 props 或 state 變化時,React 會先調用 shouldComponentUpdate,若返回 false,會跳過後續的 render、Diff 算法和真實 DOM 更新流程,直接終止本次渲染。
使用場景:當組件的 props/state 變化但不會影響 UI 時,手動返回 false 阻止渲染。純組件(PureComponent)內置了該鈎子的淺比較邏輯,會自動對比 props 和 state 的淺值。
10. react diff 原理
React Diff 算法是對比新舊虛擬 DOM 差異的核心算法,核心目標是高效找出最小更新集,其核心原理可概括為“同層比較、類型判斷、key 優化”三大要點:
- 同層比較(層級遍歷):只對比同一層級的節點,不跨層級比較,降低算法複雜度(時間複雜度從 O(n³)優化為 O(n))。若父節點類型變化,直接銷燬舊父節點及其所有子節點,無需深入子節點對比。
- 節點類型判斷:若節點類型不同(如 div→p),直接銷燬舊節點並創建新節點;若類型相同,對比屬性差異並更新,再遞歸對比子節點。
- 列表優化:通過 key 標識列表元素唯一性,快速判斷列表元素的新增、刪除、移動,避免無 key 時按索引匹配導致的不必要渲染和狀態錯亂。
11. 什麼是受控組件
受控組件是 React 中表單元素的一種處理方式,其值由組件的 state 控制,表單元素的狀態與 React 狀態“雙向綁定”。
核心特點:① 表單元素(input、textarea、select 等)的值由 state 驅動;② 通過 onChange 事件監聽用户輸入,觸發 setState 更新 state,進而更新表單元素的值;③ 組件狀態完全受 React 控制,可方便地對輸入進行驗證、格式化等處理。
示例:<input type="text" value={this.state.value} onChange={(e) => this.setState({ value: e.target.value })} />
非受控組件:值由 DOM 自身維護,通過 ref 獲取 DOM 元素的值,適用於簡單場景(如文件上傳 input[type="file"])。
12. 組件的狀態(state)和屬性(props)之間有什麼不同?
(與第 2 題一致,核心差異如下)
- 數據來源不同:state 是組件內部定義的私有狀態,由組件自身維護;props 是父組件傳遞給子組件的數據,組件自身無法修改。
- 可變性不同:state 是可變的,通過 setState/useState 修改;props 是不可變的,子組件僅能讀取,修改需由父組件更新傳遞。
- 作用範圍不同:state 僅作用於當前組件內部;props 可在父組件傳遞給子組件,實現跨組件數據傳遞。
- 初始化方式不同:state 在組件內部初始化(constructor/useState);props 在組件調用時由父組件傳入,可設置默認值。
13. 調用 super(props)的目的是什麼?
super(props)用於 React 類組件的 constructor 中,核心目的有兩個:
- 調用父類(React.Component)的構造函數,完成父類的初始化工作,確保組件繼承的屬性和方法能正常使用。
- 將 props 傳遞給父類構造函數,使得在 constructor 中可以通過 this.props 訪問到 props(若不傳遞 props,constructor 中 this.props 為 undefined,但 render 等其他生命週期中仍可訪問)。
注意:在 React 類組件中,若定義了 constructor,必須在其中調用 super(),且 super()必須是 constructor 中的第一個語句;若需要在 constructor 中使用 props,必須傳遞 super(props)。
14. react 中構建組件的方式
React 中構建組件主要有三種方式,各有適用場景:
- 函數組件(推薦,React 16.8+):用普通函數定義的組件,接收 props 作為參數,返回 JSX。簡潔輕便,無 this 問題,通過 Hook(useState、useEffect 等)實現狀態管理和生命週期功能。適用於大多數場景。示例:const MyComponent = (props) => <div>{props.name}</div>。
- 類組件:繼承 React.Component 的類,通過 render 方法返回 JSX。可維護自身 state,使用生命週期鈎子。適用於複雜狀態管理和生命週期邏輯的場景(React 16.8 後可被函數組件 +Hook 替代)。示例:class MyComponent extends React.Component { render() { return <div>{this.props.name}</div> } }。
- 純組件(PureComponent):繼承 React.PureComponent 的類組件,內置了 shouldComponentUpdate 的淺比較邏輯,當 props 和 state 淺比較無變化時,不觸發重新渲染。適用於 props 和 state 為簡單類型、無深層嵌套的組件,可優化性能。
15. 什麼是高階組件 HOC
高階組件(Higher-Order Component,HOC)是 React 中複用組件邏輯的高級技巧,本質是一個“函數”,接收一個或多個組件作為參數,返回一個新的增強組件。
核心作用:抽離組件的公共邏輯,實現邏輯複用(如權限控制、數據請求、日誌打印等),不修改原組件,而是通過包裝增強原組件的功能。
實現方式:① 屬性代理(最常用):通過包裹原組件,向原組件傳遞增強的 props;② 反向繼承:繼承原組件,重寫原組件的 render 或生命週期方法。
示例(屬性代理):const withAuth = (WrappedComponent) => { return (props) => { const isLogin = localStorage.getItem('token'); return isLogin ? <WrappedComponent {...props} /> : <Login />; } }; 使用:const AuthComponent = withAuth(MyComponent);
16. 什麼是 hook
Hook 是 React 16.8 推出的新特性,允許在函數組件中使用狀態(state)、生命週期、上下文(Context)等 React 特性,無需編寫類組件。
核心作用:解決類組件的痛點(如 this 指向混亂、生命週期邏輯分散、代碼複用複雜),讓函數組件能實現類組件的所有功能,同時使代碼更簡潔、邏輯更聚合。
常用 Hook:① useState:用於聲明狀態變量,替代類組件的 this.state;② useEffect:處理副作用(如數據請求、DOM 操作),替代 componentDidMount 等生命週期;③ useContext:獲取 Context 中的數據,簡化跨層級數據傳遞;④ useReducer:用於複雜狀態管理;⑤ useRef:獲取 DOM 元素或保存持久化的值。
使用規則:① 只能在函數組件或自定義 Hook 的頂層調用;② 不能在循環、條件判斷或嵌套函數中調用;③ 只能在 React 函數中調用。
17. 無狀態組件的特點
無狀態組件(Stateless Component)早期指“不依賴 state,僅接收 props 並返回 JSX”的函數組件,React 16.8 後 Hook 推出,現在更強調“不維護自身狀態,僅作為 UI 展示”的組件。
核心特點:① 無自身狀態(state),數據完全依賴 props 傳入;② 是純函數:相同的 props 輸入,必然返回相同的 JSX 輸出,無副作用;③ 代碼簡潔:無需編寫類、constructor 等冗餘代碼;④ 性能更優:React 對其渲染優化更好,無需處理類組件的生命週期和狀態管理邏輯;⑤ 易於測試:純函數特性使其測試更簡單。
適用場景:純 UI 展示組件(如按鈕、卡片、列表項)。
18. 三種請求方式的區別(ajax,axios,fetch)
| 對比維度 | AJAX(原生 XHR) | Axios | Fetch |
|---|---|---|---|
| 本質 | 原生 XHR 對象,異步請求基礎 | 基於 XHR 封裝的第三方庫(Promise 風格) | ES6 原生 API(Promise 風格),替代 XHR |
| 語法複雜度 | 繁瑣,需手動處理狀態、事件 | 簡潔,支持鏈式調用和 async/await | 簡潔,原生支持 Promise |
| 功能特性 | 基礎 GET/POST 請求,無擴展功能 | 內置攔截器、自動 JSON 轉換、取消請求、超時設置 | 支持 CORS、Stream 流,無內置 JSON 轉換等功能 |
| 錯誤處理 | 需手動判斷 status 和 readyState | 統一通過.catch()捕獲所有錯誤 | 僅捕獲網絡錯誤,4xx/5xx 視為成功 |
| 兼容性 | 兼容 IE6+ | 依賴 Promise,IE 需 polyfill | 現代瀏覽器支持,IE 不支持 |
19. 什麼是 react 狀態提升
狀態提升是 React 中解決“多個兄弟組件共享狀態”的核心模式,指將多個組件需要共享的狀態“提升”到它們的共同父組件中,由父組件統一管理該狀態,再通過 props 將狀態和修改狀態的方法傳遞給子組件。
核心邏輯:兄弟組件之間無法直接通信,通過共同父組件作為“中介”,實現狀態共享和同步。
適用場景:多個組件需要基於同一狀態進行 UI 渲染,或一個組件的狀態變化需要同步到其他組件(如兩個輸入框聯動)。
20. 什麼是 webpack
Webpack 是一款前端模塊化打包工具,核心作用是“將前端項目中的多個模塊化文件(JS、CSS、圖片、字體等)打包為瀏覽器可識別的靜態資源”,同時提供代碼轉換、優化、分割、熱更新等功能。
核心特點:① 模塊化支持:支持 CommonJS、ES Module 等多種模塊化規範;② 資源處理:通過 Loader 處理非 JS 資源;③ 插件系統:通過 Plugin 實現擴展功能(如代碼壓縮、自動生成 HTML);④ 開發優化:提供開發服務器、熱模塊替換、Source Map 等功能。
21. webpack 的組成
Webpack 的核心組成部分包括 4 個核心模塊:
- 入口(Entry):指定 Webpack 的打包入口文件,即從哪個文件開始分析依賴關係,默認入口為./src/index.js。
- 出口(Output):指定打包輸出目錄和輸出文件命名規則,默認輸出目錄為./dist,默認輸出文件為 main.js。
- 加載器(Loader):處理非 JS 資源(如 CSS、圖片),將其轉換為 Webpack 可識別的模塊,常用 Loader 有 css-loader、babel-loader、file-loader 等。
- 插件(Plugin):擴展 Webpack 功能,解決 Loader 無法處理的問題,常用 Plugin 有 HtmlWebpackPlugin、TerserPlugin 等。
22. webpack 打包原理
Webpack 打包的核心原理是“模塊化分析 → 資源轉換 → 依賴整合 → 輸出靜態資源”,具體流程:
- 初始化與配置解析:讀取 webpack.config.js 配置,初始化打包配置,創建 Compiler 對象。
- 入口文件分析與依賴收集:從 Entry 入口文件開始,解析文件內容,收集依賴關係,構建依賴樹。
- Loader 轉換非 JS 資源:通過 Loader 將非 JS 資源(如 CSS、圖片)轉換為 JS 模塊。
- 模塊整合與代碼生成:將所有轉換後的模塊整合為一個或多個 chunk,生成最終的靜態資源文件,注入模塊化運行時代碼。
- Plugin 干預打包流程:在打包各階段執行擴展功能(如壓縮、生成 HTML)。
23. webpack 的工作過程
Webpack 的工作過程按以下 6 個階段執行:
- 啓動階段:通過命令行或 API 啓動 Webpack,讀取併合並配置,初始化 Compiler 對象,註冊 Plugin。
- 編譯階段:解析模塊路徑,通過 Loader 轉換模塊,解析模塊內容生成 AST,收集依賴關係,構建依賴樹。
- 模塊優化階段:進行代碼分割、Tree-shaking(剔除死代碼)等優化。
- chunk 優化階段:確定 chunk 輸出文件名,注入 Runtime 代碼,生成最終 chunk 資產。
- 輸出階段:將 chunk 資產寫入本地文件系統。
- 完成階段:輸出打包結果,若出錯則終止流程並提示錯誤。
24. 什麼是 typescript?
TypeScript(簡稱 TS)是微軟開發的開源編程語言,是 JavaScript 的超集,核心擴展是“靜態類型系統”。
核心特性:① 靜態類型檢查:編譯時檢查變量類型,提前發現錯誤;② 類型定義:支持基本類型、複雜類型(接口、泛型等);③ 兼容 JavaScript:所有 JS 代碼可直接在 TS 中運行,編譯後生成純 JS;④ 增強 IDE 支持:提供代碼提示、自動補全、重構等功能。
核心作用:提升代碼質量,優化開發體驗,適配大型項目協作。
25. react 中 keys 的作用是什麼?
keys 是 React 中用於標識列表元素唯一性的特殊屬性,核心作用是幫助 React 的 Diff 算法高效識別列表中的元素,優化渲染性能。
具體作用:① 唯一性標識:幫助 React 判斷列表元素是新增、刪除還是移動;② 提高 Diff 效率:通過 key 匹配元素,僅更新變化的元素,減少 DOM 操作;③ 保持元素狀態:確保列表中有狀態組件(如輸入框)的狀態與數據正確關聯,避免排序後狀態錯位。
使用規則:優先使用後端返回的唯一標識(如 id)作為 key,避免使用索引。
26. react 事件處理中如何修改 this 的指向
React 類組件中,事件處理函數的 this 默認是 undefined,需手動綁定,常用方法有 4 種:
- 構造函數中綁定(推薦):在 constructor 中通過 this.handleClick = this.handleClick.bind(this)綁定。
- 事件綁定處使用箭頭函數:<button onClick={(e) => this.handleClick(e)}> 點擊 </button>(可能影響性能)。
- 事件處理函數定義為箭頭函數:handleClick = () => { console.log(this); }(簡潔,無需手動綁定)。
- 事件綁定處使用 bind 綁定:<button onClick={this.handleClick.bind(this)}> 點擊 </button>(可能影響性能)。
函數組件中無 this 問題,直接使用 props 或 state 即可。
27. react 如何實現組件傳值?
(與第 3 題一致,核心方案如下)
- 父傳子:通過 props 傳遞數據/方法。
- 子傳父:通過回調函數傳遞數據。
- 跨層級傳遞:使用 Context API 或狀態管理庫(Redux/MobX)。
- 兄弟組件傳遞:藉助共同父組件中轉,或使用 Context/狀態管理庫。
新增:
1. 用過閉包麼?什麼場景用的?
用過。閉包的核心定義是:函數有權訪問其聲明時所在的詞法作用域,即使函數在該詞法作用域之外執行,本質是“函數 + 函數聲明時的詞法環境”的組合。
常用場景:① 數據私有化/模塊化封裝:通過閉包隱藏內部變量,避免全局污染,僅暴露指定操作方法。例如:const counter = (() => { let count = 0; return { increment: () => count++, getCount: () => count }; })(); ② 延遲執行與狀態保存:如定時器、事件回調中保存外部變量狀態,避免循環中變量污染。例如:for (var i = 0; i < 5; i++) { (function(j) { setTimeout(() => console.log(j), 1000); })(i); } ③ 函數防抖/節流:通過閉包保存定時器 ID 或時間戳,實現狀態持久化,避免頻繁觸發函數。
2. 實現一個深拷貝
深拷貝的核心是複製對象的所有層級(包括嵌套對象/數組),確保新對象與原對象完全獨立,修改新對象不影響原對象。以下是遞歸實現的核心方案(支持常見類型):
function deepClone(target) {
// 處理null和基本數據類型(直接返回,無需拷貝)
if (target === null || typeof target !== 'object') {
return target;
}
// 處理日期類型
if (target instanceof Date) {
return new Date(target);
}
// 處理正則類型
if (target instanceof RegExp) {
return new RegExp(target.source, target.flags);
}
// 處理數組和對象(創建新的容器)
const cloneObj = Array.isArray(target) ? [] : {};
// 遍歷自身屬性(不包含原型鏈屬性)
for (const key in target) {
if (target.hasOwnProperty(key)) {
// 遞歸拷貝子屬性
cloneObj[key] = deepClone(target[key]);
}
}
return cloneObj;
}
補充方案:① 簡易方案:JSON.parse(JSON.stringify(target)),優點是簡單,缺點是無法拷貝函數、日期、正則、undefined 等;② 成熟方案:使用 Lodash 的\_.cloneDeep 方法,支持更多類型,穩定性高。
3. flex 的三個參數是什麼?
flex 是 flex-grow、flex-shrink、flex-basis 三個屬性的簡寫屬性,語法:flex: [flex-grow] [flex-shrink] [flex-basis]; ,默認值為 0 1 auto。
- flex-grow:定義項目的放大比例,默認值 0(即使容器有剩餘空間,項目也不放大)。若所有項目的 flex-grow 總和 >0,剩餘空間會按比例分配給各項目。
- flex-shrink:定義項目的縮小比例,默認值 1(容器空間不足時,項目會縮小)。若總和 >0,空間不足時按比例縮小;若為 0,空間不足時不縮小。
- flex-basis:定義項目分配空間前的初始大小,默認值 auto(項目自身實際大小)。可設為具體數值(如 100px)或百分比,優先級高於 width/height。
常用簡寫:① flex: 1 → 等價於 1 1 0%(均分剩餘空間);② flex: auto → 等價於 1 1 auto;③ flex: none → 等價於 0 0 auto(不放大不縮小,保持自身大小)。
4. typeof 檢測出的結果都有啥?
typeof 用於檢測數據類型,返回值為字符串類型,共 8 種可能結果:
- "undefined":檢測 undefined 類型(如 typeof undefined);
- "boolean":檢測布爾類型(如 typeof true、typeof false);
- "string":檢測字符串類型(如 typeof 'hello'、typeof "");
- "number":檢測數字類型(如 typeof 123、typeof NaN、typeof Infinity);
- "bigint":檢測大整數類型(如 typeof 10n、typeof 9007199254740991n);
- "symbol":檢測 Symbol 類型(如 typeof Symbol('id'));
- "function":檢測函數類型(如 typeof function(){}、typeof console.log、typeof class{});
- "object":檢測對象、數組、null(如 typeof {}、typeof []、typeof null)。
注意:typeof 無法區分數組、對象、null,需通過 Array.isArray()(判斷數組)或 Object.prototype.toString.call()(精準判斷類型)進一步區分。
5. vue 跳轉路由頁面定時器在哪裏清除
Vue 中路由跳轉時,定時器必須在組件卸載前清除,否則會導致內存泄漏,推薦在 beforeDestroy 或 destroyed 生命週期鈎子中處理。
Vue2 實現步驟:
- 在 data 中定義定時器 ID:data() { return { timer: null }; }
- 在 mounted 中創建定時器:mounted() { this.timer = setInterval(() => { console.log('定時器運行'); }, 1000); }
- 在卸載鈎子中清除:beforeDestroy() { clearInterval(this.timer); this.timer = null; }
Vue3 組合式 API 實現:
import { onMounted, onUnmounted, ref } from 'vue';
export default {
setup() {
const timer = ref(null);
onMounted(() => {
timer.value = setInterval(() => { console.log('定時器運行'); }, 1000);
});
onUnmounted(() => {
clearInterval(timer.value);
timer.value = null;
});
return {};
}
}
6. 登錄模塊怎麼做的?token 怎麼用的?
一、登錄模塊核心流程
- 前端表單校驗:驗證用户名、密碼非空、格式合法性(如密碼長度)等基礎規則;
- 請求登錄接口:將校驗後的用户名、密碼提交給後端,後端驗證通過後返回 token(用户身份憑證);
- 存儲 token:將 token 存入 localStorage(持久化,刷新頁面不丟失)或 sessionStorage(會話級,關閉瀏覽器丟失);
- 路由守衞控制:通過 router.beforeEach 全局路由守衞,判斷用户是否登錄(是否存在有效 token),未登錄則跳轉至登錄頁;
- 登錄成功跳轉:跳轉至首頁或之前訪問的目標頁面(可通過路由 query 參數記錄);
- 退出登錄:清除存儲的 token,跳轉至登錄頁。
二、token 的使用方式
- 請求攜帶 token:通過 axios 請求攔截器,在所有需要權限的接口請求頭中攜帶 token,格式通常為:headers: { Authorization:
Bearer ${token}}; - token 過期處理:通過 axios 響應攔截器捕獲 401 狀態碼(token 過期/無效),清除本地 token 並跳轉至登錄頁;
- token 刷新機制:若後端支持,可在 token 即將過期時,調用刷新 token 接口獲取新 token,更新本地存儲的 token,避免用户重新登錄。
7. vue 中權限控制怎麼設置?
Vue 中權限控制核心是“基於角色的權限控制(RBAC)”,主要分為 4 個層面:
- 路由權限控制:① 路由攔截:通過 router.beforeEach 守衞,判斷用户角色是否有權訪問目標路由,無權限則跳轉至無權限頁面;② 動態路由:根據用户權限動態添加可訪問路由(如 admin 角色添加管理路由,普通用户不添加)。
- 菜單權限控制:根據用户權限動態渲染菜單,無權限的菜單不顯示(如 v-if="hasPermission('menu:user')")。
- 按鈕權限控制:通過自定義指令(如 v-permission)控制按鈕顯示/隱藏,無權限則不渲染或禁用。
- 接口權限控制:後端驗證 token 合法性及用户權限,前端通過響應攔截器處理 403(無權限)狀態碼,跳轉至無權限頁面。
實現步驟:① 登錄後獲取用户角色及權限列表,存儲到 vuex 或本地;② 路由層面:初始化基礎路由,動態添加權限路由;③ 視圖層面:通過權限列表控制菜單、按鈕渲染;④ 接口層面:依賴後端權限校驗,前端輔助攔截。
8. git 流程是什麼?怎麼解決衝突?
一、常用 Git 工作流程(以 Git Flow 為例)
- 菜單權限控制:根據用户權限動態渲染菜單,無權限的菜單不顯示(如 v-if="hasPermission('menu:user')")。
- 克隆遠程倉庫:git clone 倉庫地址;
- 創建開發分支:從 master 分支創建 develop 分支(長期分支),git checkout -b develop master;
- 提交代碼:開發完成後,git add . → git commit -m "完成用户模塊開發";
- 合併分支:將功能分支合併到 develop,git checkout develop → git merge --no-ff feature/user;
- 功能開發:從 develop 創建功能分支(如 feature/user),git checkout -b feature/user develop;
- 修復 bug:從 master 創建 hotfix 分支(如 hotfix/bug1),修復後合併到 master 和 develop。
- 發佈版本:從 develop 創建 release 分支(如 release/1.0.0),測試無誤後合併到 master 和 develop;
二、Git 衝突解決方法
- 按鈕權限控制:通過自定義指令(如 v-permission)控制按鈕顯示/隱藏,無權限則不渲染或禁用。
- 接口權限控制:後端驗證 token 合法性及用户權限,前端通過響應攔截器處理 403(無權限)狀態碼,跳轉至無權限頁面。
實現步驟:① 登錄後獲取用户角色及權限列表,存儲到 vuex 或本地;② 路由層面:初始化基礎路由,動態添加權限路由;③ 視圖層面:通過權限列表控制菜單、按鈕渲染;④ 接口層面:依賴後端權限校驗,前端輔助攔截。
8. git 流程是什麼?怎麼解決衝突?
一、常用 Git 工作流程(以 Git Flow 為例)
- 克隆遠程倉庫:git clone 倉庫地址;
- 創建開發分支:從 master 分支創建 develop 分支(長期分支),git checkout -b develop master;
- 功能開發:從 develop 創建功能分支(如 feature/user),git checkout -b feature/user develop;
- 提交代碼:開發完成後,git add . → git commit -m "完成用户模塊開發";
- 合併分支:將功能分支合併到 develop,git checkout develop → git merge --no-ff feature/user;
- 發佈版本:從 develop 創建 release 分支(如 release/1.0.0),測試無誤後合併到 master 和 develop;
- 修復 bug:從 master 創建 hotfix 分支(如 hotfix/bug1),修復後合併到 master 和 develop。
二、Git 衝突解決方法
衝突原因:多人修改同一文件的同一部分,Git 無法自動合併。
- 查看衝突:git pull 或 git merge 時提示衝突,文件中會出現衝突標記(<<<<<<< HEAD(當前分支內容)、=======(分隔線)、>>>>>>> 分支名(待合併分支內容));
- 解決衝突:打開衝突文件,根據需求修改內容(刪除衝突標記,保留正確代碼);
- 提交解決結果:git add 衝突文件 → git commit -m "解決衝突:合併用户模塊代碼";
- 後續操作:若在 pull 時解決衝突,直接完成同步;若在 merge 時解決衝突,繼續完成合並流程。
注意:解決衝突前先溝通,避免誤刪他人代碼;大型項目建議頻繁提交、同步代碼,減少衝突概率。
9. git 中 rebase 和 merge 的區別
rebase(變基)和 merge(合併)都是 Git 合併分支的方法,核心區別在於提交歷史的處理方式:
| 對比維度 | merge(合併) | rebase(變基) |
|---|---|---|
| 提交歷史 | 保留完整提交歷史,會生成一個新的合併提交(commit),歷史記錄呈分叉狀 | 改寫提交歷史,將待合併分支的提交“移植”到目標分支末尾,歷史記錄呈線性 |
| 衝突處理 | 所有衝突一次性處理,解決後生成合並提交 | 按提交順序依次處理衝突,每處理一個衝突需執行 git add → git rebase --continue |
| 適用場景 | 團隊協作中合併公共分支(如 develop 合併到 master),保留分支開發軌跡 | 個人開發分支同步公共分支(如 feature 分支同步 develop),保持歷史記錄簡潔 |
| 風險 | 無風險,不修改已有提交記錄 | 有風險,修改了待合併分支的提交歷史,禁止在公共分支(如 develop、master)使用 |
總結:公共分支合併用 merge,個人分支同步公共分支用 rebase。
10. 數組對象中存了 name 和 age 怎麼根據 age 進行排序
使用數組的 sort()方法,結合自定義比較函數實現按 age 排序,sort()默認按字符串 Unicode 編碼排序,需手動指定數字排序規則:
// 示例數組
const userList = [
{ name: '張三', age: 25 },
{ name: '李四', age: 20 },
{ name: '王五', age: 30 }
];
// 1. 按age升序排序(從小到大)
const ascendingSort = userList.sort((a, b) => a.age - b.age);
console.log(ascendingSort); // 李四(20) → 張三(25) → 王五(30)
// 2. 按age降序排序(從大到小)
const descendingSort = userList.sort((a, b) => b.age - a.age);
console.log(descendingSort); // 王五(30) → 張三(25) → 李四(20)
原理:sort()的比較函數返回值 >0 時,交換 a 和 b 的位置;返回值 <0 時,不交換;返回值=0 時,位置不變。a.age - b.age 實現升序,b.age - a.age 實現降序。
11. 數組對象如何去重
數組對象去重核心是根據唯一標識(如 id)判斷重複,常用 3 種方法:
方法 1:利用 Map(推薦,高效)
const arr = [
{ id: 1, name: '張三' },
{ id: 2, name: '李四' },
{ id: 1, name: '張三' } // 重複
];
const uniqueArr = Array.from(new Map(arr.map(item => [item.id, item])).values());
console.log(uniqueArr); // 保留第一個重複項
方法 2:利用 filter + findIndex
const uniqueArr = arr.filter((item, index) => {
// 只保留第一次出現的id對應的元素
return arr.findIndex(obj => obj.id === item.id) === index;
});
方法 3:利用 Set(需先轉唯一鍵)
const idSet = new Set();
const uniqueArr = [];
for (const item of arr) {
if (!idSet.has(item.id)) {
idSet.add(item.id);
uniqueArr.push(item);
}
}
説明:以上方法均按 id 去重,若需按其他字段(如 name),只需將 item.id 改為對應字段即可;默認保留第一個重複項,若需保留最後一個,可反向遍歷數組。
12. 防抖和節流在哪些場景用過,怎麼實現
一、防抖(debounce)
核心邏輯:觸發事件後,延遲 n 秒執行函數;若 n 秒內再次觸發,重新計時。
適用場景:① 搜索框輸入聯想(避免輸入過程中頻繁請求接口);② 窗口 resize 事件(避免窗口調整時頻繁觸發計算);③ 按鈕點擊防重複提交(避免快速點擊多次觸發)。
實現代碼:
function debounce(fn, delay) {
let timer = null;
return function(...args) {
// 清除之前的定時器,重新計時
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args); // 綁定this和參數
}, delay);
};
}
二、節流(throttle)
核心邏輯:觸發事件後,n 秒內只執行一次函數,避免頻繁執行。
適用場景:① 滾動事件(如滾動加載更多、監聽滾動位置);② 鼠標移動事件(如拖拽時獲取位置);③ 高頻點擊事件(如遊戲射擊按鈕)。
實現代碼(時間戳版):
function throttle(fn, interval) {
let lastTime = 0; // 上一次執行時間
return function(...args) {
const now = Date.now();
// 若當前時間 - 上一次執行時間 > 間隔,執行函數
if (now - lastTime > interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
補充:節流還有定時器版,核心是觸發時執行一次,然後設置定時器,n 秒內不重複執行;時間戳版立即執行,定時器版延遲執行,可根據場景選擇。
13. 前端怎麼做性能優化
前端性能優化從“加載優化、渲染優化、運行時優化”三個核心維度入手:
- 加載優化:① 資源壓縮與合併(JS/CSS 壓縮、圖片壓縮);② 資源緩存(設置 HTTP 緩存 Cache-Control/Expires、使用 ETag);③ 懶加載(圖片懶加載、組件懶加載、路由懶加載);④ 預加載/預連接(preload 關鍵資源、preconnect 第三方域名);⑤ 減少 HTTP 請求(合併文件、使用 Sprite 精靈圖);⑥ 使用 CDN 加速(靜態資源部署到 CDN)。
- 渲染優化:① 減少重排(Reflow)和重繪(Repaint)(避免頻繁操作 DOM、使用 CSS3 硬件加速 transform/opacity);② 優化 CSS 選擇器(避免複雜選擇器、減少嵌套);③ 避免阻塞渲染(JS 放 body 底部、CSS 用 link 標籤而非 @import);④ 使用虛擬 DOM(React/Vue)減少真實 DOM 操作;⑤ 合理使用 requestAnimationFrame 代替 setTimeout。
- 運行時優化:① 代碼層面(減少閉包使用、避免內存泄漏、優化循環邏輯);② 狀態管理優化(避免不必要的狀態更新、使用 memo/useMemo 緩存組件/計算結果);③ 大數據渲染優化(虛擬列表、分頁加載);④ 避免頻繁 GC(減少臨時變量創建)。
輔助優化:① 性能監控(使用 Lighthouse、Chrome DevTools 分析性能瓶頸);② 服務端優化(SSR 服務端渲染、SSG 靜態站點生成,提升首屏加載速度)。
14. webpack 用過嗎?流程是什麼?常用的 plugin 和 loader 都有啥?打包後文件過大怎麼辦?
一、Webpack 使用經驗
用過。Webpack 是前端模塊化打包工具,核心作用是將分散的模塊化文件(JS、CSS、圖片等)打包為瀏覽器可識別的靜態資源,同時提供代碼轉換、優化等功能。
二、Webpack 打包流程
- 初始化:讀取 webpack.config.js 配置,合併默認配置,創建 Compiler 對象;
- 編譯:從 Entry 入口文件開始,解析模塊依賴,通過 Loader 轉換非 JS 資源,生成 AST 抽象語法樹,收集依賴關係,構建依賴樹;
- 優化:代碼分割(拆分 chunk)、Tree-shaking(剔除死代碼)、模塊合併等;
- 生成:將優化後的模塊整合為 chunk,注入運行時代碼,生成最終靜態資源;
- 輸出:將靜態資源寫入指定目錄,完成打包。
三、常用 Plugin 和 Loader
- 常用 Loader:① babel-loader:將 ES6+ 代碼轉換為 ES5;② css-loader:解析 CSS 文件,處理 CSS 依賴;③ style-loader:將 CSS 注入到 HTML 的 style 標籤;④ file-loader:處理圖片、字體等資源,輸出為單獨文件;⑤ url-loader:小圖片轉 Base64,減少請求;⑥ sass-loader:解析 SCSS/SASS 文件。
- 常用 Plugin:① HtmlWebpackPlugin:自動生成 HTML 文件,引入打包後的資源;② MiniCssExtractPlugin:將 CSS 提取為單獨文件(替代 style-loader);③ TerserPlugin:壓縮 JS 代碼;④ CleanWebpackPlugin:打包前清空輸出目錄;⑤ DefinePlugin:注入環境變量(如 process.env.NODE\_ENV);⑥ CopyWebpackPlugin:複製靜態資源到輸出目錄。
四、打包後文件過大的解決方法
- 代碼分割:① 路由分割(React.lazy/Vue 異步組件);② 公共模塊提取(splitChunks 提取第三方庫如 lodash、react);
- 資源優化:① 壓縮代碼(TerserPlugin/MiniCssExtractPlugin 壓縮);② 圖片優化(壓縮圖片、使用 WebP 格式);③ 剔除無用代碼(Tree-shaking,需開啓 mode: 'production');
- 第三方庫優化:① 使用 CDN 引入第三方庫(如 React、Vue),避免打包進 bundle;② 替換體積大的庫(如用 lodash-es 替代 lodash,支持 Tree-shaking);
- 其他:① 開啓 Gzip/Brotli 壓縮(服務端配置);② 減少不必要的依賴,按需引入(如 Element UI 按需引入)。
15. vue3 和 vue2 的區別
| 對比維度 | Vue2 | Vue3 |
|---|---|---|
| 核心架構 | 選項式 API(Options API),按 data、methods、computed 等組織代碼 | 組合式 API(Composition API),按邏輯功能組織代碼,更靈活 |
| 響應式原理 | Object.defineProperty,監聽屬性的 get/set,無法監聽數組索引、對象新增屬性 | Proxy,代理整個對象,支持監聽數組索引、對象新增/刪除屬性,性能更好 |
| 生命週期 | 選項式生命週期(如 created、mounted) | 組合式 API 生命週期(如 onMounted、onUnmounted),需手動導入 |
| 模板語法 | 支持 HTML 模板,JSX 需額外配置 | 原生支持 JSX,模板語法新增 Teleport、Suspense 等 |
| 狀態管理 | Vuex(核心是 Mutation/Action) | Pinia(簡化版 Vuex,無需 Mutation,支持 TypeScript),Vuex4 兼容 Vue3 |
| TypeScript 支持 | 支持有限,需額外配置 vue-class-component | 原生支持 TypeScript,類型推斷更完善 |
| 性能 | 重渲染性能一般,打包體積較大 | 重渲染性能提升,打包體積更小(Tree-shaking 優化) |
| 其他 | 無碎片組件,自定義指令鈎子不同 | 支持碎片組件(多個根節點),自定義指令鈎子優化,新增 setup 入口 |
16. es6 常用的都有哪些
- let/const 聲明:替代 var,let 支持塊級作用域,const 聲明常量(不可修改引用);
- 箭頭函數:簡化函數寫法,不綁定 this(this 指向外層詞法作用域),如(a, b) => a + b;
- 模板字符串:用
包裹,支持換行和變量插值${},如Hello ${name}\`; - 解構賦值:快速提取數組/對象中的值,如 const [a, b] = [1, 2],const { name } = { name: '張三' };
- 擴展運算符(...):展開數組/對象,如[...arr1, ...arr2],{ ...obj1, name: '李四' };
- 默認參數:函數參數設置默認值,如 function fn(a = 1) {};
- 剩餘參數(...rest):收集剩餘參數為數組,如 function fn(...args) {};
- 數組方法:map(映射)、filter(過濾)、reduce(累加)、forEach(遍歷)、find(查找)、some(是否存在)、every(是否全部滿足);
- Promise:處理異步操作,解決回調地獄,如 new Promise((resolve, reject) => {});
- class 類:語法糖,替代原型鏈繼承,如 class Person { constructor(name) { this.name = name; } };
- import/export:模塊化導入導出,替代 CommonJS 的 require/module.exports;
- Set/Map 數據結構:Set 存儲唯一值,Map 存儲鍵值對(鍵可任意類型)。
17. css3 新增了哪些
- 選擇器:① 屬性選擇器(如[attr^=value]、[attr$=value]);② 偽類選擇器(如:nth-child()、:hover、:active、:focus、:not());③ 偽元素選擇器(如::before、::after、::first-line、::selection);
- 盒模型與佈局:① Flex 佈局(彈性佈局);② Grid 佈局(網格佈局);③ 多列布局(column-count/column-gap);
- 邊框與背景:① 圓角(border-radius);② 陰影(box-shadow、text-shadow);③ 背景漸變(linear-gradient、radial-gradient);④ 多背景圖(background-image 多圖疊加);⑤ 背景大小(background-size);
- 動畫與過渡:① 過渡(transition,實現平滑動畫);② 動畫(animation,自定義關鍵幀動畫);③ 變換(transform,如 rotate 旋轉、scale 縮放、translate 平移、skew 傾斜);
- 文本相關:① 文本溢出(text-overflow: ellipsis);② 文本陰影(text-shadow);③ 字體(@font-face 引入自定義字體);④ 換行(word-wrap/word-break);
- 其他:① 透明度(opacity);② 濾鏡(filter,如 blur 模糊、grayscale 灰度);③ 媒體查詢(@media,響應式佈局基礎);④ 變量(--var 定義變量,var()使用)。
18. 在 vue 中,data 中有個變量 a,在 created 裏面有個定時器,在定時器裏 this.a 能訪問到這個變量麼?如果不能為什麼?怎麼解決?
能訪問到。
原因:Vue 的 created 生命週期鈎子執行時,組件實例已創建完成,data 中的數據已被初始化並掛載到組件實例(this)上。定時器函數是在 created 內部定義的,形成閉包,有權訪問外部的 this(組件實例),因此可以通過 this.a 訪問到 data 中的變量 a。
示例代碼:
new Vue({
data() {
return { a: 10 };
},
created() {
setInterval(() => {
console.log(this.a); // 能正常訪問,輸出10
this.a++; // 也能正常修改
}, 1000);
}
})
補充:若定時器函數是普通函數(非箭頭函數),this 會指向 window(非嚴格模式),此時無法訪問 this.a。解決方法:① 用箭頭函數(綁定外層 this);② 在 created 中保存 this 到變量(如 const \_this = this; 定時器中用\_this.a);③ 用 bind 綁定 this(setInterval(function() {}).bind(this), 1000))。
19. vuex 怎麼解決刷新丟失問題?
Vuex 狀態存儲在內存中,頁面刷新時組件實例重建,內存釋放,狀態丟失。解決核心思路是“狀態持久化”,將 Vuex 狀態同步到本地存儲(localStorage/sessionStorage),刷新後從本地存儲恢復狀態。
常用實現方法:
- 手動實現(簡單場景):① 存儲:在 mutation 中,每次修改狀態後同步到本地存儲;② 恢復:在 Vuex 初始化時(如 created 鈎子或 store 創建時),從本地存儲讀取狀態並賦值給 state。示例:
// store/index.js const store = new Vuex.Store({ state: { userInfo: localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null }, mutations: { SET_USER_INFO(state, userInfo) { state.userInfo = userInfo; // 同步到localStorage localStorage.setItem('userInfo', JSON.stringify(userInfo)); } } }); - 使用第三方插件(複雜場景,推薦):使用 vuex-persistedstate 插件,自動實現狀態持久化,無需手動同步。示例:
// 安裝:npm install vuex-persistedstate --save import createPersistedState from 'vuex-persistedstate'; const store = new Vuex.Store({ // ...其他配置 plugins: [createPersistedState({ storage: localStorage, // 存儲方式:localStorage/sessionStorage reducer: (state) => ({ userInfo: state.userInfo }) // 只持久化userInfo字段 })] });
注意:① 若狀態中有敏感數據(如 token),不建議用 localStorage(明文存儲),可加密存儲或用 sessionStorage(會話級);② 本地存儲存儲容量有限(約 5MB),避免存儲過大狀態。
20. 實現一個佈局,第一個 div 佔一份,第二個佔兩份,第三個佔三份
推薦用 Flex 佈局實現,簡潔高效,兼容性好:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flex比例佈局</title>
<style>
.container {
display: flex; /* 開啓Flex佈局 */
width: 100%;
height: 300px; /* 父容器高度,可自定義 */
gap: 10px; /* 子元素間距,可選 */
}
.item1 {
flex: 1; /* 佔1份 */
background-color: #f00;
}
.item2 {
flex: 2; /* 佔2份 */
background-color: #0f0;
}
.item3 {
flex: 3; /* 佔3份 */
background-color: #00f;
}
</style>
</head>
<body>
<div class="container">
<div class="item1"></div>
<div class="item2"></div>
<div class="item3"></div>
</div>
</body>
</html>
補充:也可用 Grid 佈局實現:
.container {
display: grid;
grid-template-columns: 1fr 2fr 3fr; /* 列比例1:2:3 */
width: 100%;
height: 300px;
gap: 10px;
}
説明:1fr 表示“剩餘空間的一份”,flex: n 等價於 flex: n 1 0%,會按比例分配父容器的剩餘空間;Grid 佈局的 grid-template-columns 直接定義列比例,更直觀。
21. 二次封裝過 element ui 嗎?為什麼?
封裝過。
核心原因:① 統一業務風格:Element UI 組件是通用的,二次封裝可統一項目內組件的樣式、交互邏輯(如按鈕大小、表單校驗規則、彈窗樣式),避免重複開發;② 適配業務需求:通用組件無法滿足特定業務場景(如自定義表格列、表單聯動邏輯),二次封裝可擴展功能;③ 降低維護成本:若後續需替換 UI 庫或修改組件邏輯,只需修改封裝後的組件,無需修改所有使用處;④ 簡化使用:封裝後可減少重複屬性傳遞(如 el-button 默認設置 type="primary"),簡化代碼。
示例(封裝 el-button):
<template>
<el-button
:type="type || 'primary'"
:size="size || 'medium'"
:loading="loading"
@click="handleClick"
>
<slot></slot>
</el-button>
</template>
<script>
export default {
name: 'MyButton',
props: {
type: String,
size: String,
loading: Boolean
},
methods: {
handleClick(e) {
// 擴展業務邏輯(如按鈕點擊日誌)
console.log('按鈕點擊');
this.$emit('click', e); // 透傳事件
}
}
}
</script>
22. vue 響應攔截器是幹啥的?
Vue 中響應攔截器是 axios 的核心功能,用於“統一處理所有接口的響應數據和錯誤”,在後端返回響應後、前端業務代碼接收數據前執行,核心作用是簡化業務代碼、統一錯誤處理。
核心功能:
- 統一處理響應數據:① 過濾無用數據(如後端返回{ code: 200, data: {}, msg: '' },攔截器中直接返回 data,業務代碼無需重複解析);② 格式化數據(如日期格式化、數字格式化)。
- 統一處理錯誤:① 網絡錯誤(如請求超時、斷網);② 業務錯誤(如 code≠200、token 過期 401、無權限 403);③ 錯誤提示(統一彈框提示錯誤信息,避免業務代碼重複寫彈框)。
- 其他擴展:① 刷新 token(捕獲 401 狀態碼,調用刷新 token 接口後重新發起原請求);② 日誌記錄(記錄接口響應時間、錯誤信息)。
示例代碼:
import axios from 'axios';
const service = axios.create({ baseURL: '/api' });
// 響應攔截器
service.interceptors.response.use(
(response) => {
// 成功響應:直接返回data
const res = response.data;
return res.code === 200 ? res.data : Promise.reject(res.msg);
},
(error) => {
// 錯誤處理
if (error.response) {
const status = error.response.status;
if (status === 401) {
// token過期:清除token,跳轉登錄頁
localStorage.removeItem('token');
window.location.href = '/login';
} else if (status === 403) {
alert('無權限訪問');
}
} else {
alert('網絡錯誤,請檢查網絡');
}
return Promise.reject(error);
}
)
23. 在哪裏使用過自定義指令?
自定義指令在 Vue 項目中常用於封裝複用性強的 DOM 操作邏輯,核心使用場景集中在“元素行為控制”和“視圖交互增強”,具體場景及示例如下:
- 權限控制(按鈕/元素顯示隱藏):根據用户權限動態控制元素是否渲染或禁用,替代重複的 v-if 判斷。示例:封裝 v-permission 指令,傳入權限標識,無權限則移除元素。
- 表單輸入增強:限制輸入格式(如僅允許輸入數字、限制輸入長度)、自動聚焦、輸入防抖等。示例:封裝 v-input-number 指令,禁止輸入非數字字符。
- 元素交互效果:實現自定義的懸停效果、點擊反饋、滾動動畫等。示例:封裝 v-hover-effect 指令,鼠標懸浮時添加元素縮放、陰影變化效果。
- 資源加載處理:圖片加載失敗時顯示默認圖、動態加載腳本/樣式等。示例:封裝 v-img-error 指令,圖片加載失敗時替換為默認佔位圖。
- 其他 DOM 操作:如自動複製文本、限制元素拖拽範圍、自定義滾動條等。
示例代碼(v-permission 指令):
// 全局註冊指令(main.js)
Vue.directive('permission', {
inserted: function(el, binding) {
// 獲取用户權限列表(假設從vuex或本地存儲獲取)
const userPermissions = store.state.permissions;
// 若用户無該權限,移除元素
if (!userPermissions.includes(binding.value)) {
el.parentNode.removeChild(el);
}
}
});
// 組件中使用
// <button v-permission="['user:delete']">刪除用户</button>
24. 簡述數組方法 map,filter,forEach
三者均為數組遍歷方法,核心作用是迭代數組元素,但功能定位和返回值不同,具體區別如下:
- map:① 核心功能:遍歷數組,對每個元素執行回調函數,返回一個新數組(新數組元素為回調函數的返回值);② 不改變原數組;③ 適用場景:數組元素轉換(如格式轉換、值計算)。示例:將數組中數字翻倍:[1,2,3].map(num => num * 2) → 結果:[2,4,6];
- map:① 核心功能:遍歷數組,對每個元素執行回調函數,返回一個新數組(新數組元素為回調函數的返回值);② 不改變原數組;③ 適用場景:數組元素轉換(如格式轉換、值計算)。示例:將數組中數字翻倍:[1,2,3].map(num => num * 2) → 結果:[2,4,6];
- filter:① 核心功能:遍歷數組,篩選出符合回調函數條件(返回 true)的元素,組成新數組返回;② 不改變原數組;③ 適用場景:數組元素篩選(如篩選符合條件的數據)。示例:篩選數組中的偶數:[1,2,3,4].filter(num => num % 2 === 0) → 結果:[2,4];
- forEach:① 核心功能:遍歷數組,對每個元素執行回調函數,無返回值(返回 undefined);② 不改變原數組(若回調內手動修改元素屬性則可能改變);③ 適用場景:單純的數組遍歷(如打印元素、批量執行操作)。示例:遍歷打印數組元素:[1,2,3].forEach(num => console.log(num)) → 依次打印 1、2、3;
25. 數組方法 sort 默認排序方式是什麼?
sort()方法默認排序方式是按字符串的 Unicode 編碼(UTF-16)順序排序,而非數字大小順序。
核心特點:
- 排序前會將數組元素統一轉為字符串(即使是數字類型),再比較字符串的 Unicode 編碼;
- 數字排序陷阱:若直接對數字數組使用 sort(),會出現不符合預期的結果。例如:[10, 2, 22, 1].sort() → 結果:[1, 10, 2, 22],原因是轉為字符串後"10"的 Unicode 編碼小於"2";
- 解決方法:需傳入自定義比較函數,實現數字大小排序。示例:
// 數字升序排序 [10, 2, 22, 1].sort((a, b) => a - b); // 結果:[1, 2, 10, 22] // 數字降序排序 [10, 2, 22, 1].sort((a, b) => b - a); // 結果:[22, 10, 2, 1]
26. 獲取時間戳怎麼獲取?
時間戳是指從 1970 年 1 月 1 日 00:00:00 UTC 到當前時間的毫秒數,常用獲取方法有 4 種:
- Date.now():ES6 新增,最簡潔高效,直接返回當前時間戳(毫秒),無兼容性問題。示例:const timestamp = Date.now(); // 輸出:1755088823456;
- new Date().getTime():創建 Date 實例後調用 getTime()方法,兼容性好(支持 ES5 及以下)。示例:const timestamp = new Date().getTime();;
- new Date().valueOf():與 getTime()效果一致,返回當前時間戳(毫秒)。示例:const timestamp = new Date().valueOf();;
- +new Date():通過一元加號運算符將 Date 實例轉為數字類型,即時間戳(毫秒),寫法簡潔。示例:const timestamp = +new Date();;
補充:若需獲取秒級時間戳(後端常用),只需對以上結果除以 1000 並取整:const secondTimestamp = Math.floor(Date.now() / 1000);。
27. 如何判斷一個對象是數組?
常用 5 種判斷方法,各有優劣,推薦使用前 2 種:
- Array.isArray(obj):ES6 新增,最推薦,精準判斷,兼容性好(IE9 及以上)。示例:
Array.isArray([]); // true Array.isArray({}); // false Array.isArray(null); // false - Object.prototype.toString.call(obj) === '[object Array]':最精準,兼容性極佳(支持所有瀏覽器),可區分數組、對象、null 等。示例:
Object.prototype.toString.call([]); // "[object Array]" Object.prototype.toString.call({}); // "[object Object]" Object.prototype.toString.call(null); // "[object Null]" - obj instanceof Array:判斷原型鏈,存在侷限性:若數組在不同 iframe 中創建,原型鏈不同,會判斷為 false。示例:[] instanceof Array; // true;
- obj.constructor === Array:通過構造函數判斷,侷限性:若手動修改 obj.constructor,會導致判斷失效。示例:[].constructor === Array; // true;
- typeof obj === 'object' && obj.length !== undefined:不推薦,存在漏洞(如字符串、類數組對象也可能滿足條件)。示例:typeof [] === 'object' && [].length !== undefined; // true,但 typeof 'abc' === 'string' 不滿足,類數組對象如 arguments 會誤判。
28. js 中如何去除空格?
根據空格位置(首尾、所有、指定位置),常用方法如下:
- 去除首尾空格(最常用): trim():ES5 新增,去除字符串首尾的空格(包括空格、製表符\t、換行符\n 等空白字符),不改變原字符串。示例:' hello world '.trim() → "hello world";
- trimStart()/trimLeft():去除首部空格;trimEnd()/trimRight():去除尾部空格。示例:' hello '.trimStart() → "hello ";
- 去除所有空格: 正則表達式:str.replace(/\s+/g, ''),\s 匹配所有空白字符,g 表示全局匹配。示例:'he l lo w orld'.replace(/\s+/g, '') → "helloworld";
- 去除指定位置空格:通過正則精準匹配,例如只去除中間空格:str.replace(/(\S)\s+(\S)/g, '$1$2')。示例:'he l lo'.replace(/(\S)\s+(\S)/g, '$1$2') → "hello";
補充:若需處理數組中元素的空格,可結合 map 和 trim():const arr = [' a ', 'b ', ' c']; const newArr = arr.map(item => item.trim()); → ["a", "b", "c"]。
29. 常見的 css 兼容都有哪些?怎麼解決?
常見 CSS 兼容問題主要集中在舊瀏覽器(如 IE6-9)和不同內核瀏覽器(Chrome、Firefox、Safari),核心解決思路是“添加瀏覽器前綴”“降級處理”“特性檢測”:
- CSS3 屬性兼容(如 transition、transform、border-radius): 問題:舊瀏覽器不支持 CSS3 屬性,需添加瀏覽器私有前綴。解決:添加-webkit-(Chrome、Safari)、-moz-(Firefox)、-ms-(IE)、-o-(Opera)前綴。示例:
.box { -webkit-border-radius: 8px; /* Chrome、Safari */ -moz-border-radius: 8px; /* Firefox */ -ms-border-radius: 8px; /* IE */ -o-border-radius: 8px; /* Opera */ border-radius: 8px; /* 標準語法 */ -webkit-transform: rotate(30deg); transform: rotate(30deg); }工具優化:使用 Autoprefixer 自動添加前綴,無需手動編寫。 - 盒模型兼容: 問題:IE6-7 使用怪異盒模型(width 包含 padding 和 border),標準瀏覽器使用標準盒模型。解決:通過 box-sizing 統一盒模型。示例:
{ -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; /* 統一為怪異盒模型,width=內容+padding+border */ } - 浮動清除兼容: 問題:IE6-7 浮動後父元素塌陷,且不支持:after 偽元素清除浮動。解決:① 標準瀏覽器:使用 clearfix 偽類;② IE6-7:添加 zoom: 1 觸發 hasLayout。示例:
.clearfix:after { content: ""; display: block; clear: both; visibility: hidden; height: 0; } .clearfix { zoom: 1; /* 兼容IE6-7 */ } - inline-block 兼容: 問題:IE6-7 不支持 inline-block 屬性(對塊級元素無效)。解決:對 IE6-7 添加 display: inline; zoom: 1。示例:
.inline-box { display: inline-block; *display: inline; /* IE6-7專用 */ *zoom: 1; /* 觸發hasLayout */ } - 透明度兼容: 問題:IE6-8 不支持 opacity 屬性,使用 filter 濾鏡。解決:
.transparent { opacity: 0.5; /* 標準語法 */ filter: alpha(opacity=50); /* IE6-8,值為0-100 */ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; /* 更規範的IE寫法 */ }
30. es5 的繼承有哪些缺點?es6 為什麼沒有這些缺點?
一、ES5 繼承的核心缺點(以原型鏈繼承 + 構造函數繼承為例)
- 原型鏈繼承缺點:① 父類原型的引用類型屬性會被所有子類實例共享(修改一個實例的屬性會影響其他實例);② 子類實例創建時無法向父類構造函數傳遞參數;
- 構造函數繼承缺點:① 無法繼承父類原型上的方法和屬性(只能繼承父類構造函數內的屬性和方法),導致方法複用性差(每個實例都有獨立的方法副本);② 父類構造函數會被調用多次,效率低;
- 組合繼承(原型鏈 + 構造函數)缺點:雖解決了上述部分問題,但父類構造函數會被調用兩次(一次是創建子類原型時,一次是創建子類實例時),導致子類原型上存在多餘的父類屬性。
二、ES6 Class 繼承解決上述缺點的原因
ES6 的 class 繼承基於 ES5 的原型鏈,但語法糖封裝更完善,底層優化了繼承邏輯:
- 解決引用類型共享問題:ES6 通過 constructor 構造函數初始化實例屬性,每個實例的屬性獨立,原型上的方法共享,避免引用類型屬性共享;
- 支持向父類傳遞參數:通過 super()調用父類構造函數,可在子類構造函數中向父類傳遞參數。示例:class Child extends Parent { constructor(name) { super(name); } };
- 高效繼承原型方法:子類通過 extends 繼承父類的原型方法和屬性,無需重複定義,方法複用性強,且父類構造函數僅調用一次(子類實例創建時通過 super()調用);
- 清晰的繼承層級:class 語法更直觀,繼承關係明確,避免了 ES5 原型鏈繼承的複雜操作(如手動設置子類原型為父類實例)。
示例對比:ES5 組合繼承需手動處理原型和構造函數,ES6 class 只需 extends 和 super 即可實現完整繼承。
31. ajax 和 fetch 有什麼區別?fetch 的特點是什麼?
一、Ajax 和 Fetch 的核心區別
| 對比維度 | Ajax(XMLHttpRequest) | Fetch(ES6 新增) |
|---|---|---|
| 語法 | 語法繁瑣,需手動創建 XHR 對象、綁定事件、處理狀態 | 語法簡潔,基於 Promise,支持鏈式調用(.then()/.catch()) |
| 異步處理 | 早期依賴回調函數,易產生回調地獄;可結合 Promise 封裝 | 原生支持 Promise,可結合 async/await 進一步簡化異步代碼 |
| 錯誤處理 | 網絡錯誤觸發 onerror 事件,HTTP 錯誤(如 404、500)需手動判斷 status | 網絡錯誤會 reject,但 HTTP 錯誤(404、500)不會 reject,需手動判斷 response.ok |
| 默認行為 | 默認不攜帶 Cookie,需手動設置 withCredentials: true | 默認也不攜帶 Cookie,需設置 credentials: 'include' |
| 終止請求 | 支持 abort()方法終止請求 | 需結合 AbortController 實現請求終止 |
| 兼容性 | 兼容性好(支持所有主流瀏覽器,包括 IE6+) | IE 不支持,需 polyfill 兼容舊瀏覽器 |
二、Fetch 的核心特點
- 基於 Promise:原生支持 Promise,避免回調地獄,可結合 async/await 寫出同步風格的異步代碼;
- 語法簡潔:一行代碼即可發起請求,如 fetch('/api/data'),相比 Ajax 的多步操作更簡潔;
- 模塊化設計:將請求和響應分離為 Request、Response 對象,可靈活配置請求頭、請求體、響應處理等;
- 支持流式處理:可處理大文件(如視頻、音頻)的流式傳輸,無需等待整個文件加載完成;
- 默認不攜帶 Cookie:需手動設置 credentials: 'include'才能攜帶 Cookie,增強安全性;
- HTTP 錯誤不 reject:只有網絡錯誤(如斷網、跨域失敗)會觸發 reject,404、500 等 HTTP 錯誤需通過 response.ok 判斷。
32. 什麼是 BFC?BFC 的原理是什麼?
一、BFC 定義
BFC(Block Formatting Context,塊級格式化上下文)是 CSS 中的一種渲染機制,可理解為“一個獨立的渲染區域”,區域內的元素渲染規則不受外部影響,同時也不會影響外部元素。
二、BFC 的觸發條件(滿足任一即可)
- 根元素(html);
- 浮動元素(float: left/right,不包括 none);
- 絕對定位/固定定位元素(position: absolute/fixed);
- 行內塊元素(display: inline-block);
- overflow 值不為 visible 的塊元素(overflow: hidden/auto/scroll);
- flex 容器(display: flex/inline-flex);
- grid 容器(display: grid/inline-grid)。
三、BFC 的核心原理(渲染規則)
- 區域內元素垂直排列:BFC 內的塊級元素會沿垂直方向依次排列,每個元素的 margin-top 和 margin-bottom 會發生重疊(margin 塌陷);
- 獨立的渲染環境:BFC 內的元素渲染不會影響外部元素,外部元素也不會影響內部;
- margin 重疊解決:兩個相鄰的塊級元素若都處於不同的 BFC 中,它們的 margin 不會重疊;
- 清除浮動影響:BFC 會包含內部的浮動元素(即父元素觸發 BFC 後,不會因內部浮動元素而塌陷);
- 阻止元素被浮動元素覆蓋:BFC 區域不會與浮動元素的區域重疊(可用於實現兩欄佈局)。
四、BFC 的應用場景
- 解決父元素浮動塌陷;
- 解決 margin 重疊問題;
- 實現兩欄佈局(左側浮動,右側觸發 BFC 不被覆蓋);
- 阻止文字環繞浮動元素。
33. vue 怎麼確定事件源?
Vue 中確定事件源(即觸發事件的 DOM 元素),核心是通過**事件對象(event)**的相關屬性獲取,常用方法有 3 種:
- event.target:獲取實際觸發事件的元素(可能是子元素,若事件委託場景)。示例:
<template> <div @click="handleClick" class="parent"> 父元素 <button class="child">子按鈕</button> </div> </template> <script> export default { methods: { handleClick(e) { console.log(e.target); // 點擊子按鈕時輸出button元素,點擊父元素其他區域輸出div元素 } } } </script> - event.currentTarget:獲取綁定事件的元素(即 @click 綁定的元素,固定不變)。示例:上述代碼中,無論點擊子按鈕還是父元素其他區域,e.currentTarget 都輸出 div 元素;
- 通過$event 傳遞參數,結合 ref 獲取元素:若需精準定位特定元素,可給元素添加 ref,通過 ref 獲取 DOM 元素,結合事件對象確認。示例:
<template> <button ref="btn1" @click="handleBtn($event)">按鈕1</button> <button ref="btn2" @click="handleBtn($event)">按鈕2</button> </template> <script> export default { methods: { handleBtn(e) { if (e.target === this.$refs.btn1) { console.log("觸發按鈕1"); } else if (e.target === this.$refs.btn2) { console.log("觸發按鈕2"); } } } } </script>
補充:Vue3 組合式 API 中,可通過 ref()獲取 DOM 元素,事件對象的使用方式與 Vue2 一致。
34. 怎麼解決跨域?分別有什麼弊端?
跨域是指瀏覽器因“同源策略”限制,禁止不同協議、域名、端口的頁面之間相互請求資源。常用解決方法及弊端如下:
- JSONP(JSON with Padding): 原理:利用 script 標籤不受同源策略限制的特性,通過動態創建 script 標籤,請求後端接口並指定回調函數,後端將數據包裹在回調函數中返回,前端執行回調獲取數據。弊端:① 僅支持 GET 請求,不支持 POST、PUT 等其他請求方式;② 存在安全風險(可能遭受 XSS 攻擊,需驗證後端返回的回調函數名);③ 無法捕獲請求錯誤(如 404、500)。
- CORS(跨域資源共享,後端配置): 原理:後端在響應頭中添加 Access-Control-Allow-Origin 等字段,告知瀏覽器允許指定域名的跨域請求。弊端:① 需後端配合配置,前端無法單獨實現;② 複雜請求(如 POST、帶自定義請求頭)會觸發預檢請求(OPTIONS),增加網絡開銷;③ 舊瀏覽器(如 IE8-9)不支持 CORS,需兼容處理。
- 代理服務器(前端配置,如 Vue CLI 代理、Nginx 代理): 原理:利用服務器端不受同源策略限制的特性,前端請求本地代理服務器,代理服務器轉發請求到目標後端服務器,再將響應結果返回給前端。弊端:① 需配置代理服務器,增加部署複雜度;② 僅適用於開發環境或可控制的服務器環境;③ 若代理服務器配置不當,可能引發安全風險(如反向代理泄露內部接口)。
- document.domain + iframe(主域名相同,子域名不同場景): 原理:將兩個頁面的 document.domain 設置為相同的主域名(如 a.test.com 和 b.test.com 都設置為 test.com),實現跨域通信。弊端:① 僅適用於主域名相同、子域名不同的場景,侷限性大;② 只能實現頁面間通信,無法直接解決接口請求跨域;③ 存在安全風險(同主域名下的其他子域名可共享資源)。
- postMessage(頁面間跨域通信): 原理:通過 window.postMessage()方法向其他窗口發送消息,目標窗口通過監聽 message 事件接收消息,實現跨域通信。弊端:① 僅適用於頁面間通信(如 iframe、新窗口),不適合接口請求跨域;② 需嚴格驗證消息來源(origin),否則可能遭受 XSS 攻擊;③ 兼容性依賴瀏覽器支持。
推薦方案:開發環境用代理服務器,生產環境用 CORS。
35. 圖片懶加載怎麼實現?
圖片懶加載(Lazy Loading)核心是“只加載可視區域內的圖片”,減少初始加載資源,提升頁面性能。常用實現方法有 2 種:
- 原生方法(IntersectionObserver API,推薦): 原理:利用 IntersectionObserver 監聽圖片元素是否進入可視區域,進入後再設置圖片的 src 屬性加載圖片。實現步驟:
<!-- 1. 頁面初始化時,圖片src設為佔位圖,真實地址存放在data-src屬性中 --> <img class="lazy-img" src="placeholder.jpg" data-src="real1.jpg" alt="圖片1"> <img class="lazy-img" src="placeholder.jpg" data-src="real2.jpg" alt="圖片2"> <script> // 2. 創建IntersectionObserver實例,監聽元素可見性 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { // 3. 元素進入可視區域 if (entry.isIntersecting) { const img = entry.target; // 4. 將data-src賦值給src,加載真實圖片 img.src = img.dataset.src; // 5. 圖片加載完成後,停止監聽該元素 observer.unobserve(img); } }); }); // 6. 監聽所有懶加載圖片 document.querySelectorAll('.lazy-img').forEach(img => { observer.observe(img); }); </script>優點:性能好(瀏覽器原生 API,異步監聽,不阻塞主線程),無需手動計算滾動位置;缺點:IE 不支持,需 polyfill 兼容。 - 傳統方法(監聽 scroll 事件): 原理:監聽 window 的 scroll 事件,滾動時計算圖片元素的位置與可視區域的關係,若圖片進入可視區域,則加載圖片。實現步驟:
<img class="lazy-img" src="placeholder.jpg" data-src="real1.jpg" alt="圖片1"> <script> // 計算元素是否進入可視區域 function isInViewport(img) { const rect = img.getBoundingClientRect(); // 元素頂部小於視口高度,且元素底部大於0(進入可視區域) return rect.top < window.innerHeight && rect.bottom > 0; } // 加載可視區域內的圖片 function loadLazyImg() { document.querySelectorAll('.lazy-img').forEach(img => { if (isInViewport(img) && !img.src.includes('real')) { img.src = img.dataset.src; } }); } // 監聽scroll事件(結合節流優化性能) window.addEventListener('scroll', throttle(loadLazyImg, 100)); // 頁面初始化時加載一次 loadLazyImg(); </script>優點:兼容性好(支持所有瀏覽器);缺點:scroll 事件觸發頻繁,需結合節流優化,否則影響頁面性能。
補充:Vue 項目中可封裝為自定義指令(v-lazy),實現圖片懶加載的複用。
36. 為什麼 rem 能實現移動端佈局?
rem(font size of the root element)是相對單位,含義是“相對於根元素(html)的字體大小”。其能實現移動端佈局的核心原因是可通過動態修改根元素字體大小,實現頁面元素的等比例縮放,適配不同屏幕尺寸的移動端設備。
具體原理和實現邏輯:
- 相對根元素字體大小:rem 的計算基準是 html 標籤的 font-size。例如:若 html 的 font-size=16px,則 1rem=16px,元素設置 width: 10rem,實際寬度為 160px;
- 動態適配不同屏幕:通過 JS 動態計算並設置 html 的 font-size,使其與屏幕寬度成固定比例。例如:設計稿寬度為 375px,設置 1rem=100px(即 html 的 font-size=100px),則設計稿中 375px 的寬度對應 3.75rem;在屏幕寬度為 750px 的設備上,動態設置 html 的 font-size=200px,3.75rem 對應的實際寬度為 750px,實現等比例縮放;
- 結合 viewport 元標籤:移動端需設置 viewport 元標籤,禁止頁面縮放,確保屏幕寬度為設備物理寬度。示例:<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">;
- 實現步驟示例:
// 設計稿寬度375px,設置1rem=100px(即設計稿1px=1/100 rem) function setRem() { const designWidth = 375; const rootFontSize = 100; // 計算當前屏幕寬度與設計稿寬度的比例 const scale = window.innerWidth / designWidth; // 動態設置html的font-size(限制最小和最大比例,避免極端情況) const fontSize = Math.min(Math.max(rootFontSize * scale, 80), 120); document.documentElement.style.fontSize = fontSize + 'px'; } // 頁面加載和窗口 resize 時執行 window.addEventListener('load', setRem); window.addEventListener('resize', setRem);
優點:實現簡單,適配所有移動端屏幕;缺點:需動態設置根元素字體大小,且 rem 計算需轉換設計稿尺寸(可通過構建工具自動轉換)。
37. vue 中的 watch 方法第一次頁面加載時執行嗎?如果需要執行怎麼實現?
一、默認情況
Vue 中的 watch 方法默認在第一次頁面加載時不執行,僅在監聽的屬性發生變化時才會觸發回調函數。
原因:watch 的核心作用是“監聽屬性變化並執行回調”,默認設計為“變化時觸發”,初始加載時屬性未發生變化,因此不執行。
二、需要第一次加載時執行的實現方法
通過 watch 的 immediate 選項實現,設置 immediate: true,即可讓 watch 在頁面初始加載時執行一次回調函數。
1. Vue2 實現示例:
new Vue({
data() {
return {
name: '張三' // 初始值
};
},
watch: {
name: {
// 回調函數,val為新值,oldVal為舊值(初始加載時oldVal為undefined)
handler(val, oldVal) {
console.log('name值變化/初始加載', val, oldVal);
},
immediate: true // 開啓初始加載執行
}
}
});
2. Vue3 選項式 API 實現:
export default {
data() {
return { name: '張三' };
},
watch: {
name: {
handler(val) {
console.log('初始加載/變化時執行', val);
},
immediate: true
}
}
};
3. Vue3 組合式 API(watch 函數)實現:
import { ref, watch } from 'vue';
export default {
setup() {
const name = ref('張三');
// 第三個參數傳入{ immediate: true }
watch(
name,
(val, oldVal) => {
console.log('初始加載/變化時執行', val, oldVal);
},
{ immediate: true }
);
return { name };
}
};
三、補充説明
- immediate: true 時,回調函數的 oldVal 為 undefined(初始加載時無舊值);
- 若需監聽對象的深層屬性,需同時設置 deep: true(與 immediate 可同時使用)。示例:
watch: { 'user.info': { handler(val) { console.log(val); }, immediate: true, deep: true // 深層監聽 } }
38. 兩個數組怎麼合併?輸出兩種方法。兩個對象怎麼合併?
一、兩個數組合並(兩種方法)
- 擴展運算符(...): 原理:將兩個數組展開為獨立元素,再用新數組包裹,生成新數組(不改變原數組)。示例:
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const newArr = [...arr1, ...arr2]; // 結果:[1,2,3,4,5,6] console.log(arr1, arr2); // 原數組不變:[1,2,3]、[4,5,6]優點:語法簡潔,不改變原數組;缺點:若數組元素為引用類型,僅淺拷貝。 - concat()方法: 原理:concat()方法用於連接兩個或多個數組,返回新數組(不改變原數組)。示例:
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const newArr = arr1.concat(arr2); // 結果:[1,2,3,4,5,6] // 也可連接多個數組:arr1.concat(arr2, arr3, arr4)優點:兼容性好(支持 ES5 及以下),不改變原數組;缺點:同樣是淺拷貝。
補充:若需深合併(數組元素為引用類型),需結合深拷貝方法(如 deepClone 函數)。
二、兩個對象合併
常用 3 種方法,核心是“合併對象屬性,後一個對象屬性覆蓋前一個對象同名屬性”:
- 擴展運算符(...):
const obj1 = { name: '張三', age: 25 }; const obj2 = { age: 30, gender: '男' }; const newObj = { ...obj1, ...obj2 }; // 結果:{ name: '張三', age: 30, gender: '男' }優點:語法簡潔,不改變原對象;缺點:淺拷貝,引用類型屬性會共享。 - Object.assign()方法:
const obj1 = { name: '張三', age: 25 }; const obj2 = { age: 30, gender: '男' }; // 第一個參數為目標對象,後續為源對象,返回合併後的目標對象 const newObj = Object.assign({}, obj1, obj2); // 結果同上 // 注意:若直接Object.assign(obj1, obj2),會修改obj1(原對象)優點:兼容性好(ES6+),可合併多個對象;缺點:淺拷貝。 - 深合併(解決引用類型共享問題): 若對象包含嵌套引用類型(如對象、數組),需深合併,可使用 Lodash 的\_.merge()方法或自定義深拷貝函數。
// 1. Lodash _.merge() import _ from 'lodash'; const obj1 = { name: '張三', info: { address: '北京' } }; const obj2 = { info: { phone: '13800138000' } }; const newObj = _.merge({}, obj1, obj2); // 結果:{ name: '張三', info: { address: '北京', phone: '13800138000' } } // 2. 自定義深合併(基於深拷貝函數) function deepMerge(obj1, obj2) { const result = deepClone(obj1); // 調用之前實現的深拷貝函數 for (const key in obj2) { if (obj2.hasOwnProperty(key)) { if (typeof obj2[key] === 'object' && obj2[key] !== null) { result[key] = deepMerge(result[key] || (Array.isArray(obj2[key]) ? [] : {}), obj2[key]); } else { result[key] = obj2[key]; } } } return result; }
39. vue 中 data 外的數據能實現雙向綁定嗎?
Vue 中 data 外的數據默認無法實現雙向綁定,只有在 data 中聲明的屬性,才會被 Vue 的響應式系統處理,實現數據與視圖的雙向綁定。
一、原因
Vue 的雙向綁定基於“響應式系統”,核心流程是:
- Vue 初始化時,會遍歷 data 中的所有屬性,通過 Object.defineProperty(Vue2)或 Proxy(Vue3)為屬性添加 getter 和 setter;
- 當數據變化時,setter 會觸發依賴更新,通知視圖重新渲染;
- 當視圖輸入變化時,會通過 v-model 等指令觸發 setter,更新數據。
而 data 外的屬性(如直接在組件實例上添加的屬性:this.name = '張三'),不會被 Vue 的響應式系統處理,因此無法觸發視圖更新,也無法實現雙向綁定。
二、若需讓 data 外的數據實現雙向綁定,解決方案
核心是“將數據手動納入 Vue 的響應式系統”,分 Vue2 和 Vue3 兩種場景:
- Vue2 場景: 使用 Vue.set()方法(或 this.$set()),將屬性添加到 data 中的響應式對象上,使其成為響應式屬性。示例:
new Vue({ data() { return { user: { name: '張三' } // data中的響應式對象 }; }, mounted() { // 給data中的user對象添加新屬性age(data外的屬性),使其響應式 this.$set(this.user, 'age', 25); } });注意:Vue.set()只能給已有的響應式對象添加屬性,無法直接給組件實例添加屬性(如 this.$set(this, 'name', '張三')無效)。
Vue3 場景: Vue3 的響應式系統基於 Proxy,提供了 ref 和 reactive 兩種 API,可直接將 data 外的數據轉化為響應式數據: ① 使用 ref API(適用於基本類型和引用類型):import { ref } from 'vue'; export default { setup() { // 直接創建ref響應式數據(無需在data中聲明) const name = ref('張三'); // name為響應式對象,value屬性存儲實際值 // 視圖中可直接使用name,無需.value(模板自動解包) // 腳本中修改需通過.value:name.value = '李四' return { name }; } };② 使用 reactive API(適用於引用類型):import { reactive } from 'vue'; export default { setup() { // 創建響應式對象(data外的數據) const user = reactive({ name: '張三', age: 25 }); // 直接修改屬性即可觸發響應式更新 user.age = 30; return { user }; } };
總結: 無論是 Vue2 還是 Vue3,data 外的數據要實現雙向綁定,核心都是將數據手動納入 Vue 的響應式系統。Vue2 依賴$set 給響應式對象添加屬性,Vue3 則通過 ref/reactive 直接創建響應式數據,更靈活便捷。
40. vue 中 data 數據雙向綁定在那個生命週期?
Vue 中 data 數據雙向綁定的核心是響應式系統的初始化,其初始化時機分 Vue2 和 Vue3 兩種場景,對應不同的生命週期階段:
總結:無論是 Vue2 還是 Vue3,data 數據成為響應式數據(雙向綁定的基礎)的核心階段在 beforeCreate 之後、created 之前;而完整的雙向綁定(視圖與數據聯動)需等到 mounted 生命週期 DOM 掛載完成後才能實現。
- Vue2 場景:雙向綁定的基礎(響應式數據初始化)發生在
beforeCreate生命週期之後、created生命週期之前。 核心邏輯:Vue2 通過Object.defineProperty實現響應式,在beforeCreate階段,組件實例剛創建,data 數據尚未初始化;隨後 Vue 會初始化 data 並將其轉為響應式數據(綁定 getter/setter),這個過程完成後才進入created階段。因此,在created生命週期中,已經可以通過this訪問到 data 中的響應式數據,但此時 DOM 尚未掛載,視圖渲染還未發生。 雙向綁定的完整聯動(數據 → 視圖、視圖 → 數據):需等到mounted生命週期之後,DOM 掛載完成,v-model 等指令完成視圖與數據的綁定,此時修改 data 會同步更新視圖,修改視圖輸入也會同步更新 data。 - Vue3 場景:雙向綁定的基礎(響應式數據初始化)發生在
setup函數執行階段,而setup是在beforeCreate之前執行、created之前完成的。 核心邏輯:Vue3 通過Proxy實現響應式,在setup中通過ref/reactive定義的響應式數據,會被 Vue 自動納入響應式系統;beforeCreate和created階段時,響應式數據已初始化完成。 雙向綁定的完整聯動:同樣需要等到mounted生命週期 DOM 掛載完成後,v-model 等指令綁定視圖與數據,實現完整的雙向聯動。