前言
大家好,我是林三心,用最通俗易懂的話講最難的知識點是我的座右銘,基礎是進階的前提是我的初心
一週一個大廠 是我新出的一個系列文章,大概的流程是這樣的:
- 我會收集一些大廠的面經,並試著去回答
- 如果全都會則等待下一週重新一輪
- 如果有不會的,則記錄下來,並去克服它們,寫成文章,然後下一輪
這個系列的目的就是:逼自己學習,寫文章鞏固新知識,且複習舊知識
Taptap
今天是 一週一個大廠 的第一期,今天我們們來複盤一下Taptap的面經,發現不足,並逐一擊破他們吧!
一面
1、換膚都做過什麼處理,有沒有處理過可能改變尺寸的換膚
我分別在在 微信小程式
和 PC管理系統
中做過換膚
- 小程式:我們公司做的是一款遊戲類的小程式,這款小程式中涉及到換膚的地方是閱讀故事情節的彈出抽屜,需求是通過使用者點選按鈕,改變三種皮膚,並且同時改變閱讀文字的顏色。我的解決方案是,利用Map儲存三種皮膚資訊,點選按鈕後會匹配Map中對應的皮膚,進行抽屜背景圖片的替換,並通過正則的方式,修改閱讀文字HTML程式碼,替換style裡的color屬性,達到換膚效果
- PC管理系統:我們公司的一款後臺管理系統,做了
黑夜模式
和白天模式
的切換功能,跟產品確定主題色之後,開始程式設計工作。由於我們的系統是使用antd
元件庫進行編寫的,所以需要引入antd
相應的主題檔案,並封裝相應的切換主題hook
,同時也要儲存主題標識到localStorage
中,確保重新整理後主題復原。接著就是封裝一個AppDarkModeToggle
的一個切換元件,切換後觸發事件,改變html
的data-dark
屬性,通過屬性選擇器,觸發底下標籤的樣式切換,同時改變Header,Sider,Footer
等佈局元件的背景顏色、字型顏色
等的切換
2、i18n在團隊內部都做了哪些實踐?
專案實施
i18n
的意思是 讓產品無需做大的改變就能夠適應不同語言和地區的需要
。在我的專案中也做過 i18n
,我們做的是 官網
和 管理系統
的 國際化語言切換
,切換的是 中文
和 英文
兩種語言,我當時的步驟是
- 1、根據設計稿確定
中文
和英文
的文字情況 - 2、整理成兩個資料夾
en
和zh
,分別存放對應語言的資料 - 3、確定一下資料的形式是物件的鍵值對形式
- 4、通過
vite
的import.meta.globEager
方法,將兩種資料整理成i18n外掛
所需的格式(不同外掛所需格式不同) - 5、在專案初始化時,初始化
i18n外掛
- 6、封裝對應的切換語言元件——
AppLocalePicker
,用來切換主題,同時改變html
標籤的lang
屬性 - 7、同時做好
語言標識
的初始化,避免重新整理後狀態丟失
外掛選擇
我會選擇一些使用起來比較方便的外掛,來實現 i18n
- Vue:Vue我選擇的外掛是
vue-i18n
- React:Reacr我選擇的外掛是
i18next
3、Webpack 遷移 Vite 遇到了哪些問題
- 1、環境變數需要
Vite_
字首 - 2、獲取環境變數方式為
import.meta
3、Vue中使用tsx時報錯:
React is not defined
// vite.config.ts esbuild: { jsxFactory: 'h', jsxFragment: 'Fragment', jsxInject: 'import { h } from "vue";', },
- 4、主題外掛
vite-plugin-theme
- 5、windicss的外掛
vite-plugin-windicss
- 6、儘量不用
commonjs
,使用es module
4、CI/CD做過哪些實踐?
- CI代表了
持續整合
,我理解的意思就是,我們們每天都會編寫程式碼,並且提交到主幹分支上,天天如此,是一個重複性很高的操作,持續整合
我理解就是會以最快的速度幫你把程式碼提交到主分支上,這樣的好處就是,當開發人員數量多的時候,這樣快速的整合,有利於減少開發人員程式碼之間的衝突 - CD代表了
持續部署
,我理解就是既然有持續整合
程式碼的話,而程式碼的整合肯定是為了最後的釋出部署,所以持續部署
就是在自動整合程式碼後,自動部署到環境上,節約了很多人力
曾經在之前的公司裡參與過CI/CD的工作流程,並使用過,但是沒有去深入地實現過,當時公司是使用 Jenkins
5、鑑權有了解過嗎?jwt如何實現踢人,session 和 jwt 鑑權的區別?
session鑑權
session鑑權
是需要服務端儲存的
- 1、使用者登入
- 2、服務端接收到登入請求,生成相應的使用者標識,存在服務端
- 3、服務端將標識傳給前端,這個標識存在
Cookie
中,並存在瀏覽器 - 4、前端接下來每次請求都會帶著這個
Cookie
去請求 - 5、服務端接收到標識,拿標識去資料庫裡匹配,匹配到則接受,匹配不到則不接受
- 6、使用者登出時,前後端都會銷燬這個標識
jwt鑑權
jwt鑑權
是不需要服務端儲存的
- 1、使用者登入
- 2、服務端接收到登入請求,根據使用者資訊,生成一個
token
給前端 - 3、前端接收到
token
,存在了瀏覽器本地快取
中(例如LocalStorage
) - 4、接下來每次請求,都需將
token
帶在請求頭
裡 - 5、服務端解析前端傳來的
token
,有效則接受,無效則返回401
- 6、使用者登出時,只需要前端銷燬這個
token
jwt 踢人
我理解的 jwt踢人
,就是後端控制某個人的 token
無效,我能想到四種方法
- 1、服務端生成
token
的同時,生成一段標識1
,把標識1 + token
發給前端,當我想踢掉某個人時,後端就生成一個新的標識2
,並修改此使用者對應的token為標識2 + token
,那麼下次此使用者下次帶著標識1 + token
訪問時,便會檢驗不通過。這種方法需要用到服務端儲存,違背了jwt鑑權
的初衷 - 2、設定兩個token,一個
access_token
一個refresh_token
,前者過期時間較短,為1小時,後者過期時間較長,為一個月。將refresh_token
存在後端資料庫(例如redis),access_token
發給前端,前端拿著access_token
請求時,都要判斷redis中refresh_token
過期了沒,沒過期的話就通過,或者access_token
過期了,只要refresh_token
還沒過期,就重新整理access_token
返回給前端。那麼想要踢某人下線,就好辦了,直接把refresh_token
從redis中清除就行。缺點跟第1點一樣 - 3、黑名單模式。就是把需要踢的使用者的
token
放在一個陣列裡,此使用者訪問時,遍歷陣列看有沒有這個人,有的話就踢下線 - 4、另外一種踢人模式是,在我們公司的遊戲業務中,踢人下線是使用了
Websocket
的技術,實時將人踢出房間,不涉及token
6、TCP 三次握手 http1.0 http1.1 http2都有哪些區別?
HTTP一直是我的不足之處,記錄下來,記錄下來
7、https,為什麼可以防止中間人攻擊?
推薦我寫的一篇文章我畫了13張圖,用最通俗易懂的話講HTTPS,拿下!
8、氣泡排序
平均時間複雜度
O(n^2)
思路
陣列中有 n 個數,比較每相鄰兩個數,如果前者大於後者,就把兩個數交換位置;這樣一來,第一輪就可以選出一個最大的數放在最後面;那麼經過 n-1(陣列的 length - 1) 輪,就完成了所有數的排序。
實現
//從大到小排序
var array=[10,20,9,8,79,65,100];
//比較輪數
for ( var i=0;i<array.length-1;i++){
//每輪比較次數,次數=長度-1-此時的輪數
for (var j=0;j<array.length-1-i;j++) {
if (array[j] > array[j + 1]) {
var temp = array[i];
array[j] = array[j + 1];
array[j + 1] = temp;
} //end if
}//end for 次數
} //end for 輪數
console.log(array);
二面
1、給你一個已經升序排列的陣列,給一個數字,找一下這個數字在這個陣列裡出現了幾次
思路
既然是升序排列過了,那說明這個數字如果多次出現。那肯定是緊挨著的,所以不需要遍歷完整個陣列,只要以遇到這個數字開始,離開這個數字為結束,這段時間統計完就可以跳出遍歷了,不需要做後面的無用功(這是我僅能想到的優化點)
實現
const arr = [1, 2, 3, 4, 5, 5, 6, 6, 7]
const computed = (arr: number[], num: number): number => {
let flag = false
let sum = 0
for (let item of arr) {
if (item === num) {
sum++
flag = true
continue
}
if (item !== num && flag) break
}
return sum
}
console.log(computed(arr, 5))
2、洗牌演算法,如何驗證這個洗牌演算法可以把牌洗得足夠亂
1、隨機索引I
- 1、建立一個空陣列
- 2、生成一個
0 —— length - 1
的隨機索引,並將此隨機索引對應元素放到新陣列裡 - 3、刪除原陣列中此索引對應元素,原陣列length更新
- 4、重複2、3,直到原陣列
length === 0
5、返回新陣列
const shuffle = (arr: number[]) => { if (!arr.length) return [] let random: number let res: number[] = [] while (arr.length) { random = Math.floor(Math.random() * arr.length) res.push(arr[random]) arr.splice(random, 1) } return res }
2、隨機索引II
- 1、選取陣列(長度n)中最後一個元素
(arr[length-1])
,將其與n個元素中的任意一個交換,此時最後一個元素已經確定 - 2、選取倒數第二個元素
(arr[length-2])
,將其與n - 1
個元素中的任意一個交換 - 3、重複第 1 2 步,直到剩下1個元素為止
const shuffle = (arr: number[]) => {
if (!arr.length) return []
let index = arr.length - 1
let random: number
while (index) {
random = Math.floor(Math.random() * index--)
// 或者
// random = (Math.random() * index--) >>> 0
const temp = arr[index]
arr[index] = arr[random]
arr[random] = temp
// 這樣也行,但是我的eslint不允許哈哈
// [arr[lastIndex], arr[random]] = [arr[random], arr[lastIndex]]
}
return arr
}
3、sort方法
const shuffle = (arr: number[]) => {
return arr.sort(() => 0.5 - Math.random())
}
3、node stream 去取一個超大資料量的日誌,由於記憶體限制每次只能取一部分,現在希望在全部日誌中隨機取一萬條,如何做?
這一道題不會。。記錄下來,記錄下來
4、介紹一下專案 有哪些是由你主導提出的方案做的事情
主導過兩、三個專案:
- 專案A:小程式
- 專案B:官網
- 專案C:後臺管理系統
不足記錄
- TCP 三次握手 http1.0 http1.1 http2都有哪些區別?
- CI/CD的實踐經驗不足
- node stream的使用
結語
我是林三心,一個熱心的前端菜鳥程式設計師。如果你上進,喜歡前端,想學習前端,那我們們可以交朋友,一起摸魚哈哈,摸魚群
題目摘取
此次面經題目摘取自時隔一年半,我,一個卑微的前端菜雞,又來寫面經了