一次令人窒息的百度面試
最近接到了百度的面試,個人覺得基礎(chǔ)知識準(zhǔn)備的比較充分,就去網(wǎng)上找了一些百度的面經(jīng),冥冥之中我在眾多的面試題中打開了下邊兩個面試題:
2021百度前端社招面經(jīng)
百度前端面試題分享,帶答案
看完之后我直呼“哇哦~”,全部在我的射程范圍之內(nèi)。我該不會如此幸運(yùn)到問的全會吧。
是的,答案就是不會,我就是沒有幸運(yùn)到問的全會。
話不多說,接下來就回顧下面試的問題。
看簡歷
上來首先是萬年不變的自我介紹,介紹完之后面試官就開始逐行看我的簡歷,并針對簡歷上的項(xiàng)目經(jīng)歷進(jìn)行詢問。詢問的十分詳細(xì)。
如何實(shí)現(xiàn)新手指引
問這個問題的原因是我簡歷上寫到了使用driver.js庫實(shí)現(xiàn)了新手指引。
使用js實(shí)現(xiàn)如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>新手指引功能</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
box-sizing: border-box;
}
.stepBlock {
background-color: burlywood;
margin-right: 20px;
}
.positionStyle{
position: absolute;
z-index: 200;
}
/* 蒙層樣式 */
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .5);
z-index: 100;
}
</style>
</head>
<body>
<section id="mask">
<section class="positionStyle" id="tip"></section>
<section class="positionStyle" id="curStepMask"></section>
</section>
<section style="margin:200px;">
<span id="first" class="stepBlock">
第一步
</span>
<span id="second" class="stepBlock">
第二步
</span>
<span id="third" class="stepBlock">
第三步
</span>
</section>
<section style="margin-top:30px">
<button onclick="setMask()">開始指引</button>
</section>
<script>
const tipDict = [
{ id: 'first', content: '這里是第1步哦' },
{ id: 'second', content: '這里是第2步哦' },
{ id: 'third', content: '這里是最后一步哦,點(diǎn)擊完成按鈕結(jié)束新手指引' },
]
let flag = 0;
function setMask() {
// 添加蒙層
let mask = document.getElementById('mask')
mask.setAttribute('class', 'overlay')
setTip()
}
function removeMask() {
// 移除蒙層
let mask = document.getElementById('mask')
mask.setAttribute('class', '')
// 移除tip提示的子元素
removeTip()
removeStepMask()
}
function setTip() {
if (flag < tipDict.length) {
// 獲取當(dāng)前步驟的元素,以及元素的位置信息,供后續(xù)定位提示信息和覆蓋信息使用
const curStepEle = document.getElementById(tipDict[flag].id)
const bound = curStepEle.getBoundingClientRect()
// 找到id為tip的元素
let ele = document.getElementById("tip")
// 如果存在子元素,先移除
removeTip()
removeStepMask()
// 創(chuàng)建提示信息和下一步的統(tǒng)一父元素,方便后續(xù)移除元素
let node = document.createElement('div')
// 創(chuàng)建提示信息
let tipText = document.createTextNode(tipDict[flag].content)
// 將提示信息插入到父元素
node.appendChild(tipText)
// 創(chuàng)建“下一步”按鈕
let nextBtn = document.createElement('button')
nextBtn.innerHTML = flag === tipDict.length - 1 ? '完成' : '下一步';
nextBtn.onclick = setTip;
// 將按鈕插入到父元素
node.appendChild(nextBtn)
// 設(shè)置統(tǒng)一父元素的位置
ele.style.left = bound.x + 'px'
ele.style.top = bound.y + 20 + 'px'
// 將統(tǒng)一的父元素插入到id為tip的元素
ele.appendChild(node)
// 將當(dāng)前步驟高亮顯示
let tag = flag - 1
if (tag >= 0) {
document.getElementById(tipDict[tag].id).style = ''
}
// const curStepEle = document.getElementById(tipDict[flag].id)
// const bound = curStepEle.getBoundingClientRect()
const curStepMask = document.getElementById('curStepMask')
curStepMask.style.left = bound.x + 'px'
curStepMask.style.top = bound.y + 'px'
const curStepEleClone = curStepEle.cloneNode(true)
curStepMask.appendChild(curStepEleClone)
flag++
} else {
flag = 0;
removeMask()
}
}
function removeStepMask() {
let ele = document.getElementById('curStepMask')
let child = ele.lastElementChild
if (child) {
ele.removeChild(child)
}
}
function removeTip() {
let ele = document.getElementById("tip")
let child = ele.lastElementChild
if (child) {
ele.removeChild(child)
}
}
</script>
</body>
</html>
復(fù)制代碼
注意元素中包含如下結(jié)構(gòu):
<section id="mask">
<section class="positionStyle" id="tip"></section>
<section class="positionStyle" id="curStepMask"></section>
</section>
復(fù)制代碼
實(shí)現(xiàn)思路是:
點(diǎn)擊“開始指引”:找到id為mask的元素,為該元素添加蒙層樣式(setMask) 添加提示信息:找到id為tip的元素,將提示信息添加為該元素的子元素(setTip) 高亮當(dāng)前步驟元素:找到當(dāng)前目標(biāo)元素,克隆目標(biāo)元素,然后將克隆后的目標(biāo)元素添加為curStepMask的子元素(setTip) 定位tip和curStepMask的元素:curStepMask元素在當(dāng)前目標(biāo)元素的正上方,tip元素根據(jù)情況而定 每次添加當(dāng)前提示信息時要移除上一次添加的提示信息和覆蓋元素(removeTip,removeStepMask)
getBoundingClientRect
返回值是一個 `DOMRect`[3] 對象,是包含整個元素的最小矩形(包括
padding和border-width)。該對象使用left、top、right、bottom、x、y、width和height這幾個以像素為單位的只讀屬性描述整個矩形的位置和大小。除了width和height以外的屬性是相對于視圖窗口的左上角來計(jì)算的。--MDN

