一次令人窒息的百度面試

作者:let_code

https://juejin.cn/post/7178783712363708475

最近接到了百度的面試,個人覺得基礎知識準備的比較充分,就去網上找了一些百度的面經,冥冥之中我在衆多的面試題中打開了下邊兩個面試題:2021 百度前端社招面經 [1],百度前端面試題分享,帶答案 [2]

看完之後我直呼 “哇哦~”,全部在我的射程範圍之內。我該不會如此幸運到問的全會吧。

是的,答案就是不會,我就是沒有幸運到問的全會。

話不多說,接下來就回顧下面試的問題。

看簡歷

上來首先是萬年不變的自我介紹,介紹完之後面試官就開始逐行看我的簡歷,並針對簡歷上的項目經歷進行詢問。詢問的十分詳細。

如何實現新手指引

問這個問題的原因是我簡歷上寫到了使用 driver.js 庫實現了新手指引。

使用 js 實現如下:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta >
    <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: '這裏是最後一步哦,點擊完成按鈕結束新手指引' },
        ]
        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) {
                // 獲取當前步驟的元素,以及元素的位置信息,供後續定位提示信息和覆蓋信息使用
                const curStepEle = document.getElementById(tipDict[flag].id)
                const bound = curStepEle.getBoundingClientRect()

                // 找到id爲tip的元素
                let ele = document.getElementById("tip")

                // 如果存在子元素,先移除
                removeTip()
                removeStepMask()

                // 創建提示信息和下一步的統一父元素,方便後續移除元素
                let node = document.createElement('div')
                // 創建提示信息
                let tipText = document.createTextNode(tipDict[flag].content)
                // 將提示信息插入到父元素
                node.appendChild(tipText)
                // 創建“下一步”按鈕
                let nextBtn = document.createElement('button')
                nextBtn.innerHTML = flag === tipDict.length - 1 ? '完成' : '下一步';
                nextBtn.onclick = setTip;
                // 將按鈕插入到父元素
                node.appendChild(nextBtn)
                // 設置統一父元素的位置


                ele.style.left = bound.x + 'px'
                ele.style.top = bound.y + 20 + 'px'
                // 將統一的父元素插入到id爲tip的元素
                ele.appendChild(node)

                // 將當前步驟高亮顯示
                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>

注意元素中包含如下結構:

<section id="mask"> 
    <section class="positionStyle" id="tip"></section> 
    <section class="positionStyle" id="curStepMask"></section>
</section>

實現思路是:

getBoundingClientRect

返回值是一個 DOMRect[3] 對象,是包含整個元素的最小矩形(包括 paddingborder-width)。該對象使用 lefttoprightbottomxywidthheight 這幾個以像素爲單位的只讀屬性描述整個矩形的位置和大小。除了 widthheight 以外的屬性是相對於視圖窗口的左上角來計算的。 --MDN

圖片上有一個人臉,除了臉部以外加上蒙層

方案一:

添加遮罩層,在圖片上方添加一張只有人臉的圖片:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta >
    <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>

最終實現效果:(沒有用一模一樣的圖片,只是模擬了類似的效果)

上述是在整個頁面添加蒙層,若想只在圖片部分添加蒙層:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta >
    <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>

效果:

echarts 動畫實現原理

對 Echarts 目前處於使用的水平,原理後續學習的話再補上吧~~~

瞭解 canvas 嗎

HTML5 <canvas> 元素用於圖形的繪製,通過腳本 (通常是 JavaScript) 來完成。

<canvas> 標籤只是圖形容器,必須使用腳本來繪製圖形。

可以通過多種方法使用 canvas 繪製路徑, 盒、圓、字符以及添加圖像。

canvas 教程傳送門 [4]

如何實現組件滑動切換效果

不使用 react-transition-group

對語義化的理解

語義化就是正確的標籤做正確的事。語義化的好處在於:

HTML5 有哪些語義化標籤

常用的語義化標籤有:

less 多處用到 px 轉換爲 vw 如何實現

sass 中可以定義函數,接收參數並且返回計算值:

/*比如:在父元素字體大小爲 12px 的容器內繪製圖形交互*/
@function pxToEm ($px) {
  @return ($px/12) + em;
}

# Sass
.box {
    width: pxToEm(36);
}

# CSS
.box {
    width: 3em;
}
複製代碼

less 中函數是內置的不能夠自定義,所以可以使用混入:

/*
將寬度爲 375px 的 UI 設計稿轉換成使用單位 vw 來適配移動端的網頁。
避免編譯:~' 值 '
*/

.pxToVW (@px, @attr: width) {
    @vw: (@px / 375) * 100;
    @{attr}: ~"@{vw}vw"; 
}

# Less
.box {
    .pxToVW(75);
    .pxToVW(150, height);
}

# CSS
.box {
    width: 20vw;
    height: 40vw;
}

less 傳送門 [5]

vue-router 中 router 和 route 的區別

router 是路由實例對象,包含一些路由跳轉方法,比如 push。

route 是路由信息對象,包含和路由相關的一些信息,比如 params,location 等。

vue 單頁面應用無刷新更新組件怎麼實現的

我理解面試官詢問的點在於 vue-router 兩種模式下如何實現的 url 到組件的映射。

hash 模式

hash 模式是 vue-router 的默認模式。hash 指的是 url 描點,當描點發生變化的時候,瀏覽器只會修改訪問歷史記錄,不會訪問服務器重新獲取頁面。因此可以監聽描點值的變化,根據描點值渲染指定 dom。

可以通過location.hash = "/hashpath"的方式修改瀏覽器的 hash 值。

可以通過監聽 hashchange 事件監聽 hash 值的變化。

window.addEventListener('hashchange'() ={
   const hash = window.location.hash.substr(1)
   // 根據hash值渲染不同的dom
})

history 模式

通過 mode 選項開啓 history 模式,history 模式和 hash 模式的區別在於:

  1. history 模式中不帶有 “#”,更加美觀

  2. history 模式當用戶刷新或直接輸入地址時會向服務器發送一個請求,所以 history 模式需要服務端同學進行支持,將路由都重定向到根路由

H5 的 history 對象提供了 pushState 和 replaceState 兩個方法,當調用這兩個方法的時候,url 會發生變化,瀏覽器訪問歷史也會發生變化,但是瀏覽器不會向後臺發送請求。

// 第一個參數:data對象,在監聽變化的事件中能夠獲取到
// 第二個參數:title標題
// 第三個參數:跳轉地址
history.pushState({}, "", '/a')

可以通過監聽 popstate 事件監聽 history 變化,也就是點擊瀏覽器的前進或者後退功能時觸發。

window.addEventListener("popstate"() ={
    const path = window.location.pathname
    // 根據path不同可渲染不同的dom
})

從某種程度來說,調用 pushState()window.location = "#foo"基本上一樣,他們都會在當前的 document 中創建和激活一個新的歷史記錄。但是 pushState() 有以下優勢:

  • 新的 URL 可以是任何和當前 URL 同源的 URL。但是設置 window.location[6] 只會在你只設置錨的時候纔會使當前的 URL。

  • 非強制修改 URL。相反,設置 window.location = "#foo"; 僅僅會在錨的值不是 #foo 情況下創建一條新的歷史記錄。

  • 可以在新的歷史記錄中關聯任何數據。window.location = "#foo"形式的操作,你只可以將所需數據寫入錨的字符串中。

注意: pushState() 不會造成 hashchange[7] 事件調用,即使新的 URL 和之前的 URL 只是錨的數據不同。 ----MDN

vue 在頁面中如何監聽回到上一步的操作

掛載完成後,判斷瀏覽器是否支持 popstate

mounted(){
  if (window.history && window.history.pushState) {
    history.pushState(null, null, document.URL);
    window.addEventListener('popstate', this.goBack, false);
  }
},

頁面銷燬時,取消監聽。否則其他 vue 路由頁面也會被監聽

destroyed(){
  window.removeEventListener('popstate', this.goBack, false);
},

頁面跳轉函數

methods:{
  goBack(){
    this.$router.replace({path: '/'});
    //replace替換原路由,作用是避免回退死循環
  }
}

代碼題:迴文字符串

function checkStr(str) {
    return str === str.split('').reverse().join('')
}

場景提:一個公告欄,每一天都可以展示,當用戶點擊關閉後今天不顯示,明天(過了今天零點)還會顯示

我給出的方案就是在 localStorage 中存儲用戶關閉公告欄的時間戳,等再次進入頁面的時候判斷是不是存在 localStorage:

代碼題:命名方式中劃線改小駝峯

方案一:

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']))

方案二:

function turnName(str){
    return str.replace(/-[a-zA-Z]/g,match=>match.replace('-','').toUpperCase())
}

代碼題:命名方式小駝峯改中劃線

let s1 = 'aBBcdE';

let t = s1.replace( /[A-Z]/g, match=>'-'+match.toLowerCase());
console.log(t);

git commit 之後修改上一次 commit 的信息

剛 commit 還沒有 push

git commit --amend

會進入 vim 編輯器,點擊 i,修改 commit 信息後,點擊 esc,輸入 ZZ 退出。

git log 可以看見最近 commit 信息

剛 push,修改最近一次 commit

git commit --amend

會進入 vim 編輯器,點擊 i,修改 commit 信息後,點擊 esc,輸入 ZZ 退出。

git log 可以看見最近 commit 信息,pull 後再 push 到遠程 (但是每次 pull 後再 push 會導致覆蓋原來的更改,後來直接強制推送成功了:git push origin HEAD:master --force)

修改歷史 push 的 commit 信息

git rebase -i HEAD~3

表示要修改當前版本的倒數第三次狀態.

這個命令出來之後,會出來三行東東:

pick:*******

pick:*******

pick:*******

如果你要修改哪個,就把那行的 pick 改成 edit,然後保存退出 (點擊 esc,輸入 ZZ 退出)

這時通過 git log 你可以發現,git 的最後一次提交已經變成你選的那個了,這時再使用:

git commit --amend 來對 commit 進行修改。

修改完成後使用 git rebase --continue

然後將變化 push 到遠程:git push origin HEAD:master --force

webpack 優化構建速度

前端性能優化

頁面渲染優化

Webkit 渲染引擎流程:

  1. 避免 css 阻塞:css 影響 renderTree 的構建,會阻塞頁面的渲染,因此應該儘早(將 CSS 放在 head 標籤裏)和儘快(啓用 CDN 實現靜態資源加載速度的優化) 的將 css 資源加載

  2. 避免 js 阻塞:js 可以修改 CSSOM 和 DOM,因此 js 會阻塞頁面的解析和渲染,並且會等待 css 資源的加載。也就是說 js 會搶走渲染引擎的控制權。所以我們需要給 js 資源添加 defer 或者 async,延遲 js 腳本的執行。

  3. 使用字體圖標 iconfont 代替圖片圖標:

  1. 降低 css 選擇器的複雜度:瀏覽器讀取選擇器,遵循的原則是從選擇器的右邊到左邊讀取。
  1. 減少重繪和迴流:

    CSS

    JavaScript

JS 中的性能優化

  1. 使用事件委託

  2. 防抖和節流

  3. 儘量不要使用 JS 動畫 [8],css3 動畫 [9] 和 canvas 動畫 [10] 都比 JS 動畫性能好

圖片的優化

  1. 雪碧圖:藉助減少 http 請求次數來進行優化

  2. 圖片懶加載:在圖片即將進入可視區域的時候進行加載(後邊有判斷即將進入可視區域的方法)

  3. 使用 CSS3 代替圖片:有很多圖片使用 CSS 效果(漸變、陰影等)就能畫出來,這種情況選擇 CSS3 效果更好

webpack 優化

  1. 代碼壓縮:html,css,js 文件壓縮

  2. Tree shaking 去除死代碼

  3. babel-plugin-transform-runtime減少 ES6 轉化 ES5 的冗餘

  4. 提升打包速度

vue

  1. 路由懶加載

  2. 合理使用 computed 和 watch

  3. v-for 添加 key

  4. v-for 的同時避免使用 v-if

  5. destory 時銷燬事件:比如 addEventListener 添加的事件、setTimeout、setInterval、bus.$on 綁定的監聽事件等

  6. 第三方插件按需引入

react

  1. map 循環展示添加 key

  2. 路由懶加載

  3. 使用 scu,memo 或者 pureComponent 避免不必要的渲染

前端的緩存機制

分爲強緩存和協商緩存。

強緩存不需要客戶端向服務端發送請求,有兩種響應頭實現方案:

強緩存過期之後會使用協商緩存,協商緩存需要客戶端向服務端發送請求,資源未過期則服務端返回 304 否則返回新的資源。 協商緩存也有兩種實現方案:

如何判斷圖片即將進入可視區域

方案 1:clientHeight+scroolTop>offsetTop

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta >
    <title>圖片加載優化</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//獲取屏幕可視區域的高度
            const scrollT = document.documentElement.scrollTop//獲取瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離
            const offsetTop = document.getElementById('yellow').offsetTop//獲取元素相對於文檔頂部的高度
            if (clientH + scrollT > offsetTop) {
                console.log('進入可視區域啦!!')
            }
        })
    </script>
</body>

</html>

方案 2:下滑過程中 bound.top 會越來越小

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta >
    <title>圖片加載優化</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('進入可視區域啦')
            }
        })
    </script>
</body>

</html>

參考資料

[1]

2021 百度前端社招面經: https://juejin.cn/post/7002233276861513758

[2]

百度前端面試題分享,帶答案: https://juejin.cn/post/6970867290480853006

[3]

DOMRect: https://developer.mozilla.org/zh-CN/docs/Web/API/DOMRect

[4]

canvas 教程傳送門: https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial

[5]

less 傳送門: https://juejin.cn/post/6844903520441729037

[6]

window.location: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/location

[7]

hashchange: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/hashchange_event

[8]

JS 動畫: https://zh.javascript.info/js-animation

[9]

css3 動畫: https://www.runoob.com/css3/css3-animations.html

[10]

canvas 動畫: https://juejin.cn/post/7008811592733655077

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/m-wBQ7Ef_oy4SRluFx5O8A