這份前端面試小冊子dog cheng帶來啦~
寫在前面
沒有錯,就是我啦dog cheng,好久不見,從17年在部落格園寫下第一篇文章,轉身間已然兩年,從大二到現在的大四預備畢業生,我仍然在這條道路上前進。秋招早已經結束,在拿到用友,滴滴的offer之後,最終在九月選擇了百度APP,但是我沒有停下,懷著學習的態度完成了一份90頁PDF , 近140+commits的面試小冊之後,寫下了這段文字
網際網路發展迅猛之餘也伴隨著網際網路寒冬,行業不景氣這樣的詞,等畢業季去各個求職網站投簡歷,去各個人才市場找機會,才發現四處碰壁,作為應屆求職者更需要打好基礎,明確發展規劃,跟上行業步伐。下面是本人2019年秋招前端面試經歷,結合個人部落格和牛油們面經中的高頻問題以及行業前輩們複習資料的綜合整理,包含基礎篇、Vue框架篇、HTTP&瀏覽器、構建工具篇、安全篇、演算法篇,歡迎交流斧正,希望大家在畢業季都能一帆風順,從容斬獲OFFER。近100+前端面試題及推薦解答,資源合集,這個冬天不會很冷
因為篇幅有限,下面留下了前兩篇各五道面試題,這個專案已經在github上開源,歡迎大家取用:Github
HTML/CSS
瀏覽器解析渲染頁面過程
大致過程:
HTML解析構建DOM->CSS解析構建CSSOM樹->根據DOM樹和CSSOM樹構建render樹->根據render樹進行佈局渲染render layer->根據計算的佈局資訊進行繪製
不同瀏覽器的核心不同,所以渲染過程其中有部分細節有不一樣,以webkit主流程為例:
一篇很棒的文章:How Browser Work
我有話說:瀏覽器解析渲染頁面過程是一個複雜的過程,其中有不少的細節和規則,如果把上面分享的文章翻譯成譯文,至少有3~5頁PDF左右,所以這裡只能總結大致過程(作為面試回答【很可能讓回答的儘可能詳細】瞭解來說已經足夠,更深入的瞭解可以好好讀下上面那篇文章)
較詳細過程:
HTML解析構建DOM樹:其中HTML Parser就起到了將HTML標記解析成DOM Tree的作用,HTML Parser將文字的HTML文件,提煉出關鍵資訊,巢狀層級的樹形結構,便於計算拓展;這其中也有很多的規則和操作,比如容錯機制,識別特殊標籤<br></br>
等
CSS解析構建CSSOM樹:CSS Parser將很多個CSS檔案中的樣式合併解析出具有樹形結構Style Rules,也叫做CSSOM。
※其中還有一個細節是瀏覽器解析文件:當遇到
<script>
標籤的時候會停止解析文件,立即解析指令碼,將指令碼中改變DOM和CSS的地方分別解析出來,追加到DOM Tree和CSSOM上
根據DOM樹和CSSOM樹構建Render樹:Render Tree的構建其實就是DOM Tree和CSSOM Attach的過程,在webkit中,解析樣式和建立呈現器的過程稱為"附加",每個DOM節點都有一個"attach"方法,Render Tree其實就相當於一個計算好樣式,與HTML對應的Tree
根據Render樹進行佈局渲染render layer:建立渲染樹後,Layout根據根據渲染樹中渲染物件的資訊,計算好每一個渲染物件的位置和尺寸,將其放在瀏覽器視窗的正確位置,某些時候會在文件佈局完成之後進行DOM修改,重新佈局的過程就稱為迴流
※其中計算(樣式計算)一個複雜的過程,因為DOM中的一個元素可以對應樣式表中的多個元素,Firefox採用了規則樹和樣式上下文樹來簡化樣式計算,規則樹包含了所有已知規則的匹配路徑,樣式上下文包含端值,webkit也有樣式物件,但它們不儲存在類似上下文樹這樣的結構中,只是由DOM節點指向此類物件的相關樣式
根據計算的佈局資訊進行繪製:繪製階段則會遍歷呈現樹,並呼叫呈現器的paint方法,將呈現器的內容顯示在螢幕上,繪製的順序其實就是元素進入堆疊樣式上下文的順序,例如,塊呈現器的堆疊順序如下:1.背景顏色,2.背景圖片,3.邊框,4.子代,5.輪廓
移動端點透現象有遇到過嘛
首先需要了解的是,移動端在touch上一共有4個事件,
執行順序為touchstart -> touchmove -> touchend -> touchcancel
當用戶點選螢幕時,會觸發touch和click事件,touch事件會優先處理,touch事件經過捕獲,目標,冒泡一系列流程處理完成之後,才會觸發click,所有我們經常會談到移動端點選事件300ms延遲的問題
移動端點選事件300ms問題,常見的解決方案:
- 阻止使用者雙擊縮放,並限制視口大小
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
- 設定css
touch-action
用於指定某個給定的區域是否允許使用者操作,以及如何相應使用者操作
* {
touch-action: none;
}
- fastclick.js來解決,其原理是在檢測到touchend事件的時候,會通過自定義事件立即觸發模擬一個click事件,並在300ms之後把真正的click事件阻止掉
點透現象:
發生條件:①按鈕A和按鈕B不是後代繼承關係,②A發生touch,A touch後立即消失,B繫結click,③A z-index大於B,即 A 顯示在 B 浮層之上
發生原因:當點選螢幕時,系統生成touch和click兩個事件,touch先執行,touch執行完之後A消失,然後要執行click的時候,就會發現使用者點選的是B,所以就執行了B的click
解決方法:①阻止預設事件,在touch的某個時間段執行event.preventDefault,去取消系統生成的click事件,一半在 touchend 中執行。②要消失的元素延遲300ms後在消失
margin塌陷和合並問題
首先,margin塌陷是相對於父子級關係的兩個元素,而margin合併是相對兩個兄弟級關係的兩個元素
兩個兄弟級關係的元素,垂直方向上的margin,其外邊距會發生重疊現象,兩者兩個的外邊距取的是兩個所設定margin的最大值,就是所說的margin合併問題
兩個父子級關係的元素,垂直方向上的margin會粘合在一起,外層和模型的margin-top取兩個元素中margin-top的最大值,發生margin塌陷的內層元素相對於整個文件移動
解決方案:兩者都可以通過觸發BFC來解決
CSS定位的方式有哪些分別相對於誰
static(預設值)
absolute(絕對定位,相對於最近已定位的父元素,如果沒有則相對於<html>)
fixed(固定定位,相對於視窗)
relative(相對定位,相對於自身)
sticky(2017年瀏覽器開始支援,粘性定位)
absolute會使元素位置與文件流無關,不佔據空間,absolute 定位的元素和其他元素重疊
relative相對定位時,無論元素是否移動,仍然佔據原來的空間
sticky是2017年瀏覽器才開始支援,會產生動態效果,類似relative和fixed的結合,一個例項是"動態固定",生效前提是必須搭配top,left,bottom,right
一起使用,不能省略,否則等同於relative
定位,不產生"動態固定"的效果
移動端佈局的解決方案,平時怎麼做的處理
- 使用Flexbox
- 百分比佈局結合媒體查詢
- 使用rem
rem轉換畫素大小(根元素的大小乘以rem值),取決與頁面根元素的字型大小,即HTML元素的字型大小
em轉換畫素大小(em值乘以使用em單位的元素的字型大小),比如一個div的字型大小為16px,那麼10em就是180px(或者接近它)
rem平時怎麼做的轉換:為了方便計算,時常將html的字型大小設定為62.5%,那麼12px就會是1.2rem
JavaScript
列表無限滾動曾經有遇到過嘛
簡單列表滾動載入是監聽滾動條在滿足條件的時候觸發回撥,然後通過把新的元素加入到頁面頁尾的方法完成,但是如果使用者載入過多列表資料(比如我這一個列表頁有一萬條資料需要展示),那麼使用者不斷載入,頁面不斷增加新的元素,很容易就導致頁面元素過多而造成卡頓,所以就提出的列表的無限滾動載入,主要是在刪除原有元素並且維持高度的基礎上,生成並載入新的資料
如果滾動過快怎麼辦,高頻率觸發事件解決方案-防抖和節流
節流:在一段時間內不管觸發了多少次都只認為觸發了一次,等計時結束進行響應(假設設定的時間為2000ms,再觸發了事件的2000ms之內,你在多少觸發該事件,都不會有任何作用,它只為第一個事件等待2000ms。時間一到,它就會執行了。 )
//時間戳方式
function throttle(fn,delay){
let pre = Date.now();
return function(){
let context = this;
let args = arguments;
let now = Date.now();
if(now - pre > delay){
fn.apply(context,args);
pre = Date.now();
}
}
}
//定時器方式
function throttle(fn,delay){
let timer = null;
return function(){
let context = this;
let args = arguments;
if(!timer){
timer = setTimeout(function(){
fn.apply(context,args);
timer = null;
},delay)
}
}
}
防抖:在某段時間內,不管你觸發了多少次事件,都只認最後一次(假設設定時間為2000ms,如果觸發事件的2000ms之內,你再次觸發該事件,就會給新的事件新增新的計時器,之前的事件統統作廢。只執行最後一次觸發的事件。)
//定時器方式
function debounce(fn,delay){
let timer = null;
return function(){
let context = this;
let args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
fn.apply(context,args);
},delay)
}
}
解釋一下變數提升
JavaScript引擎會先進行預解析,獲取所有變數的聲明覆制undefined,然後逐行執行,這導致所有被宣告的變數,都會被提升到程式碼的頭部(被提升的只有變數,值不會被提升),也就是變數提升(hoisting)
console.log(a) // undefined
var a = 1
function b(){
console.log(a)
}
b() // 1
預解析階段會先獲的變數a賦值undefined,並將var a = undefined放在最頂端
,此時a = 1還在原來的位置,實際就是:
var a = undefined
console.log(a) // undefined
a = 1
function b(){
console.log(a)
}
b() // 1
然後會是執行階段,逐行執行造成了打印出a是undefined
js為什麼0.1+0.2不等於0.3
主要是因為JavaScript同樣採用IEEE754標準,在64位中儲存一個數字的有效數字形式
第0位表示符號位,0表示整數1表示負數,第1~11位儲存指數部分,第12~63位存小數部分;
由於二進位制的有效數字總是表示為1.xxx...
這樣的形式,尾數部分在規約形式下的第一位預設為1,故儲存時第一位省略不寫,尾數部分儲存有效數字小數點後的xxx...,最長52位,因此,JavaScript提供的有效數字最長為53個二進位制位(尾數部分52位+被省略的1位)
由於需要對求和結果規格化(用有效數字表示),右規導致低位丟失,此時需對丟失的低位進行舍入操作,遵循IEEE754舍入規則,會有精度損失
對eventloop事件迴圈機制的瞭解
首先,JavaScript一大特點就是單執行緒,這樣的設計讓它在同一時間只做一件事;作為瀏覽器指令碼語言,JavaScript的主要用途是與使用者互動,以及操作DOM,避免了複雜性,比如假設JavaScript有兩個執行緒,那麼在同一時間進行新增刪除節點操作,為瀏覽器分辨以哪個執行緒為準帶來困難,所以單執行緒是它作為瀏覽器指令碼語言的優勢,也是它的核心特徵。
注:雖然為了利用多核CPU的計算能力,HTML5提出了web worker標準,允許JavaScript建立多個執行緒,但是子執行緒完全受主執行緒控制,且不得操作DOM,所以也並沒有改變JavaScript單執行緒的本質
那麼,單執行緒就意味著,所有任務需要排隊,前一個任務結束才會執行後一個任務,所以為了提高CPU的利用效率,就把所有任務分成了同步任務(synchronous)和非同步任務(asynchronous),同步任務在主執行緒順序執行,非同步任務則不進入主執行緒而是進入到任務佇列(task queue)中。在主執行緒上會形成一個執行棧,等執行棧中所有任務執行完畢之後,會去任務佇列中檢視有哪些事件,此時非同步任務結束等待狀態,進入執行棧中,開始執行。
主執行緒從任務佇列中讀取事件,這個過程是迴圈不斷的,所以整個的這種執行機制又稱為Event Loop(事件迴圈)
寫在後面
篇幅有限,上面留下了小冊中前兩篇的各五道高頻問題,更多問題以及資源合集,在Github可以直接看到,而且除了Github還提供了其他兩種方案,gitbook和pdf(近90頁),都可以選擇
那麼,最後為了突出主題呢,還是要寫一些對於這份小冊的願景吧:如果你是應屆生(當然,大牛除外),正面臨找前端開發的工作,或者即將成為畢業生的預備生,我相信這份前端面試小冊多多少少會幫到你,在這"不景氣"的"寒冬"之季,我們仍要提高自身綜合素質,堅持技術生活,通過不斷給自己設立小目標並實現來督促自身學習提高,最後就以習大大給我們寄語結束吧—2020願我們只爭朝夕不負韶