圖片上有一個人臉,除了臉部以外加上蒙層
方案一:
添加遮罩層,在圖片上方添加一張只有人臉的圖片:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圖片添加蒙層</title>
<style type="text/css">
img {
position: absolute;
top: 50%;
left: 50%;
width: 300px;
}
.overlay {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, .5);
z-index: 100;
}
</style>
</head>
<body>
<div class="overlay">
<img src="../images/mask.png" style="width:200px" />
</div>
<img src="../images/cat.png" />
</body>
</html>
復(fù)制代碼
最終實(shí)現(xiàn)效果:(沒有用一模一樣的圖片,只是模擬了類似的效果)

上述是在整個頁面添加蒙層,若想只在圖片部分添加蒙層:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圖片添加蒙層</title>
<style type="text/css">
img {
width: 300px;
}
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, .5);
z-index: 100;
}
</style>
</head>
<body>
<div style="position: relative;width: 300px;">
<div class="overlay">
<img src="../images/kid.png" style="width:200px" />
</div>
<img src="../images/cat.png" />
</div>
</body>
</html>
復(fù)制代碼
效果:

echarts動畫實(shí)現(xiàn)原理
對Echarts目前處于使用的水平,原理后續(xù)學(xué)習(xí)的話再補(bǔ)上吧~~~
了解canvas嗎
HTML5 <canvas> 元素用于圖形的繪制,通過腳本 (通常是JavaScript)來完成。
<canvas> 標(biāo)簽只是圖形容器,必須使用腳本來繪制圖形。
可以通過多種方法使用 canvas 繪制路徑,盒、圓、字符以及添加圖像。
canvas教程傳送門[4]
如何實(shí)現(xiàn)組件滑動切換效果
不使用react-transition-group
對語義化的理解
語義化就是正確的標(biāo)簽做正確的事。語義化的好處在于:
對于開發(fā)團(tuán)隊(duì)而言,代碼更加容易維護(hù) 在css沒有加載出來的情況下也能很好的展示結(jié)構(gòu) 有利于SEO優(yōu)化 更好地支持各種終端,例如無障礙閱讀和有聲小說等
HTML5有哪些語義化標(biāo)簽
常用的語義化標(biāo)簽有:
header:定義頁眉信息 nav:導(dǎo)航欄 section:頁面的組成部分 footer:腳注信息 aside:側(cè)邊欄信息,比如菜單或者廣告等
less 多處用到px轉(zhuǎn)換為vw 如何實(shí)現(xiàn)
sass中可以定義函數(shù),接收參數(shù)并且返回計(jì)算值:
/*比如:在父元素字體大小為 12px 的容器內(nèi)繪制圖形交互*/
@function pxToEm ($px) {
@return ($px/12) + em;
}
# Sass
.box {
width: pxToEm(36);
}
# CSS
.box {
width: 3em;
}
復(fù)制代碼
less中函數(shù)是內(nèi)置的不能夠自定義,所以可以使用混入:
/*
將寬度為 375px 的 UI 設(shè)計(jì)稿轉(zhuǎn)換成使用單位 vw 來適配移動端的網(wǎng)頁。
避免編譯:~' 值 '
*/
.pxToVW (@px, @attr: width) {
@vw: (@px / 375) * 100;
@{attr}: ~"@{vw}vw";
}
# Less
.box {
.pxToVW(75);
.pxToVW(150, height);
}
# CSS
.box {
width: 20vw;
height: 40vw;
}
復(fù)制代碼
less傳送門[5]
vue-router中router和route的區(qū)別
router是路由實(shí)例對象,包含一些路由跳轉(zhuǎn)方法,比如push。
route是路由信息對象,包含和路由相關(guān)的一些信息,比如params,location等。
vue單頁面應(yīng)用無刷新更新組件怎么實(shí)現(xiàn)的
我理解面試官詢問的點(diǎn)在于vue-router兩種模式下如何實(shí)現(xiàn)的url到組件的映射。
hash模式
hash模式是vue-router的默認(rèn)模式。hash指的是url描點(diǎn),當(dāng)描點(diǎn)發(fā)生變化的時候,瀏覽器只會修改訪問歷史記錄,不會訪問服務(wù)器重新獲取頁面。因此可以監(jiān)聽描點(diǎn)值的變化,根據(jù)描點(diǎn)值渲染指定dom。
改變描點(diǎn)
可以通過location.hash = "/hashpath"的方式修改瀏覽器的hash值。
監(jiān)聽描點(diǎn)變化
可以通過監(jiān)聽hashchange事件監(jiān)聽hash值的變化。
window.addEventListener('hashchange', () => {
const hash = window.location.hash.substr(1)
// 根據(jù)hash值渲染不同的dom
})
復(fù)制代碼
history模式
通過mode選項(xiàng)開啟history模式,history 模式和 hash 模式的區(qū)別在于:
history模式中不帶有“#”,更加美觀 history模式當(dāng)用戶刷新或直接輸入地址時會向服務(wù)器發(fā)送一個請求,所以history模式需要服務(wù)端同學(xué)進(jìn)行支持,將路由都重定向到根路由
改變url
H5的history對象提供了pushState和replaceState兩個方法,當(dāng)調(diào)用這兩個方法的時候,url會發(fā)生變化,瀏覽器訪問歷史也會發(fā)生變化,但是瀏覽器不會向后臺發(fā)送請求。
// 第一個參數(shù):data對象,在監(jiān)聽變化的事件中能夠獲取到
// 第二個參數(shù):title標(biāo)題
// 第三個參數(shù):跳轉(zhuǎn)地址
history.pushState({}, "", '/a')
復(fù)制代碼
監(jiān)聽url變化
可以通過監(jiān)聽popstate事件監(jiān)聽history變化,也就是點(diǎn)擊瀏覽器的前進(jìn)或者后退功能時觸發(fā)。
window.addEventListener("popstate", () => {
const path = window.location.pathname
// 根據(jù)path不同可渲染不同的dom
})
復(fù)制代碼
從某種程度來說,調(diào)用
pushState()和window.location = "#foo"基本上一樣,他們都會在當(dāng)前的 document 中創(chuàng)建和激活一個新的歷史記錄。但是pushState()有以下優(yōu)勢:
新的 URL 可以是任何和當(dāng)前 URL 同源的 URL。但是設(shè)置 `window.location`[6] 只會在你只設(shè)置錨的時候才會使當(dāng)前的 URL。 非強(qiáng)制修改 URL。相反,設(shè)置 window.location = "#foo";僅僅會在錨的值不是 #foo 情況下創(chuàng)建一條新的歷史記錄。可以在新的歷史記錄中關(guān)聯(lián)任何數(shù)據(jù)。 window.location = "#foo"形式的操作,你只可以將所需數(shù)據(jù)寫入錨的字符串中。注意:
pushState()不會造成 `hashchange`[7] 事件調(diào)用,即使新的 URL 和之前的 URL 只是錨的數(shù)據(jù)不同。----MDN
vue在頁面中如何監(jiān)聽回到上一步的操作
掛載完成后,判斷瀏覽器是否支持popstate
mounted(){
if (window.history && window.history.pushState) {
history.pushState(null, null, document.URL);
window.addEventListener('popstate', this.goBack, false);
}
},
復(fù)制代碼
頁面銷毀時,取消監(jiān)聽。否則其他vue路由頁面也會被監(jiān)聽
destroyed(){
window.removeEventListener('popstate', this.goBack, false);
},
復(fù)制代碼
頁面跳轉(zhuǎn)函數(shù)
methods:{
goBack(){
this.$router.replace({path: '/'});
//replace替換原路由,作用是避免回退死循環(huán)
}
}
復(fù)制代碼
代碼題:回文字符串
function checkStr(str) {
return str === str.split('').reverse().join('')
}
復(fù)制代碼
場景提:一個公告欄,每一天都可以展示,當(dāng)用戶點(diǎn)擊關(guān)閉后今天不顯示,明天(過了今天零點(diǎn))還會顯示
我給出的方案就是在localStorage中存儲用戶關(guān)閉公告欄的時間戳,等再次進(jìn)入頁面的時候判斷是不是存在localStorage:
若不存在則證明從來沒有關(guān)閉過公告欄,那就顯示; 若存在,就判斷時間戳和當(dāng)前時間是否是同一天,不是同一天就顯示
代碼題:命名方式中劃線改小駝峰
方案一:
function transName(arr) {
let res = arr.map(e => {
let items = e.split('-').map((item, index) => {
if (index) {
let first = item.substring(0,1)
let rest = item.substring(1)
return first.toUpperCase()+rest
}else{
return item.toLowerCase()
}
})
return items.join('')
})
return res
}
console.log(transName(['A-b-cee', 'ca-de-ea', 'e-fe-eaa','f-g','mn']))
復(fù)制代碼
方案二:
function turnName(str){
return str.replace(/-[a-zA-Z]/g,match=>match.replace('-','').toUpperCase())
}
復(fù)制代碼
代碼題:命名方式小駝峰改中劃線
let s1 = 'aBBcdE';
let t = s1.replace( /[A-Z]/g, match=>'-'+match.toLowerCase());
console.log(t);
復(fù)制代碼
git commit之后修改上一次commit的信息
剛commit還沒有push
git commit --amend
會進(jìn)入vim編輯器,點(diǎn)擊i,修改commit信息后,點(diǎn)擊esc,輸入ZZ退出。
git log 可以看見最近c(diǎn)ommit信息
剛push,修改最近一次commit
git commit --amend
會進(jìn)入vim編輯器,點(diǎn)擊i,修改commit信息后,點(diǎn)擊esc,輸入ZZ退出。
git log 可以看見最近c(diǎn)ommit信息,pull后再push到遠(yuǎn)程(但是每次pull后再push會導(dǎo)致覆蓋原來的更改,后來直接強(qiáng)制推送成功了:git push origin HEAD:master --force)
修改歷史push的commit信息
git rebase -i HEAD~3
表示要修改當(dāng)前版本的倒數(shù)第三次狀態(tài).
這個命令出來之后,會出來三行東東:
pick:*******
pick:*******
pick:*******
復(fù)制代碼
如果你要修改哪個,就把那行的pick改成edit,然后保存退出(點(diǎn)擊esc,輸入ZZ退出)
這時通過git log你可以發(fā)現(xiàn),git的最后一次提交已經(jīng)變成你選的那個了,這時再使用:
git commit --amend 來對commit進(jìn)行修改。
修改完成后使用git rebase --continue
然后將變化push到遠(yuǎn)程:git push origin HEAD:master --force
webpack優(yōu)化構(gòu)建速度
直接參考這篇文章[8]
前端性能優(yōu)化
頁面渲染優(yōu)化
Webkit 渲染引擎流程:
處理 HTML 并構(gòu)建 DOM 樹 處理 CSS 構(gòu)建 CSS 規(guī)則樹(CSSOM) DOM Tree 和 CSSOM Tree 合成一棵渲染樹 Render Tree。 根據(jù)渲染樹來布局,計(jì)算每個節(jié)點(diǎn)的位置 調(diào)用 GPU 繪制,合成圖層,顯示在屏幕上
避免css阻塞:css影響renderTree的構(gòu)建,會阻塞頁面的渲染,因此應(yīng)該盡早(將 CSS 放在 head 標(biāo)簽里)和盡快(啟用 CDN 實(shí)現(xiàn)靜態(tài)資源加載速度的優(yōu)化)的將css資源加載
避免js阻塞:js可以修改CSSOM和DOM,因此js會阻塞頁面的解析和渲染,并且會等待css資源的加載。也就是說js會搶走渲染引擎的控制權(quán)。所以我們需要給js資源添加defer或者async,延遲js腳本的執(zhí)行。
使用字體圖標(biāo) iconfont 代替圖片圖標(biāo):
圖片會增加網(wǎng)絡(luò)請求次數(shù),從而拖慢頁面加載時間 iconfont可以很好的縮放并且不會添加額外的請求 降低css選擇器的復(fù)雜度:瀏覽器讀取選擇器,遵循的原則是從選擇器的右邊到左邊讀取。
減少嵌套:最多不要超過三層,并且后代選擇器的開銷較高,慎重使用 避免使用通配符,對用到的元素進(jìn)行匹配即可 利用繼承,避免重復(fù)匹配和定義 正確使用類選擇器和id選擇器 減少重繪和回流:
CSS
JavaScript
避免頻繁操作樣式,最好一次性重寫style屬性,或者將樣式列表定義為class并一次性更改class屬性。 避免頻繁操作DOM,創(chuàng)建一個documentFragment,在它上面應(yīng)用所有DOM操作,最后再把它添加到文檔中。 為元素設(shè)置display: none,操作結(jié)束后再把它顯示出來。因?yàn)樵赿isplay屬性為none的元素上進(jìn)行的DOM操作不會引發(fā)回流和重繪。用一次回流替代多次回流 避免頻繁讀取會引發(fā)回流/重繪的屬性,如果確實(shí)需要多次使用,就用一個變量緩存起來。 對具有復(fù)雜動畫的元素生成一個新圖層 避免使用table布局。 盡可能在DOM樹的最末端改變class。 避免設(shè)置多層內(nèi)聯(lián)樣式。 將動畫效果應(yīng)用到position屬性為absolute或fixed的元素上。 避免使用CSS表達(dá)式(例如:calc())。
JS中的性能優(yōu)化
使用事件委托 防抖和節(jié)流 盡量不要使用JS動畫[9],css3動畫[10]和canvas動畫[11]都比JS動畫性能好
圖片的優(yōu)化
雪碧圖:借助減少http請求次數(shù)來進(jìn)行優(yōu)化 圖片懶加載:在圖片即將進(jìn)入可視區(qū)域的時候進(jìn)行加載(后邊有判斷即將進(jìn)入可視區(qū)域的方法) 使用CSS3代替圖片:有很多圖片使用 CSS 效果(漸變、陰影等)就能畫出來,這種情況選擇 CSS3 效果更好
webpack優(yōu)化
代碼壓縮:html,css,js文件壓縮 Tree shaking 去除死代碼 babel-plugin-transform-runtime減少ES6轉(zhuǎn)化ES5的冗余提升打包速度
vue
路由懶加載 合理使用computed和watch v-for添加key v-for的同時避免使用v-if destory時銷毀事件:比如addEventListener添加的事件、setTimeout、setInterval、bus.$on綁定的監(jiān)聽事件等 第三方插件按需引入
react
map循環(huán)展示添加key 路由懶加載 使用scu,memo或者pureComponent避免不必要的渲染
前端的緩存機(jī)制
分為強(qiáng)緩存和協(xié)商緩存。
強(qiáng)緩存不需要客戶端向服務(wù)端發(fā)送請求,有兩種響應(yīng)頭實(shí)現(xiàn)方案:
Expires:值是一個絕時間,在這個時間前緩存有效,但是如果本地時間被修改,會導(dǎo)致緩存失效 Cache-control:值是一個相對時間,單位為秒,資源在這個時間內(nèi)有效
強(qiáng)緩存過期之后會使用協(xié)商緩存,協(xié)商緩存需要客戶端向服務(wù)端發(fā)送請求,資源未過期則服務(wù)端返回304否則返回新的資源。協(xié)商緩存也有兩種實(shí)現(xiàn)方案:
Last-Modified 和 If-Modified-Since: Last-Modified表示本地文件最后修改日期,If-Modified-Since會將Last-Modified的值發(fā)送給服務(wù)器,詢問服務(wù)器在該日期后資源是否有更新,有更新的話就會將新的資源發(fā)送回來。但是如果本地文件被打開,會導(dǎo)致Last-Modified被修改。ETag 和 If-None-Match: ETag類似于文件指紋,If-None-Match會將當(dāng)前ETag發(fā)送給服務(wù)器,詢問該資源ETag是否變動,有變動的話就將新的資源發(fā)送回來。并且ETag優(yōu)先級比Last-Modified高。
如何判斷圖片即將進(jìn)入可視區(qū)域
方案1:clientHeight+scroolTop>offsetTop
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圖片加載優(yōu)化</title>
</head>
<body>
<div style="background-color: green;width:100vw;height:8000px">
</div>
<div id="yellow" style="background-color: yellow;width:100vw;height:800px">
</div>
<script>
document.addEventListener('scroll', () => {
const clientH = document.documentElement.clientHeight//獲取屏幕可視區(qū)域的高度
const scrollT = document.documentElement.scrollTop//獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離
const offsetTop = document.getElementById('yellow').offsetTop//獲取元素相對于文檔頂部的高度
if (clientH + scrollT > offsetTop) {
console.log('進(jìn)入可視區(qū)域啦??!')
}
})
</script>
</body>
</html>
復(fù)制代碼
方案2:下滑過程中bound.top會越來越小
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>圖片加載優(yōu)化</title>
</head>
<body>
<div style="background-color: green;width:100vw;height:8000px">
</div>
<div id="yellow" style="background-color: yellow;width:100vw;height:800px">
</div>
<script>
document.addEventListener('scroll', () => {
var bound = document.getElementById('yellow').getBoundingClientRect(); ////獲取元素的大小及位置
var clientHeight = window.innerHeight;
if (bound.top <= clientHeight) {
console.log('進(jìn)入可視區(qū)域啦')
}
})
</script>
</body>
</html>
復(fù)制代碼
關(guān)于本文
來自:let_code
https://juejin.cn/post/7178783712363708475
