兩萬字三月前端面經(含回答)

語言: CN / TW / HK

theme: smartblue

前言

從三月剛開始就瘋狂的海投簡歷,到最後順利拿下某中廠的日常實習OC,中間還是遇見了很多的坎坷,這裏來按照不同的公司來記錄一下我的前端面試經歷吧

知乎

這是我最後悔的一場面試,不是因為別的,就是因為我沒有準備充分,錯過了我這個很喜歡的公司 由於是第一次面試,所以緊張的不行,不過面試官人真的很好,一直笑眯眯的鼓勵我繼續説下去,作為所有面試的開頭,這場面試讓我對後面的面試更加有信心和好奇了

CSS部分

  • 常用的選擇器有哪些?(我是傻逼,太緊張了亂説一通)

常用的選擇器包括標籤選擇器、類選擇器、ID選擇器、後代選擇器、子元素選擇器、相鄰兄弟選擇器、通用選擇器和屬性選擇器等

  • 權重

CSS的權重是指在樣式衝突時,某一條樣式被應用的優先級。權重值的計算方式是根據選擇器的類型和數量來確定的。選擇器的權重值從高到低為:!important > 行內樣式 > ID選擇器 > 類選擇器、屬性選擇器和偽類選擇器 > 標籤選擇器和偽元素選擇器。具體來説,ID選擇器的權重值為100,類選擇器、屬性選擇器和偽類選擇器的權重值為10,標籤選擇器和偽元素選擇器的權重值為1。在樣式衝突時,權重值高的樣式會覆蓋權重值低的樣式。

  • 比如我想隱藏一個元素應該怎麼辦呢?這裏我回答了visibility和display,還自作聰明的説了z-index,自己給自己挖了坑
  1. 使用display屬性:設置元素的display屬性為none,這樣元素在頁面上不會佔用任何空間,同時也不會對其他元素產生影響。
  2. 使用visibility屬性:設置元素的visibility屬性為hidden,這樣元素在頁面上不可見,但仍然佔用空間。
  3. 使用opacity屬性:設置元素的opacity屬性為0,這樣元素在頁面上不可見,但仍然佔用空間。
  4. 使用position屬性:將元素的position屬性設置為absolute或fixed,然後將元素移動到屏幕外或者使其超出容器的範圍之外,這樣元素在頁面上不可見,但仍然佔用空間。
  5. 使用z-index屬性:將元素的z-index屬性設置為負值,這樣元素在頁面上不可見,但仍然佔用空間。
  6. 使用clip屬性:將元素的clip屬性設置為一個矩形區域,這樣元素只會顯示矩形區域內的部分,其餘部分會被裁剪掉。
  • 接着上面的繼續問,比如我想要一個元素,既不會被移除,但又要被隱藏(這裏我回答的是visibility,但面試官不滿意,讓我想想其他的方法) 最後説的方法是將其移除視窗外,或者直接縮小到不可見(這個方法確實出乎我的意料)

使用position屬性:將元素的position屬性設置為absolute或fixed,然後將元素移動到屏幕外或者使其超出容器的範圍之外,這樣元素在頁面上不可見,但仍然佔用空間。

  • css當中如果出現了兩個一樣的類定義,你如何避免衝突(有點太緊張了,就説的是scoped進行樣式隔離,然後又按照自己的記憶隨便回答了個外部引入的方式,結果把面試官弄笑了,唉)
  1. 優先級:CSS中每個選擇器都有一個優先級,可以通過優先級來確定哪個樣式定義將被應用。如果兩個類定義中的樣式相沖突,可以通過調整優先級來解決衝突。
  2. 父元素選擇器:可以使用父元素選擇器來限定樣式的範圍,從而避免衝突。比如,如果兩個類定義中的樣式都應用到某個父元素的不同子元素上,可以使用父元素選擇器來限定樣式的範圍,從而避免衝突。
  3. 命名空間:可以使用命名空間來區分不同模塊或組件的樣式定義,從而避免衝突。比如,可以為不同模塊或組件的類定義添加不同的命名空間前綴,從而將它們區分開來。
  4. scoped樣式:可以使用scoped樣式來將樣式限定在特定的組件或模塊中,從而避免與其他組件或模塊的樣式衝突。scoped樣式是一種Vue框架提供的特殊樣式,可以通過在style標籤中添加scoped屬性來實現。

後面的有點記不清楚了

JavaScript部分

  • 問我基本數據類型和引用數據類型(這裏大意了,説的很快,可能沒説全面。。。)
  • 基本數據類型:Undefined null Boolean number string
  • 引用數據類型:object array function
  • 問我es6裏的新引入的東西,這裏面試官想讓我説map forEach,我説了箭頭函數那些,他就鼓勵我繼續説下去,結果大腦當機了沒想到這些
  • 問我map和forEach的區別,我又亂回答了一通,js基礎真的太差了我,唉

map()forEach()都是JavaScript數組對象的方法,用於遍歷數組。它們的區別在於返回值和使用方式。

forEach()方法會對數組的每個元素執行一次回調函數,沒有返回值,僅僅是遍歷數組。

arr = [1, 2, 3, 4]; arr.forEach((num) => { console.log(num * 2); }); // 輸出2 4 6 8

map()方法會對數組的每個元素執行一次回調函數,並將回調函數的返回值組成一個新的數組返回,不會修改原數組。

arr = [1, 2, 3, 4]; const newArr = arr.map((num) => { return num * 2; }); console.log(newArr); // 輸出[2, 4, 6, 8]

因此,如果我們想對數組進行遍歷並執行一些操作,可以使用forEach()方法;如果我們需要在遍歷數組的同時生成一個新的數組,可以使用map()方法。

記不清楚了後面,反正回答的有點差

Vue部分

  • 面試官本身是react的,不是很清楚vue,就讓我介紹vue,我就説了Vue的一些特點和react的區別之類的
  • 然後問我Vue雙向綁定之類的,我就又講了一些底層的東西
  • 問我接觸過react沒,我回答沒😢

項目部分

我這次能拿到這個面試完全看的是組件庫的項目,面試官似乎對我組件庫的項目很感興趣

  • 就説為什麼我的button按鈕那裏不進行一個直接的映射,而是還需要進行調用顏色,我説這部分實現遇見了bug
  • 項目的組件還是太少了
  • 用過git嗎?給我介紹一下,我就開始介紹了,又給自己挖了坑,提到了git merge,然後問我如何切換分支,我説不會,只能硬着頭皮説自己項目沒分支,面試官看上去很驚訝😢

手撕js部分

沒有我背的八股js,直接哭死

實現了將一個由鍵值對對象組成的數組轉換成一個鍵為對象中key屬性值、值為value屬性值的對象

function change(arr) {   return arr.reduce((pre, { key, value }) => {       pre[key] = value;       return pre   }, {}) } ​ let a = change([{ key: 'a', value: '1' }, { key: 'b', value: '2' }]) console.log(a)

我直接不會,然後面試官教我怎麼實現

反問

我第一次面試,所以我感覺很爛,您覺得怎麼樣呢,還有後續嗎?

  • 我感覺還行,但你的js部分可能還不是很好🥹😭感覺很陽光開朗

知乎的技術棧是什麼呢?

  • 大部分是react,有一些是Vue

請問你們的組件是直接用現成的還是自己弄呢?

  • b端的話會自己弄,c端需要很多自己定義樣式的地方,大都手搓

好的,沒有了,謝謝您

  • 好的

總結

自己太緊張了,很多地方發揮的很失敗,面試沒準備好,在面試的時候我才在搭建環境,自己的js基礎太差了,git命令也很差,需要惡補一下,知乎的寄了,接下來加油吧

扁鵲健康

第二場面試,八股文基本上全答出來了,結果反手刪我微信給我掛了😓

第二次面試很明顯比第一次好多了,沒有那麼緊張,情緒也沒有失控,可能是沒開視頻的原因?感覺面試的時候還是得儘量嚴肅一點吧,控制好自己的情緒和麪部表情才能夠更好的回答問題,在面試前讓自己冷靜下來,像對待考試一樣去對待每一場面試

面試官遲到了6分鐘的樣子 電話面試 本以為只會面試10分鐘,結果面試了半小時

之前看牛客上的面經,以為是很簡單的那種,結果大意了,把我簡歷扒拉乾淨了的感覺

一上來還是自我介紹

數據結構

一上來就進行數據結構的拷打嗚嗚,我數據結構很爛誒

  • 有哪些排序的方法?
  1. Array.prototype.sort():該方法可以對數組進行原地排序,即直接修改原數組,不會返回新的數組。默認情況下,它會將數組元素轉換為字符串,然後按照Unicode碼點排序。如果需要按照其他方式排序,可以傳入一個比較函數作為參數。
  2. Array.prototype.reverse():該方法可以將數組中的元素按照相反的順序重新排列,並返回新的數組。
  3. 冒泡排序(Bubble Sort):這是一種簡單的排序算法,它重複地遍歷要排序的數組,比較相鄰的元素並交換位置,直到整個數組都已經排序。
  4. 快速排序(Quick Sort):這是一種快速的排序算法,它的基本思想是選擇一個基準元素,然後將數組中的元素分為小於基準元素和大於基準元素的兩部分,再對這兩部分分別進行排序。
  5. 插入排序(Insertion Sort):這是一種簡單的排序算法,它將數組分為已排序和未排序兩部分,然後將未排序部分的第一個元素插入到已排序部分的正確位置上。
  6. 選擇排序(Selection Sort):這是一種簡單的排序算法,它將數組分為已排序和未排序兩部分,然後從未排序部分選擇最小的元素並放到已排序部分的末尾。
  7. 歸併排序(Merge Sort):這是一種分治的排序算法,它將數組分成兩個子數組,分別對這兩個子數組進行排序,然後將排序後的子數組合併成一個有序的數組
  • 我説了快排和冒泡排序後問我快排的時間複雜度

快速排序的平均時間複雜度為 O(nlogn)。具體來説,當待排序數組的劃分比較平均時,快速排序的時間複雜度是最優的。而當待排序數組已經有序或接近有序時,快速排序的時間複雜度會退化為 O(n^2)。

快速排序的時間複雜度分析如下:

  • 每次劃分操作需要對整個數組進行一次遍歷,時間複雜度為 O(n);
  • 快速排序的遞歸樹的深度為 logn,因為每次劃分都會將數組一分為二,所以深度為 logn;
  • 每次劃分的時間複雜度為 O(n),因此快速排序的總時間複雜度為 O(nlogn)。

需要注意的是,快速排序的最壞時間複雜度為 O(n^2),但這種情況很少出現,通常情況下快速排序的時間複雜度為 O(nlogn),是一種高效的排序算法。

  • 我説了有兩種情況,然後追問我如何對快排進行優化(這裏沒回答好)

JavaScript 中,可以使用以下技巧來優化快速排序算法:

  1. 三數取中:在選擇基準元素時,使用數組中間、頭部和尾部的三個元素的中位數作為基準元素。這可以降低最壞情況的出現概率。
  2. 插入排序:在數組的長度小於某個值(如10)時,使用插入排序算法而不是快速排序。插入排序在處理小數組時比快速排序更快。
  3. 隨機化數組:在每次執行快速排序時,隨機打亂數組,以增加算法的隨機性。
  4. 尾遞歸優化:使用尾遞歸優化快速排序的實現,避免棧溢出。
  • 問我快排最差的情況是什麼

最差情況是每次選取的基準元素都是當前子數組中最大或最小的元素。在這種情況下,每次劃分都只能排除一個元素,因此需要進行 n 次劃分才能完成排序,時間複雜度為 O(n^2)。這種情況發生的概率非常低,但是如果數據本身就是有序的,或者是基本有序的,快排容易陷入最差情況。

後面突然問我計算機組成原理了解嗎?把我嚇到了,連忙説之前學過,但沒怎麼了解

CSS

CSS問題我記得不是很清楚了下次面試一定要錄音!

  • CSS盒模型
  • 出了個場景題,説content大小為100px,border為100px,問此時怪異盒模型的寬高(這裏我電話沒聽太清楚,好像是説我出了問題了,但我下來一看感覺是面試官説錯了(・∀・(・∀・(・∀・*))

js

  • es6新特性
  • 箭頭函數和普通函數區別
  1. 語法不同:箭頭函數使用箭頭符號(=>)來定義函數,而普通函數使用關鍵字 function 來定義。
  2. this 的指向不同:箭頭函數沒有自己的 this,它會繼承父級作用域中的 this 值。而普通函數中的 this 則是在函數被調用時動態確定的,它的值取決於調用函數的方式。
  3. 無法使用 arguments 對象:箭頭函數沒有自己的 arguments 對象,因此在箭頭函數中使用 arguments 會引用外部作用域的 arguments
  4. 不能用作構造函數:箭頭函數不能使用 new 關鍵字來創建實例,因為它們沒有自己的 this,也沒有原型對象。
  5. 沒有原型:箭頭函數沒有 prototype 屬性,因此不能通過它來定義方法。
  6. 沒有自己的 arguments, super, new.target 對象:箭頭函數沒有自己的 arguments, super, new.target 對象,它們都是從外部繼承的。
  • Promise
  • Promise的參數有哪些

Promise 構造函數的參數為一個函數,這個函數接收兩個參數:resolve 和 reject,它們分別表示 Promise 對象的兩種狀態:已解決(fulfilled)和已拒絕(rejected)

  • Promise.all瞭解嗎
  • Promise.all的使用場景
  1. 多個異步操作並行執行,且需要等待所有操作完成後進行下一步處理,比如從多個 API 接口獲取數據,然後將所有數據合併到一起再進行渲染
  2. 多個異步操作中有一個操作失敗就立即停止所有操作,並執行錯誤處理邏輯
  • 數據類型,基本數據類型和引用數據類型
  • symbol瞭解嗎 説説他的使用場景

定義對象的私有屬性:Symbol 值作為屬性名是唯一的,可以防止屬性名的衝突,因此可以用來定義對象的私有屬性

防止對象屬性被意外修改:由於 Symbol 值是唯一的,因此可以用來定義對象屬性,防止屬性被意外修改

定義常量:由於 Symbol 值是唯一的,因此可以用來定義常量

定義枚舉:由於 Symbol 值是唯一的,因此可以用來定義枚舉

計算機網絡

  • 説説http和https的區別
  1. 安全性:HTTP 傳輸的數據是明文的,容易被竊取和篡改,而 HTTPS 使用 SSL/TLS 加密傳輸數據,可以保證數據的機密性和完整性,防止數據被竊取和篡改
  2. 端口號:HTTP 默認使用端口號 80,而 HTTPS 默認使用端口號 443
  3. 證書:HTTPS 使用 SSL/TLS 協議對傳輸數據進行加密,需要使用證書對網站進行身份驗證,防止中間人攻擊。HTTP 不需要證書進行身份驗證
  4. 速度:由於 HTTPS 使用 SSL/TLS 加密傳輸數據,會增加傳輸數據的時間和帶寬消耗,因此速度比 HTTP 慢一些
  5. 緩存:HTTP 可以使用瀏覽器緩存來提高訪問速度,而 HTTPS 在加密傳輸數據時會禁止瀏覽器緩存,以保證數據的安全性
  • 説説http狀態碼
  • 瞭解https數據傳輸的過程嗎?(沒太回答上來)
  1. 客户端向服務器發起 HTTPS 請求,請求中包含了 SSL/TLS 支持的信息,比如支持的 SSL/TLS 版本號、加密算法等。
  2. 服務器返回證書給客户端,證書中包含了服務器的公鑰、服務器的身份信息和證書的有效期等。
  3. 客户端驗證服務器的身份,包括驗證證書的有效性、證書是否過期、證書中的域名與服務器的域名是否一致等。
  4. 如果證書驗證通過,客户端生成一個隨機的加密密鑰,並使用服務器的公鑰進行加密,然後發送給服務器。
  5. 服務器使用私鑰解密客户端發來的密鑰,然後生成一個隨機數作為會話密鑰,並將會話密鑰加密後發送給客户端。
  6. 客户端和服務器使用會話密鑰進行數據傳輸,客户端和服務器之間的所有數據都使用會話密鑰進行加密和解密,保證數據的機密性和完整性

項目

移動端媒體項目:

  • 你這個項目為什麼不用vuex而是pinia呢?説説二者的區別吧
  • 你這個token持久化是怎麼實現的?(亂編了我就)
  • 除了pinia那個方法還有什麼呢?我説了session
  • session關掉後就沒有了哦,還有嗎?(答案是localstorage)

組件庫項目:

  • 為什麼想到做個組件庫的項目呢?
  • 你是如何實現組件庫的封裝呢?(這裏回答defineComputed 然後講我是如何寫的組件就行)
  • 説一下常用的git命令吧
  • 經典問題之git merge和git rebase的區別

git merge 命令會生成一個新的合併提交,並且會保留原來的分支歷史記錄,合併後的提交包含兩個分支的修改。而 git rebase 命令則是將當前分支的修改應用到目標分支上,重新生成一顆新的分支歷史記錄,使得分支歷史記錄更加線性化

  • vite和webpack的區別
  1. 構建方式不同:Vite 利用 ES Modules 的特性進行構建,每個文件都是一個獨立的模塊,開發過程中只需要編譯修改的文件,不需要每次都編譯整個項目;而 Webpack 採用靜態分析的方式進行構建,需要分析整個項目中的依賴關係,每次修改後需要重新編譯整個項目。
  2. 開發體驗不同:Vite 支持快速的熱更新和即時預覽,開發者可以在修改代碼的同時,立即在瀏覽器中查看到最新效果;而 Webpack 需要重新編譯後才能查看最新效果。
  3. 對 Vue 的支持:Vite 是 Vue.js 官方推薦的開發工具,內置了對 Vue 單文件組件的支持,可以直接在瀏覽器中運行 Vue 組件;而 Webpack 需要通過插件等方式進行支持。
  4. 總體來説,Vite 更適合於輕量級的應用,對於 Vue 單文件組件的支持更加完善,而 Webpack 則更適合於複雜的應用,可以通過插件等方式進行更加靈活的配置
  5. vite支持熱重載

熱重載(Hot Reload)是指在應用程序運行時對代碼進行修改,而無需重新啟動應用程序或重新加載整個頁面,即可使更改的部分立即生效並反映在應用程序中。熱重載可以幫助開發人員更快地調試和開發應用程序,同時減少開發週期

還有一些我就忘記了哈哈

反問

  • 公司技術棧?

react 少部分是vue2

  • 多久出結果

一週內吧

感覺還算是很不錯的面試,基本上都回答出來了,不過這個是阿里的外包公司,一半員工都是阿里過去的,看了一下公司規模很小,估計也沒hc讓我進去,就當一次電話面試的體驗了,加油!

即刻app

很熱情的一個面試官!反問環節最有意思的一家,不過難度也算最高的一家

這是我目前面過的公司裏難度感覺最高的一家了,不是常規的那種八股文直接硬背,而是真的結合實際的場景進行出題的,對於一個技術棧的考察的深度很深,是一個很不錯的面試官

首先第一點給我感覺不一樣的就是,沒有自我介紹!上來就直接發了個鏈接,可能是打算結合實際代碼來對我進行考察,可惜這裏失敗了(學校的網真的差。。。)

ref和reactive的區別

ref是一個函數,它可以將一個普通數據類型(如數字、字符串等)轉換為一個響應式對象,從而讓這個數據在Vue的響應式系統中被追蹤。ref返回一個對象,這個對象有一個.value屬性,用來獲取和設置這個響應式對象的值

import { ref } from 'vue'; ​ const count = ref(0); ​ console.log(count.value); // 0 ​ count.value = 1; ​ console.log(count.value); // 1

而reactive則是一個函數,它可以將一個普通的Javascript對象轉換為一個響應式對象。它會遞歸地將這個對象的所有屬性都轉換為響應式對象,從而讓整個對象在Vue的響應式系統中被追蹤。reactive返回一個Proxy對象,用來代理原始對象的訪問和修改

import { reactive } from 'vue'; ​ const state = reactive({  count: 0,  message: 'hello' }); ​ console.log(state.count); // 0 console.log(state.message); // 'hello' ​ state.count = 1; state.message = 'world'; ​ console.log(state.count); // 1 console.log(state.message); // 'world'

因此,ref主要用於創建一個單一的響應式數據,而reactive則適用於創建一個複雜的、包含多個屬性的響應式數據對象

ref可以大量的替換成reactive嗎

不能直接把ref替換成reactive。

ref主要用於將基本數據類型(如字符串、數字等)轉換為響應式數據,並提供一個.value屬性用於訪問和修改該數據。而reactive則用於將一個普通的JavaScript對象轉換為響應式對象,並使用Proxy來攔截對該對象的訪問和修改,以實現響應式更新。

因此,如果你需要使用響應式數據來存儲基本數據類型,或者你只需要響應式地跟蹤一個值的變化,那麼ref仍然是更合適的選擇。而如果你需要管理一個對象的多個屬性,並希望這些屬性可以響應式地更新,那麼reactive會更加合適

為什麼vue和react都去選擇自己實現一個路由,是出於什麼目的呢

  1. 更好地集成到框架中:由於路由是前端應用中必不可少的一部分,因此框架集成路由功能可以提供更好的用户體驗和開發效率。通過自己實現路由庫,Vue和React可以將路由功能無縫集成到框架中,提供更好的開發體驗和更高的開發效率。
  2. 更好地控制代碼和依賴:Vue和React自己實現的路由庫可以更好地控制代碼和依賴。如果使用第三方路由庫,可能會增加代碼的複雜性和依賴關係,而自己實現路由庫可以避免這些問題。
  3. 更好地滿足框架的需求:Vue和React的路由庫可以更好地滿足框架的需求。由於框架本身的特性和設計思想,可能需要特定的路由實現方式來滿足這些需求。通過自己實現路由庫,可以更好地滿足框架的需求,提供更好的開發體驗和更高的性能。
  4. 更好地控制性能:Vue和React的路由庫可以更好地控制性能。由於路由是前端應用中的關鍵部分,性能往往是一個重要的考慮因素。通過自己實現路由庫,Vue和React可以更好地控制路由的性能,從而提供更好的用户體驗和更高的性能

總之,Vue和React都實現了自己的路由庫,主要是為了更好地集成到框架中,更好地控制代碼和依賴,更好地滿足框架的需求,以及更好地控制性能

瀏覽器自己本身就有路由,為什麼不直接用a標籤進行一個跳轉,而是選擇用router來進行一個跳轉呢

瀏覽器本身的路由是基於URL的,即每個頁面都有一個唯一的URL地址。使用瀏覽器本身的路由,需要在URL中手動編寫參數,處理頁面刷新和前進/後退等操作的邏輯,這樣會使得代碼複雜性增加,並且不太方便維護。

而使用router庫可以將路由相關的邏輯抽象出來,讓開發者可以更加方便地處理頁面跳轉和傳遞參數等操作。使用router可以實現單頁應用(SPA),使得用户在應用中的操作更流暢,且無需每次跳轉都重新加載整個頁面。

此外,使用router還可以提供一些額外的功能,如路由守衞、動態路由等,這些功能可以幫助開發者更好地控制路由的行為,提高應用的性能和安全性

雖然瀏覽器本身也有路由,但使用router庫可以提供更好的開發體驗和更豐富的功能,使得應用的開發更加方便、高效和可維護

瀏覽器為什麼支持單頁面路由呢?

參考鏈接:http://developer.mozilla.org/zh-CN/docs/Web/API/History

瀏覽器支持單頁面路由的一個重要原因是History API。

在傳統的多頁面應用中,頁面之間的跳轉通過超鏈接或表單提交等方式實現,每個頁面都有一個唯一的URL地址。而在單頁面應用中,頁面的跳轉是通過JavaScript代碼控制,使用history API可以更加方便地實現這種頁面切換邏輯。

history API是HTML5規範中新增的一組API,可以讓開發者更加方便地操作瀏覽器的歷史記錄。通過history API,開發者可以在不重新加載整個頁面的情況下,改變瀏覽器的URL地址,添加或修改歷史記錄,以及監聽歷史記錄的變化等操作。

在單頁面應用中,開發者可以使用history API來實現前端路由,即在不重新加載整個頁面的情況下,通過改變URL地址,實現不同頁面之間的切換。這樣可以提高應用程序的性能,並且使得應用程序更具交互性和動態性

當我們在使用history進行導航的時候,我們的頁面真的進行了一個切換嗎?是怎麼做到的呢

當我們使用history進行導航時,實際上並沒有進行頁面的刷新或重新加載。相反,瀏覽器僅僅是通過修改URL地址和瀏覽器歷史記錄,模擬了一個頁面的切換效果。

具體來説,使用history進行導航時,我們通常會調用history.pushState或history.replaceState方法,這些方法可以向瀏覽器歷史記錄中添加或修改一個記錄,並且同時修改當前URL地址。然後,瀏覽器會根據新的URL地址重新渲染頁面,並且在瀏覽器的歷史記錄中添加或修改一個記錄。

當我們使用history進行導航時,雖然頁面並沒有進行刷新或重新加載,但是瀏覽器會觸發一些相關的事件,如popstate事件,用來處理導航過程中的一些邏輯。開發者可以在這些事件中添加相關的處理邏輯,從而實現前端路由的功能

vue如何監聽路由的變化呢?

在Vue中,可以使用Vue Router提供的導航守衞(Navigation Guards)來監聽路由的變化。

導航守衞是Vue Router提供的一組鈎子函數,可以在路由發生變化時被觸發。通過使用導航守衞,開發者可以實現一些常見的路由控制邏輯,如路由權限控制、路由攔截、路由跳轉前的確認等等。

Vue Router提供了三種類型的導航守衞:

  1. 全局守衞:在整個應用程序中,所有的路由變化都會觸發這些守衞。可以通過Vue Router實例的beforeEach、beforeResolve和afterEach方法來註冊全局守衞。
  2. 路由獨享守衞:在某個路由上,只有該路由變化時才會觸發這些守衞。可以在路由配置對象中通過beforeEnter屬性來註冊路由獨享守衞。
  3. 組件內守衞:在某個路由對應的組件中,可以通過Vue組件生命週期鈎子函數來監聽路由的變化,實現一些組件內部的路由控制邏輯。

以下是一個使用全局守衞來監聽路由變化的示例:

import Vue from 'vue' import VueRouter from 'vue-router' ​ Vue.use(VueRouter) ​ const router = new VueRouter({  routes: [   { path: '/', component: Home },   { path: '/about', component: About } ] }) ​ router.beforeEach((to, from, next) => {  console.log('路由變化:', from.path, ' => ', to.path)  next() })

在上面的示例中,我們通過Vue Router實例的beforeEach方法註冊了一個全局守衞,在每次路由變化時都會觸發這個守衞,並且打印出路由變化的信息。可以根據實際需求,編寫自己的路由守衞邏輯

原生js如何進行監聽路由的變化

在原生 JavaScript 中,可以使用 window.onpopstate 事件來監聽路由的變化。

當用户在瀏覽器中進行前進或後退操作時,或者通過 JavaScript 調用 history.pushState()history.replaceState() 方法時,都會觸發 window.onpopstate 事件。

可以通過如下代碼來監聽 window.onpopstate 事件:

window.onpopstate = function(event) { console.log("當前路由:", window.location.pathname); };

在上面的代碼中,當用户在瀏覽器中進行前進或後退操作時,或者通過 JavaScript 調用 history.pushState()history.replaceState() 方法時,會觸發 window.onpopstate 事件,並輸出當前路由路徑到控制枱。

需要注意的是,這種方式只能監聽瀏覽器歷史記錄中的路由變化,對於通過 AJAX 或其他方式進行的路由變化是無法監聽的。如果需要監聽所有的路由變化,可以考慮使用一些現有的路由庫,如 React Router、Vue Router 等

沒有hash的路由如何進行監聽

如果你使用的是 HTML5 History API 來管理路由,而不是 hash 路由,那麼可以通過監聽 popstate 事件來實現路由變化的監聽。

HTML5 History API 可以讓我們使用 pushState()replaceState() 方法來操作瀏覽器的歷史記錄,並可以修改當前頁面的 URL,而不會導致頁面的刷新。

當通過 pushState()replaceState() 方法修改瀏覽器歷史記錄時,會觸發 popstate 事件。我們可以通過監聽這個事件來獲取路由的變化。

例如,我們可以使用如下代碼來監聽路由的變化:

window.addEventListener('popstate', function(event) { console.log("當前路由:", window.location.pathname); });

在上面的代碼中,當用户通過瀏覽器前進或後退按鈕,或者通過 pushState()replaceState() 方法修改瀏覽器歷史記錄時,會觸發 popstate 事件,並輸出當前路由路徑到控制枱

onpopstate可以監聽到一個pushstate的事件嗎

這裏回答錯了,我説的是可以進行監聽的,面試官讓我回去再好好看看

onpopstate 事件只能監聽到由瀏覽器觸發的歷史記錄變化,例如點擊瀏覽器的前進或後退按鈕,或者調用 history.back()history.forward() 方法。

如果你在 JavaScript 中調用 history.pushState()history.replaceState() 方法來修改瀏覽器的歷史記錄,那麼不會觸發 onpopstate 事件。但是,調用這兩個方法會添加新的歷史記錄,並且可以在歷史記錄中回退和前進,這些歷史記錄變化會觸發 onpopstate 事件。

因此,如果你想要在調用 pushState()replaceState() 方法後立即獲取路由變化,可以在調用這兩個方法後手動觸發 popstate 事件,例如

history.pushState({}, '', '/new-path'); window.dispatchEvent(new PopStateEvent('popstate'));

在上面的代碼中,我們先調用 pushState() 方法來修改瀏覽器的歷史記錄,並修改當前頁面的 URL 為 /new-path。然後,手動觸發 popstate 事件,這會立即觸發綁定在 window.onpopstate 上的事件處理函數,並獲取到新的路由信息

ts泛型的作用,在開發當中最常用在哪裏

TypeScript 中的泛型(generics)是一種用於在編譯時期處理類型的工具。泛型可以讓我們編寫更通用、更可重用的代碼。

泛型最常用的場景之一是在函數和類中使用。通過使用泛型,我們可以編寫可重用的函數或類,可以支持多種不同類型的參數或屬性。例如,下面是一個使用泛型的函數示例

``` function reverse(list: T[]): T[] { return list.reverse(); }

let numbers = [1, 2, 3, 4]; let reversedNumbers = reverse(numbers);

let letters = ['a', 'b', 'c']; let reversedLetters = reverse(letters); ```

在上面的代碼中,我們定義了一個名為 reverse 的函數,它使用了一個類型參數 T。我們可以將 reverse 函數應用於任何具有 reverse 方法的數組類型。在調用 reverse 函數時,我們將一個類型為 T[] 的數組作為參數傳遞,並返回一個類型為 T[] 的數組。

除了函數和類,泛型還可以應用於 TypeScript 中的其它特性,例如接口、類型別名等

在開發中,我們最常使用泛型的場景是編寫通用的數據結構、算法和函數,例如列表、樹、排序算法、搜索算法等等。泛型可以讓我們編寫更通用、更可重用的代碼,並且可以提高代碼的靈活性和可擴展性。同時,使用泛型還可以讓我們在編譯時期發現類型錯誤,避免一些潛在的運行時錯誤

axios二次封裝的好處

通過對 axios 進行二次封裝,我們可以實現以下功能:

  1. 統一處理請求參數和響應數據格式:我們可以對請求參數和響應數據進行預處理或格式化,以便在多個請求中使用相同的格式。
  2. 統一處理錯誤信息:我們可以對錯誤信息進行統一處理或格式化,以便在多個請求中使用相同的錯誤信息處理邏輯。
  3. 添加請求頭、設置超時時間等功能:我們可以在二次封裝中添加一些公共的請求頭、超時時間等參數,以便在多個請求中使用相同的參數。
  4. 支持自定義攔截器:我們可以通過自定義攔截器來對請求或響應進行處理,例如添加 token、在請求頭中添加認證信息等

如何標識用户已經登錄

在 Web 應用程序中,標識用户是否已經登錄通常使用 Session 或 Token 的方式。

Session 是一種服務器端的技術,用於跟蹤用户的狀態。當用户登錄成功後,服務器會創建一個 Session,併為該用户分配一個唯一的 Session ID,將該 Session ID 存儲在 Cookie 中或者通過 URL 傳遞給客户端。在用户訪問其他頁面時,客户端會將 Session ID 發送回服務器,並使用該 ID 來查找服務器端的 Session 數據。如果 Session 數據存在,説明用户已經登錄,否則用户未登錄。使用 Session 的優點是可以在服務器端存儲敏感的用户信息,不會在客户端暴露。

Token 是一種基於客户端的技術,通常使用 JSON Web Token (JWT) 或類似的技術。當用户登錄成功後,服務器會生成一個 Token,並將該 Token 發送給客户端。客户端在後續的請求中攜帶該 Token,服務器可以通過解析 Token 來驗證用户的身份。使用 Token 的優點是可以讓客户端緩存用户的登錄狀態,減輕服務器的負擔,同時可以在不同的服務之間共享用户的登錄狀態。

在實現登錄功能時,通常需要將用户的登錄信息存儲在服務器端,例如數據庫、緩存等等。當用户登錄成功後,服務器會創建 Session 或生成 Token,並將其返回給客户端。客户端可以將 Session ID 存儲在 Cookie 中,或將 Token 存儲在本地存儲或會話存儲中。在後續的請求中,客户端會將 Session ID 或 Token 發送回服務器,服務器可以根據 Session ID 或解析 Token 來驗證用户的身份

token已經過期的話,我想要刷新token如何實現

在實現 Token 刷新功能時,通常需要注意以下幾個步驟:

  1. 在服務器端,需要定義一個用於刷新 Token 的 API 接口,該接口需要驗證當前 Token 的有效性,並根據需要生成一個新的 Token,並返回給客户端。
  2. 在客户端,當發現當前 Token 已經過期時,需要向服務器端發送一個刷新 Token 的請求,並將當前 Token 和刷新 Token 的回調函數傳遞給服務器端。
  3. 在服務器端,當收到客户端發送的刷新 Token 的請求時,需要驗證當前 Token 的有效性。如果當前 Token 有效,生成一個新的 Token,並將其返回給客户端。如果當前 Token 無效,需要向客户端返回一個錯誤碼或提示信息。
  4. 在客户端,當收到服務器端返回的新 Token 時,需要將新 Token 存儲到本地存儲或會話存儲中,並調用刷新 Token 的回調函數

無感刷新token

在前後端分離的應用中,為了保證安全性和用户體驗,通常會使用token來實現用户身份認證。當token過期時,需要重新獲取新的token,以保持用户的登錄狀態。在這種情況下,無感刷新token可以提高用户體驗,使用户無需手動重新登錄即可繼續訪問應用程序。

以下是一種基本的無感刷新token的實現思路:

  1. 定義token的過期時間,例如30分鐘。
  2. 在用户每次發送請求時,檢查token是否快要過期,例如在token過期時間前5分鐘進行檢查。
  3. 如果token即將過期,發送一個請求給後端,請求新的token。
  4. 如果後端返回新的token,將新的token保存在本地,同時更新所有請求中的token值。
  5. 如果後端返回錯誤或者新的token無效,清除本地token,跳轉到登錄頁面。

通過這種方式,即使token過期,用户也不需要手動重新登錄即可繼續使用應用程序,從而提高用户體驗和應用程序的安全性。當然,具體實現細節可能因具體應用場景而異,需要根據實際情況進行調整。

將上面的操作寫在哪裏呢?

在實現無感刷新token的過程中,主要涉及到兩個方面的實現:前端和後端。

前端方面,可以在請求攔截器中實現token的檢查和更新。可以通過在請求頭中設置Authorization字段,將token發送給後端。當token即將過期時,可以在請求攔截器中發送一個刷新token的請求,並將新的token保存在本地存儲中,同時更新所有請求的Authorization字段。這樣,即使token過期,用户也無需手動刷新token即可繼續使用應用程序。

後端方面,需要實現一個token的刷新接口,接收舊的token並返回新的token。在處理刷新請求時,需要對舊的token進行驗證,以確保該請求是合法的。如果驗證通過,則生成新的token並返回給前端,否則返回錯誤信息。

總之,在實現無感刷新token的過程中,前端和後端都需要進行一定的實現。前端需要在請求攔截器中進行token的檢查和更新,後端需要實現一個token的刷新接口,並對舊的token進行驗證。同時,還需要定義token的過期時間,並根據實際情況進行調整

響應攔截器的功能

響應攔截器是前端網絡請求中一個非常重要的概念,它的主要功能是在從服務器接收到響應數據之後,對響應數據進行處理,然後再將其返回到調用處。

響應攔截器的主要功能包括以下幾個方面:

  1. 錯誤處理:響應攔截器可以檢查響應數據中是否存在錯誤信息,例如請求失敗、權限不足等。如果存在錯誤信息,則可以根據實際情況進行處理,例如跳轉到錯誤頁面、顯示錯誤信息等。
  2. 數據處理:響應攔截器可以對響應數據進行處理,例如對數據進行格式化、過濾、排序等操作。這樣可以提高應用程序的可讀性和可維護性。
  3. 統一處理:響應攔截器可以對所有的響應數據進行統一處理,例如添加一些公共的響應頭、對返回的數據進行加密等操作。這樣可以提高應用程序的安全性和可擴展性。
  4. token刷新:響應攔截器可以在響應數據中檢查token的過期時間,如果即將過期,則可以自動進行token的刷新,從而實現無感刷新token的功能。
  5. 緩存處理:響應攔截器可以對響應數據進行緩存處理,例如將響應數據存儲在本地存儲中,以提高應用程序的性能和用户體驗

反問

  • 您對我的面試表現和之後學習前端的建議

看我自己的興趣,看我對之後的哪些技術比較感興趣,然後説了好幾分鐘的一些我都沒聽過的前端技術/(ㄒoㄒ)/~~

一下子就感覺自己的前端之路才剛剛開始起步,後面的前端技術好多呀

  • 之後還會有二面嗎?

楠哥會給我消息,最遲是明天

  • 公司技術棧和技術氛圍

你知道你投遞是小宇宙吧,我們公司小宇宙全是react,然後很詳細的給我介紹了react裏的很多東西

丁香園

面試體驗最差的一家!面試官一進來就挎着個臉,怨氣十足的樣子,隨便問了一個八股就開始手撕,因為當時我心裏想着另一家公司的二面,所以沒怎麼走心了(互相KPI?)

  • 講一下防抖和節流
  • 手撕一下
  • 做個算法(內容我忘記了)

反問環節,不好意思😅完全不想反問你任何問題,然後面試官就嘲諷了我一頓就掛電話了😅

合合信息

面試官是一個聽聲音感覺年紀蠻大的一個人,人很和藹!很愉快的一次面試

上來面試官就自我介紹,然後介紹公司,然後就問我有什麼需要反問的?第一次遇見這種情況哈哈哈

vue響應式原理

http1和http1.1的區別

HTTP(Hypertext Transfer Protocol)是用於在Web上傳輸數據的協議。HTTP / 1.0和HTTP / 1.1是兩個版本的HTTP協議,下面是它們之間的一些區別:

  1. 持久連接:HTTP / 1.1引入了持久連接,這意味着在單個TCP連接上可以發送多個請求/響應對,從而減少了每個請求的延遲。HTTP / 1.0在每個請求/響應之後關閉TCP連接。
  2. 塊傳輸編碼:HTTP / 1.1支持塊傳輸編碼,這意味着可以在接收響應時逐步解壓縮數據,而不必等待整個響應。這對於處理大型響應或流式數據非常有用。
  3. 身份驗證:HTTP / 1.1提供了更安全的身份驗證方法,例如基於令牌的身份驗證方案,可以替代HTTP / 1.0中的基本身份驗證。
  4. 緩存處理:HTTP / 1.1對緩存處理進行了改進,包括新的Cache-Control指令,可以更好地控制緩存行為。
  5. 響應碼:HTTP / 1.1引入了更多的響應碼,例如“100 Continue”,這使得客户端可以更好地控制它們的請求行為。
  6. 主機頭字段:HTTP / 1.1要求在每個請求中都包含主機頭字段,這使得服務器可以更好地處理多個虛擬主機。
  7. 管道化:HTTP / 1.1支持管道化,允許客户端同時發送多個請求,從而提高性能。HTTP / 1.0不支持管道化。

除了前端你其他的學的怎麼樣?比如計算機組成原理和網絡之類的

其他的我不太行誒(這裏希望是別問太難),結果面試官就笑了,説那咱們就不問了

然後就開始閒聊了😂

這裏面試官建議我好好學學計算機基礎那些,那些相比於前後端的技術,可以讓我在AI的衝擊下走的更遠,心裏也蠻有感觸的,畢竟大家都看見了chatGPT帶來的威力,算是一次很輕鬆愉快的面試吧

同程旅行

項目

看得出來面試官是提前對我的簡歷看了很多的,一上來就先對我的項目進行了分析,然後問我最近的一次實習(就這個最近的一次實習直接把我整不會了,一下就緊張了起來😭)和項目是什麼時候

我最近還沒有實習,零經驗,我直接介紹我自己的項目可以嗎?

這裏感覺自己介紹的項目太拉胯了,沒有很好的體現出自己的項目,接下來應該將自己的項目進行概括才行啊

你為什麼要用jsx進行開發組件庫呢?有什麼好處呢?

JSX 是 React 中一種用於編寫組件的語法,它可以將 HTML 和 JavaScript 結合起來,讓開發者更加方便地編寫動態組件。使用 JSX 進行組件庫開發的好處如下:

  1. 增加可讀性和可維護性:JSX 讓代碼看起來更像是 HTML 模板,這使得代碼更容易閲讀和理解,也更容易進行修改和維護。
  2. 提高開發效率:使用 JSX 可以減少開發者在編寫組件時需要編寫的模板代碼,這可以減少代碼量,提高開發效率。
  3. 更好的性能:JSX 可以通過使用虛擬 DOM 來優化組件渲染性能。React 在每次組件更新時會生成新的虛擬 DOM 樹,並與舊的虛擬 DOM 樹進行比較,然後只更新需要更新的部分,從而提高渲染效率。
  4. 易於與 React 集成:React 是一種流行的前端框架,使用 JSX 可以使組件庫更容易與 React 集成,從而提高組件庫的適用性。

綜上所述,使用 JSX 進行組件庫開發可以提高開發效率、可讀性和可維護性,並且可以提高組件渲染性能,從而使組件庫更加適用於 React 等前端框架

pinia在這個項目裏解決了什麼問題

在一個 Vue 3 項目中使用 Pinia 可以解決以下問題:

  1. 簡化狀態管理:Pinia 提供了一個簡潔的 API,使得我們可以更容易地定義和管理狀態,並在整個應用程序中共享它們。
  2. 更好的類型支持:Pinia 提供了一個類型安全的 API,可以讓我們更容易地編寫類型安全的代碼,並減少錯誤。
  3. 更好的可測試性:Pinia 的狀態管理使得我們可以更容易地對 Vue 3 組件進行單元測試,從而提高代碼的可測試性。
  4. 更好的性能:Pinia 的狀態管理實現了基於 Proxy 的響應式系統,從而提高了性能並減少了不必要的重渲染

綜上所述,Pinia 能夠幫助我們更好地管理 Vue 3 應用程序中的狀態,並且提供了更好的類型支持、可測試性和性能,從而使得我們可以更容易地編寫高質量的 Vue 3 應用程序。在這個項目中使用 Pinia,可以提高項目的開發效率和代碼質量

pinia的store你是如何進行設計的

在設計 Pinia 的 store 時,我們通常需要考慮以下幾個方面:

  1. 狀態的劃分:我們需要考慮應用程序中需要管理的狀態,並根據不同的功能和需求進行劃分。通常情況下,我們會將狀態劃分為多個 store,每個 store 管理一部分相關的狀態。
  2. Store 的命名:我們需要為每個 store 提供一個唯一的名稱,以便在整個應用程序中引用它們。
  3. Store 的定義:我們需要定義每個 store 的結構,包括 store 的狀態、getter、mutation 和 action 等。
  4. Store 的註冊:我們需要將定義好的 store 註冊到應用程序中,以便在應用程序的其他地方使用。
  5. Store 的使用:我們需要在組件中使用 store,通過 getter 獲取 store 的狀態,並在需要時通過 mutation 和 action 來修改 store 的狀態。

在實際的應用程序中,我們通常會根據具體的業務需求來設計 store 的結構和劃分。一般來説,我們會將相關的狀態放在一個 store 中,並通過模塊化的方式來組織多個 store,從而實現更好的可維護性和可擴展性。

在使用 Pinia 的過程中,可以參考官方文檔提供的示例和最佳實踐,以便更好地設計和管理 store,從而提高應用程序的性能和可維護性

你的store有進行模塊的拆分嗎?還是説放在一起

在實際應用程序中,將 store 模塊化並組織成多個文件通常是比較好的實踐,這有助於提高應用程序的可維護性和可擴展性。

通常情況下,我們會將 store 模塊化為多個文件,每個文件對應一個 store 模塊,然後通過 Vuex 的模塊化功能來組織它們。每個 store 模塊負責管理自己的狀態、getter、mutation 和 action,並且可以通過模塊間的調用來實現跨 store 的狀態共享。

模塊化的方式可以使得 store 更加易於維護和擴展,因為每個模塊只負責自己的一部分功能,而不會將所有的狀態都放在同一個 store 中,從而導致代碼的臃腫和難以維護。

在使用 Pinia 時,同樣可以將 store 模塊化為多個文件,並使用 Pinia 提供的模塊化功能來組織它們。每個 store 模塊也負責管理自己的狀態、getter、mutation 和 action,並且可以通過其他 store 模塊的調用來實現跨 store 的狀態共享。

綜上所述,將 store 模塊化並組織成多個文件是一個比較好的實踐,可以提高應用程序的可維護性和可擴展性

你做的組件庫當中有遇見什麼困難嗎?可以舉例説明

html5

h5當中新增了哪些

以下是 HTML5 中一些新增的功能和特性:

  1. 新的語義化標籤:HTML5 新增了一些語義化標籤,如 <header><footer><nav><article><section><aside> 等,可以更好地描述頁面的結構和內容,有助於提高頁面的可讀性和可訪問性。
  2. 多媒體支持:HTML5 提供了更好的多媒體支持,包括 <audio><video> 標籤,可以直接在網頁中嵌入音頻和視頻。
  3. 本地存儲:HTML5 提供了本地存儲功能,包括 localStorage 和 sessionStorage,可以在客户端瀏覽器中存儲數據,從而提高應用程序的性能和用户體驗。
  4. Web Workers:HTML5 中新增了 Web Workers,可以在後台線程中執行 JavaScript 代碼,從而提高應用程序的性能和響應速度。
  5. Canvas:HTML5 中新增了 <canvas> 標籤,可以在網頁中繪製各種圖形和動畫,有助於實現更加複雜的交互效果。
  6. 地理位置 API:HTML5 提供了地理位置 API,可以獲取用户的地理位置信息,有助於實現基於地理位置的應用。
  7. Web Socket:HTML5 中新增了 Web Socket,可以實現雙向通信,從而實現更加實時和高效的應用程序。
  8. Web Storage:HTML5 中新增了 Web Storage,可以在客户端瀏覽器中存儲數據,從而提高應用程序的性能和用户體驗。

綜上所述,HTML5 提供了許多新的功能和特性,可以幫助開發人員更加方便地實現一些複雜的應用場景,提高應用程序的性能和用户體驗

html的行內元素和塊級元素的區別,都有哪些

HTML 元素可以分為兩類:行內元素和塊級元素。它們的主要區別在於:

  1. 顯示方式:塊級元素在頁面上以塊的形式展現,它會佔據一整行的空間,可以設置寬度、高度、內邊距和外邊距等屬性。而行內元素則不會獨佔一行,它們在一行內按照從左到右的順序排列,並且不能設置寬度、高度和內邊距等屬性。
  2. 內容模型:塊級元素通常用於包含其他塊級元素或行內元素,可以包含任何其他元素。而行內元素通常用於包含文本或其他行內元素,不能包含塊級元素。
  3. 默認樣式:塊級元素通常具有明顯的外觀特徵,例如:段落 <p> 元素會在前後添加空白,標題 <h1>~<h6> 元素會加粗並換行等等。而行內元素通常沒有這些明顯的外觀特徵,例如:超鏈接 <a> 元素只是有下劃線,並且字體顏色有所變化等等。

以下是一些常見的 HTML 塊級元素和行內元素:

塊級元素:

  • <div>
  • <p>
  • <h1>~<h6>
  • <ul><ol><li>
  • <table>
  • <form>
  • <hr>
  • <header><footer><nav><section> 等 HTML5 新增的語義化標籤

行內元素:

  • <a>
  • <span>
  • <strong><em>
  • <img>
  • <input>
  • <label>
  • <br>
  • <button>
  • <select>
  • <textarea>

img説行內還是塊呢?span説行內還是塊

元素默認是行內元素,但可以通過設置 display 屬性為 block 或 inline-block 等值來改變其顯示方式。

<span> 元素是一個純粹的行內元素,它不能包含塊級元素,但可以包含其他行內元素。

css

盒子模型

盒子模型是指 HTML 元素在渲染時呈現為一個矩形盒子的模型。這個矩形盒子包含了元素的內容、內邊距(padding)、邊框(border)和外邊距(margin)等部分。

具體來説,盒子模型包含以下幾個部分:

  1. 內容區域(content area):元素內部的實際內容,包括文本、圖像、嵌套的元素等。
  2. 內邊距(padding):內容區域和邊框之間的空白區域,可以通過 CSS 屬性 paddingpadding-* 來設置。
  3. 邊框(border):包圍內容和內邊距的線條,可以通過 CSS 屬性 borderborder-* 來設置。
  4. 外邊距(margin):邊框和周圍元素之間的空白區域,可以通過 CSS 屬性 marginmargin-* 來設置。

盒子模型在 Web 頁面佈局中扮演着重要的角色,可以用來控制元素的尺寸、位置、邊距和內邊距等方面的表現。默認情況下,元素的尺寸是指內容區域的大小,但是通過設置 box-sizing 屬性可以改變元素尺寸的計算方式,使其包括內邊距和邊框

will-change

CSS 屬性 will-change 為 web 開發者提供了一種告知瀏覽器該元素會有哪些變化的方法,這樣瀏覽器可以在元素屬性真正發生變化之前提前做好對應的優化準備工作。這種優化可以將一部分複雜的計算工作提前準備好,使頁面的反應更為快速靈敏

鏈接:http://developer.mozilla.org/zh-CN/docs/Web/CSS/will-change

垂直居中佈局

translate是基於自身還是父元素

translate() 是一種 CSS transform 函數,用於在元素上進行平移變換。它是相對於元素自身的位置進行平移,而不是相對於父元素或其他元素。

具體來説,translate() 函數接受兩個參數:translate(x, y),其中 xy 分別代表在水平和垂直方向上的平移距離。如果只給出一個參數,則默認為水平方向上的平移。

translate() 函數的平移距離是相對於元素自身的初始位置進行計算的,而不受到父元素的影響。因此,如果我們想要實現相對於父元素進行平移,可以考慮使用 position 屬性和 topbottomleftright 屬性來控制元素的位置

移動端兼容問題你是如何解決的

移動端兼容問題是前端開發中非常重要的一環,以下是我通常採用的一些解決方法:

  1. 使用移動端適配方案:移動端設備的屏幕尺寸和像素密度差異較大,因此需要使用適配方案來保證頁面在不同屏幕上顯示效果一致。常見的適配方案有:rem、vw/vh、flexible.js 等。
  2. 使用 CSS3 和 ES6 功能時需要進行前綴處理:移動端的瀏覽器兼容性不如 PC 端,因此在使用 CSS3 和 ES6 功能時需要進行前綴處理,例如 -webkit--moz--ms--o- 等。
  3. 避免使用過多的圖片和動畫效果:移動設備的網絡環境和硬件性能相對較弱,因此需要儘量減少頁面中的圖片數量和動畫效果,以提高頁面的加載速度和流暢性。
  4. 使用移動端專用的 UI 組件和交互方式:移動設備的操作方式和 PC 端有較大差異,因此需要使用移動端專用的 UI 組件和交互方式,例如滑動、輕掃、長按等。
  5. 針對不同設備的瀏覽器進行測試:移動設備的瀏覽器種類繁多,不同瀏覽器在兼容性上也有所不同,因此需要在開發完成後對不同設備的瀏覽器進行測試,以確保頁面在各種瀏覽器上的兼容性。

除此之外,還可以使用一些工具來幫助解決移動端兼容問題,例如 Autoprefixer 可以自動添加 CSS3 前綴,FastClick 可以解決移動端點擊事件的延遲等

css的相對單位有哪些

在 CSS 中,相對單位有以下幾種:

  1. em:相對於父元素的字體大小。例如,如果父元素的字體大小為 16px,子元素的 font-size 設為 1.5em,則子元素的字體大小為 24px。
  2. rem:相對於根元素的字體大小。例如,如果根元素的字體大小為 16px,元素的 font-size 設為 1.5rem,則元素的字體大小為 24px。與 em 不同的是,rem 取決於根元素的字體大小,而不是父元素的字體大小。
  3. vwvh:相對於視口寬度和高度的百分比。例如,如果視口寬度為 1000px,元素的 width 設為 50vw,則元素的寬度為 500px。
  4. vminvmax:相對於視口寬度和高度中較小或較大的那個值的百分比。例如,如果視口寬度為 1000px,視口高度為 800px,元素的 width 設為 50vmin,則元素的寬度為 400px(因為視口寬度為較大的值,所以按照視口寬度計算)。
  5. %:相對於父元素的寬度或高度的百分比。例如,如果父元素的寬度為 1000px,元素的 width 設為 50%,則元素的寬度為 500px。

相對單位與絕對單位(如像素、英寸等)相比,具有更好的響應式特性,可以根據不同的屏幕尺寸和設備類型自適應地調整大小,因此在響應式設計中得到廣泛應用

計算機網絡

輸入URL

渲染進程

js

普通數據類型存儲在哪裏?堆還是棧

在 JavaScript 中,普通數據類型的值通常存儲在棧內存中。棧是一種後進先出的數據結構,可以高效地管理函數調用和局部變量。

當我們聲明一個變量並賦值時,JavaScript 引擎會為該變量分配一段棧內存,並將變量的值存儲在其中。當該變量不再使用時,這段棧內存也會被釋放,變成可用的空間。

常見的普通數據類型包括數字、字符串、布爾值、null 和 undefined 等。這些類型的值都比較簡單,不需要過多的內存空間來存儲,因此通常存儲在棧內存中。

與普通數據類型不同,引用數據類型(如對象、數組、函數等)的值存儲在堆內存中。堆內存是一種動態分配的內存空間,可以存儲複雜的數據結構和對象。

當我們聲明一個引用類型的變量時,JavaScript 引擎會為該變量分配一段棧內存,並將其指向堆內存中的實際值。因為引用類型的值通常比較複雜,包含大量的屬性和方法,因此需要較大的內存空間來存儲

深拷貝和淺拷貝的區別。 讓你實現一個深拷貝的思路

深拷貝和淺拷貝都是對於複雜數據類型進行復制的操作,區別在於複製的方式不同。

淺拷貝是指創建一個新對象,這個新對象有着原始對象屬性值的一份精確拷貝,如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內存地址,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。

深拷貝是指創建一個新對象,這個新對象的值和原始對象的值完全沒有關聯,即便原始對象中有引用類型的屬性,新對象也會開闢新的內存地址,完全拷貝一份新的對象,修改一個對象不會影響到另一個對象。

一個實現深拷貝的思路是:

  1. 首先判斷需要拷貝的對象是否是引用類型,如果是基本類型則直接返回該值。

  2. 如果是引用類型,則創建一個新的空對象或數組(取決於原始對象的類型)。

  3. 遍歷原始對象的所有屬性或元素,將它們的值遞歸地拷貝到新對象中,這個遞歸過程需要注意以下幾點:

    • 如果屬性或元素的值是基本類型,則直接複製該值;
    • 如果屬性或元素的值是引用類型,則遞歸調用深拷貝函數,並將結果賦值給新對象的相應屬性或元素。
  4. 返回新對象或數組

function deepCopy(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } const newObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { newObj[key] = deepCopy(obj[key]); } return newObj; }

除了這個方法你還有其他的思路嗎?json如果來做深拷貝存在哪些問題

除了遞歸拷貝之外,還有其他實現深拷貝的思路:

  1. 使用Object.assign()方法實現淺拷貝,然後對於每個屬性值是引用類型的屬性,再遞歸調用深拷貝函數。
  2. 使用ES6的展開運算符(…)實現淺拷貝,然後對於每個屬性值是引用類型的屬性,再遞歸調用深拷貝函數。
  3. 使用第三方庫,如Lodash的_.cloneDeep()方法,該方法能夠遞歸地深拷貝一個對象。

使用JSON.stringify()和JSON.parse()方法進行深拷貝是一種常見的錯誤做法。雖然這種方法能夠將一個對象序列化為JSON字符串,再將JSON字符串解析為一個新對象,但是存在以下幾個問題:

  • 該方法只能序列化對象中的可枚舉屬性,不能序列化對象的原型鏈和方法。
  • 如果對象中有循環引用(即一個對象引用了自身),則該方法會拋出錯誤。
  • 該方法不能序列化RegExp、Date、Map、Set等特殊類型的對象,會將其序列化為字符串或空對象。

因此,使用JSON.stringify()和JSON.parse()方法進行深拷貝並不可靠,建議使用其他方法實現深拷貝

箭頭函數的作用,箭頭函數和普通函數的區別

箭頭函數是ES6中新增的一種函數定義方式,主要有以下幾個作用:

  1. 簡化函數定義:箭頭函數可以使用更短的語法定義函數,省略了function關鍵字和return語句。
  2. 更簡潔的this指向:箭頭函數沒有自己的this,它的this指向最近的一層非箭頭函數作用域的this,可以避免this指向混亂的問題。
  3. 更簡潔的代碼結構:箭頭函數通常可以使代碼更加簡潔易懂,特別是當需要傳遞迴調函數或者進行函數式編程時,箭頭函數可以使代碼更加簡潔易讀。

與普通函數相比,箭頭函數的主要區別在於this的指向和函數定義的語法結構:

  1. this指向:箭頭函數的this指向在定義時就已經確定了,指向最近的一層非箭頭函數作用域的this。而普通函數的this指向在運行時才能確定,可能會受到調用方式、綁定方式等多種因素的影響。
  2. 語法結構:箭頭函數省略了function關鍵字和return語句,更加簡潔明瞭。同時,箭頭函數的參數只有一個時可以省略括號,而普通函數的參數需要用括號括起來。

需要注意的是,由於箭頭函數的this指向與普通函數不同,因此在某些場景下可能會出現錯誤的結果。此外,箭頭函數也不能作為構造函數使用,因為它沒有自己的this。因此,需要根據實際情況選擇合適的函數定義方式

箭頭函數的this指向哪裏?它的this可以被改變嗎

箭頭函數的this指向在函數定義時就已經確定了,它的this指向的是定義時所在的作用域中的this,而不是在調用時所在的作用域。

具體來説,箭頭函數的this指向最近的一層非箭頭函數作用域的this。如果箭頭函數本身沒有定義作用域,則指向全局對象。這與普通函數不同,普通函數的this指向在調用時才能確定,可能會受到調用方式、綁定方式等多種因素的影響。

由於箭頭函數的this指向在定義時就已經確定,因此它的this不能被改變。即使使用apply、call等方法來改變this指向,也無法改變箭頭函數的this指向。

需要注意的是,箭頭函數的this指向是靜態的,不能動態改變,因此在某些場景下可能會出現錯誤的結果。在這種情況下,可以使用普通函數來替代箭頭函數,或者使用bind方法來綁定this指向

typeof檢測null

使用typeof檢測null的結果是”object“。

這是因為在JavaScript中,null被認為是一個空對象引用。雖然它不是對象,但typeof檢測null的結果是”object”,這是一個歷史遺留問題。在ES6中,通過Symbol.hasInstance方法可以正確地檢測null,但它並不常用。如果需要判斷一個值是否為null,可以直接使用嚴格等於(===)運算符,因為null只等於它本身,不等於任何其他值

瞭解微前端嗎?微前端目前業內的解決方案 阿里的乾坤瞭解嗎

微前端是一種將前端應用程序拆分為更小、更容易管理的部分的架構風格,每個部分可以獨立地開發、測試、部署和擴展。它的主要目的是解決單體應用程序的複雜性和可維護性問題,以及不同應用程序之間的耦合問題。

在業內,目前有許多微前端的解決方案,包括Single-SPA、qiankun、Mosaic、Piral、Luigi等等。這些解決方案都有各自的優缺點和適用場景,可以根據實際需求進行選擇。

阿里的qiankun是一種在React、Vue、Angular等前端框架上實現微前端的解決方案。它使用了主應用和子應用的概念,主應用負責整體框架的搭建和管理子應用,子應用則可以使用不同的前端框架進行開發。qiankun提供了統一的路由、狀態管理、樣式隔離等功能,可以有效地實現微前端架構

微前端的好處

  1. 技術棧無關性:不同的團隊可以使用不同的技術棧來開發不同的微前端應用,而這些應用可以無縫地集成到一個統一的應用中,不會出現技術棧不一致的問題。
  2. 模塊化開發:每個微前端應用都是獨立開發的,可以根據需求進行拆分成多個小模塊,每個模塊可以獨立開發、測試、部署和升級,從而提高了開發效率和代碼質量。
  3. 獨立部署:每個微前端應用都是獨立部署的,可以快速部署新的功能和修復bug,而不需要整個應用重新部署,從而提高了部署效率和靈活性。
  4. 高可維護性:由於每個微前端應用都是獨立開發、測試、部署和升級的,因此可以更容易地維護和更新每個應用,從而提高了整個應用的可維護性。
  5. 更好的擴展性:微前端應用可以在需要時獨立開發和擴展,可以更好地滿足業務需求,同時也可以更容易地擴展到新的平台和設備

數組如何進行扁平化的處理?給你幾個多維數組,將其平展開來

數組扁平化可以將多維數組轉化為一維數組,常用的方法有遞歸方法和非遞歸方法。以下是一些實現方法:

遞歸方法:

``` flatten(arr) { var result = []; arr.forEach(function(item) { if (Array.isArray(item)) { result = result.concat(flatten(item)); } else { result.push(item); } }); return result; }

// 示例 var arr1 = [1, 2, [3, 4], 5, [6, 7, [8, 9]]]; console.log(flatten(arr1)); // [1, 2, 3, 4, 5, 6, 7, 8, 9] ```

非遞歸方法:

``` flatten(arr) { var result = []; var stack = [...arr]; while (stack.length) { var item = stack.pop(); if (Array.isArray(item)) { stack.push(...item); } else { result.unshift(item); } } return result; }

// 示例 var arr2 = [1, 2, [3, 4], 5, [6, 7, [8, 9]]]; console.log(flatten(arr2)); // [1, 2, 3, 4, 5, 6, 7, 8, 9] ```

ES6方法:

``` flatten(arr) { return arr.flat(Infinity); }

// 示例 var arr3 = [1, 2, [3, 4], 5, [6, 7, [8, 9]]]; console.log(flatten(arr3)); // [1, 2, 3, 4, 5, 6, 7, 8, 9] ```

需要注意的是,如果數組元素中包含了對象、函數等複雜類型,則需要根據具體情況進行處理

flat參考鏈接:http://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/flat

場景題

實現一個功能,我在頁面A點擊了一個按鈕,進入頁面B,這個時候我在頁面B進行了一個操作,這時候如何讓A進行一個刷新,也就是如何實現兩個進程的通信

實現兩個進程的通信可以通過以下幾種方式:

  1. 使用瀏覽器的 localStorage 或者 sessionStorage 來存儲需要傳遞的數據,然後在另一個頁面中讀取存儲的數據並進行處理。需要注意的是,存儲的數據類型必須是字符串,因此需要使用 JSON.stringifyJSON.parse 進行數據的轉換。

示例代碼:

在頁面A中:

localStorage.setItem('data', JSON.stringify({ name: 'John', age: 25 })); // 跳轉到頁面B window.location.href = 'pageB.html';

在頁面B中:

// 從localStorage中讀取數據 const data = JSON.parse(localStorage.getItem('data')); // 處理數據 console.log(data.name, data.age); // John 25 // 刪除localStorage中的數據 localStorage.removeItem('data'); // 觸發頁面A的刷新 window.location.reload();

  1. 使用瀏覽器的 window.postMessage 方法進行跨窗口通信。該方法可以在不同窗口之間傳遞數據,並且不同窗口可以處於不同的域名和協議下。

示例代碼:

在頁面A中:

``` // 在A頁面中註冊message事件的監聽器 window.addEventListener('message', (event) => { if (event.origin !== 'http://localhost:3000') { // 如果不是指定的來源,不予處理 return; } // 處理接收到的數據 console.log(event.data); // 觸發頁面A的刷新 window.location.reload(); });

// 在A頁面中向B頁面發送數據 window.open('pageB.html'); ```

在頁面B中:

// 在B頁面中向A頁面發送數據 window.opener.postMessage({ name: 'John', age: 25 }, 'http://localhost:3000');

需要注意的是,該方法存在跨站點腳本攻擊(XSS)的風險,因此需要在處理消息時進行數據的合法性檢驗,確保消息的來源和內容都是可信的。

  1. 使用第三方的庫來實現進程間通信,例如 SignalR、Socket.IO 等。這些庫提供了更高級的通信功能,並且支持實時通信、廣播消息等特性,但也需要相應的服務器支持。

需要根據具體的場景和需求來選擇合適的通信方式

vue的響應式系統。我分了vue2和vue3來講

在 Vue 2 中,Vue 通過 Object.defineProperty() 來實現響應式系統。當一個對象被傳入 Vue 實例進行響應式處理時,Vue 會遍歷這個對象的每一個屬性,並使用 Object.defineProperty() 把這個屬性轉換成 getter 和 setter。當這個屬性被讀取時,getter 會被觸發,這個屬性就會被添加到依賴中;當這個屬性被修改時,setter 會被觸發,這個屬性的依賴就會被通知,並執行相應的更新操作。這樣,當數據被修改時,所有依賴這個數據的地方都會自動更新。

但是,Vue 2 的響應式系統存在一些問題。首先,它只能監聽對象的屬性,而不能監聽新增的屬性和刪除的屬性;其次,它無法監聽數組的變化,只能監聽數組的索引變化,即當使用數組的 push、pop、shift、unshift、splice 等方法時才能觸發更新。

在 Vue 3 中,Vue 引入了 Proxy 對象來實現響應式系統。當一個對象被傳入 Vue 實例進行響應式處理時,Vue 會使用 Proxy 對象對這個對象進行代理,這樣就可以監聽新增的屬性和刪除的屬性,同時也可以監聽數組的變化。當一個屬性被讀取或修改時,Proxy 對象的 get 和 set 方法會被觸發,這樣就可以實現響應式更新。

Vue 3 的響應式系統還有一個優點,就是它支持了多個根節點,也就是 Fragment。這樣可以在不需要添加額外的 DOM 節點的情況下,返回多個元素。

總體來説,Vue 3 的響應式系統更加靈活和高效,能夠更好地應對複雜的應用場景

vue2是如何解決數組檢測的問題

在 Vue 2 中,對於數組的檢測是通過對數組的原型進行改寫來實現的。Vue 2 中通過 Object.defineProperty() 方法對數組原型上的7個變異方法進行重寫,分別是 push()pop()shift()unshift()splice()sort()reverse()。在這些方法被調用時,除了執行它們本身的操作外,還會通知依賴更新。

當數據是數組類型時,Vue 會先判斷該數組是否具有 __ob__ 屬性(Observer 對象),如果有則説明已經被觀測過,直接返回該 __ob__ 對象;如果沒有,則會創建一個 Observer 對象來觀測該數組,然後返回該 __ob__ 對象。

雖然這種方式可以監聽數組的變化,但是存在以下問題:

  1. 監聽不到索引值的變化,比如 arr[1] = newValue
  2. 對象的新增和刪除也需要進行額外處理。
  3. 遍歷數組時會將數組中的每一項都進行依賴收集,造成性能問題。

Vue 提供了 $set 方法,可以用來給數組添加新元素或者修改已有元素的值,使得這些修改也能夠被 Vue 監聽到並更新視圖。例如:

this.$set(this.array, 1, 'new value')

這行代碼會將 array 數組中索引為 1 的元素的值改為 'new value',並通知 Vue 去更新視圖。

除了 $set 方法,Vue 還提供了 $delete 方法來刪除數組中的元素

為了解決這些問題,Vue 3 採用了更加高效的響應式系統

vue2的缺陷,性能問題。如果data裏的層次很深的話,進行多層次的監聽開銷是很大的

Vue 2 在進行響應式處理時,會遞歸遍歷數據對象中的每一個屬性,並將這些屬性轉化為 getter 和 setter。當數據層次比較深時,這種遞歸遍歷的開銷就會非常大,會導致頁面渲染性能下降。

除此之外,Vue 2 還存在以下性能問題:

  1. 每個組件都會創建自己的觀察者 Watcher 實例,當組件數量較多時,會導致大量的內存開銷和性能問題;
  2. 每次數據變化都會導致重新渲染整個組件,即使數據變化的影響僅限於某個子組件;
  3. 在大型列表渲染時,使用 v-for 進行循環渲染會產生大量的 DOM 操作,影響渲染性能

vue的模版解析過程

在 Vue 中,模板是由 HTML 代碼和 Vue 特定的模板語法組成的。Vue 的模板編譯器會將模板編譯成渲染函數,然後再生成 Virtual DOM,最終進行渲染。

下面是 Vue 的模板解析過程:

  1. 解析模板,生成 AST 抽象語法樹:Vue 的編譯器會將模板轉換為 AST 抽象語法樹,這是一個樹形結構,代表了模板的結構,包括元素節點、文本節點、指令等。
  2. 優化 AST:在生成 AST 之後,Vue 的編譯器會對其進行優化,例如靜態節點提取、靜態根節點提取等優化操作,以提高渲染性能。
  3. 生成代碼:最後,Vue 的編譯器會將 AST 轉換為渲染函數的代碼。這個渲染函數是一個 JavaScript 函數,接收一個參數 h,返回一個 Virtual DOM 節點。
  4. 生成 Virtual DOM:渲染函數生成後,Vue 會使用它來生成 Virtual DOM,然後對比新舊 Virtual DOM,計算出需要更新的部分,最終只更新需要更新的部分。
  5. 渲染:最後,Vue 將更新後的 Virtual DOM 渲染到真實的 DOM 中。

這個過程是 Vue 的模板編譯過程的核心,也是 Vue 能夠高效渲染頁面的重要原因

反問

  • 你對我的面試表現怎麼樣

挺好的 基礎問題基本上都回答的出來,前端的廣度還不夠 挺好的

  • 還會有二面嗎?

這個我們得和後面的小組商量一下

  • 同程旅行的技術棧是什麼?

蘇州這邊是vue2為主,做一個低代碼平台的組件化

同程旅行二面

二面是業務部門負責人+HRBP面,很輕鬆愉快的一次面試

基本上就是閒聊,問我對前端的一些看法,還有實習、到時候答辯會怎麼解決之類的,順便問了我為什麼掘金ID叫八歲小孩學編程,是八歲就開始了嗎😂我連忙回答説是亂取的一個名字,然後就是很愉快的交流了

後續

成功OC了,打算去蘇州實習咯,這邊也和輔導員商量好了,等正式offer郵件下來就去打印出來,和學校申請去實習了,我的三月份面試也在這裏畫上了一個還算完美的句號了,謝謝同程的收留哈哈哈,也謝謝除丁香園以外的每個面試官😽