前言
上週末將自己的阿里面試經歷寫成博文釋出到掘金後,發現很多小夥伴都擼起袖子自己動手,擼起程式碼毫不含糊!我也很喜歡這種學習分享的氛圍,但是公開地洩露名企筆試題目會產生一絲絲不好的影響,對於努力付出的小夥伴也是一種不公平的待遇!正所謂授之於魚不如授之於漁,我更希望通過我的分享能召集大家和我一起探討問題所折射的知識點,而不是探討問題本身。這樣不是更有意思麼?於是,我想把我的大廠面試經歷通過故事的形式分享給大家,如有不對歡迎各路大佬批評、指正!
故事背景
最近火遍大江南北的電視劇《都挺好》相信大家都有看過,尤其是倪大紅老師飾演的“蘇大強”更是火爆全網。不知道大家對蘇大強的印象是什麼,但是這位老爺子卻給我帶來了太多的歡樂。蘇家的老大是位集學歷、才華於一身的程式設計師,已和我等屌絲大眾程式設計師劃清了界限!誰說不孤獨、不加班的程式設計師就不是好程式設計師了?xxx過來捱打。。。
扯遠了,我們迴歸到主題!一想到這裡我就萌生出一種思想,如果蘇大強也會程式設計,那麼他肯定是一位優秀的程式設計師,生活中雞毛蒜皮的事情都能記得清清楚楚,一塊錢都花的明明白白。如果做了程式設計師,記憶體分配,程式碼優化肯定不在話下,想想都令人激動。
前端優化
又是一個風和日麗的週末,憋了一週的大強想找點事情做做,此時看到坐在電腦桌前編碼的明哲,笑眯眯地湊了上去。
“明哲呀,你在幹什麼呢?”大強率先開口道。
“哦,爸,公司讓我做一個網站,這不還沒做完在加班呢,但是我寫的網站就是沒有京東主站做得好,煩死了!”明哲還在訴苦中,老爺子心思早已悄悄地轉移到密密麻麻的電腦螢幕上。
“你這網站開啟時間就這麼慢,反正我是不會用。據一項研究表明:使用者最滿意的開啟網頁時間是2-5秒,如果等待超過10秒,99%的使用者會關閉這個網頁。所以要在網頁載入優化上多花功夫!”老爺子一臉得意的說道,還沒等蘇明哲反映過來,老爺子又搶著說:“在TCP握手之前要進行DNS查詢,也就是域名地址解析,DNS 解析也是需要時間的,可以通過預解析的方式來預先獲得域名所對應的 IP”。老爺子嘴上一邊說道,雙手已經熟練地搭在了最新款MackBook Pro的鍵盤上,只見他瘋狂地在html標籤裡敲下了如下程式碼:
<link rel="dns-prefetch" href="http://example.com" />
複製程式碼
“再說說比較耗時的操作,比如HTTP的請求。你寫的每一個link
、img
、script
標籤都會向伺服器傳送請求,而每個瀏覽器同時向同一臺伺服器傳送請求的個數是有限制的,請求數越多瀏覽器承受的壓力就越大,自然效率就越低。對於圖片資源你要這麼做:”
- 對於簡單的圖片如三角形、矩形等能用css實現的就用樣式實現;
- 把常用的圖示打成iconfont來使用;
- 由於瀏覽器限制了向同一臺伺服器同時傳送請求的次數,所以儘量把你的圖片資源放在多臺CDN上,這裡不限於圖片,js和css等靜態資源同樣適用;
- 對於小圖片用base64編碼後直接使用編碼文字;
- 使用CSS Sprites技術來將圖片資源合併成雪碧圖,webpack不是有這方面的外掛嗎?去GitHub上面搞它一個不就行了;
- 多圖片列表展示完全可以使用懶載入技術來控制圖片的載入;
- 選擇正確的圖片格式:WebP、PNG、JPEG。
“說完資源載入,再說說你的文件結構。如果把樣式表放在文件底部瀏覽器會重繪頁面元素,阻塞內容逐步呈現,從而造成白屏。JS檔案的載入會阻塞DOM樹的構建,也可能會導致白屏的出現。這裡要給你普及一下script的知識點,當script標籤加上defer
屬性以後,表示該JS檔案會並行下載,但是會放到HTML解析完成後順序執行,而script標籤加上async
屬性以後,表示載入和渲染後續文件元素的過程將和JS檔案的載入與執行並行進行。所以你要記住以下兩點:”
- 樣式表放在頭部;
- 指令碼放在底部。
看著一臉委屈的明哲,蘇老無奈地將目標轉向了JS程式碼。“還記得小時候跟你講的事件的三個階段嗎?捕獲階段
,目標階段
,冒泡階段
,可以看到會有事件冒泡,所以在繫結事件的時候要多使用事件委託而不要直接使用事件繫結,這樣會大大提高事件的效率。你看下面這個例子:”
<ul id="ul">
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
</ul>
複製程式碼
希望在點選每個LI的時候可以輸出該標籤的內容,使用事件委託可以這樣寫:
document.getElementById('ul').onclick = function(event) {
var event = event || window.event;
var target = event.target || event.srcElement;
if (target.nodeName.toLowerCase() == 'li') {
alert(target.innerHTML);
}
}
複製程式碼
“你看看這裡監聽了滾動事件,你這樣寫每次滾動條滾動都會觸發該事件,從而執行相應的邏輯操作,這是相當耗時耗力的有一件事情,現在的年輕人寫程式碼都不考慮效能問題嗎?過高的觸發頻率會導致響應速度跟不上觸發頻率,出現延遲,假死或卡頓的現象,對於觸發頻率比較高的函式使用節流來限制,保證一定時間核心心程式碼只執行一次。說到這裡,我看你們每次洗菜、洗手都會把水龍頭開到最大,難道就不能適當擰緊一點減少不必要的浪費?”。說著順勢編寫了個節流函式:
// 節流,每隔一段時間執行一次
function throttle(fn, wait) {
var timer = null;
return function() {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, Array.prototype.slice.call(arguments, 0));
timer = null;
}, wait);
}
}
}
複製程式碼
“你在看看你的搜尋框,看樣子是想實現輸入文字自動聯想匹配結果選擇這樣的功能吧?每次使用者在keypress的時候都要非同步請求介面,這不浪費嗎?你就不會等使用者輸入完了再進行請求,防抖函式聽過沒?”
// 防抖,只執行最後一次
function debounce(fn, wait) {
var timer = null;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, Array.prototype.slice.call(arguments, 0)), wait);
}
}
複製程式碼
此時的明哲被搞得一點顏面都沒有,只好沉默不語。老爺子見勢再次發起攻擊。
“對於DOM操作的優化你也需要知道”。
- 避免頻繁的DOM操作,儘量將操作合併在一起一次性操作;
- 使用class來代替樣式的變更;
- 使用css動畫來代替js動畫;
- 使用requestAnimationFrame代替setInterval操作動畫。
“這麼做的目的都是為了減少瀏覽器的重繪與迴流,requestAnimationFrame 最大的優勢是由系統來決定回撥函式的執行時機,具體一點講,如果螢幕重新整理率是60Hz,那麼回撥函式就每16.7ms被執行一次,如果重新整理率是75Hz,那麼這個時間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟著系統的重新整理步伐走。它能保證回撥函式在螢幕每一次的重新整理間隔中只被執行一次,這樣就不會引起丟幀現象,也不會導致動畫出現卡頓的問題。另外css樣式也不能亂寫,好的css寫法要遵循下面的原則:”
- 正確使用css字首,以解決瀏覽器的相容性;
- 對於可繼承的屬性儘量使用繼承;
- 避免css選擇器巢狀過深,影響效能;
- css reset的內容以及基本內容要單獨提取出來方便所有頁面公用。
“整個專案還需要優化的地方就是構建,同樣你要遵循如下幾點:”
- js混淆;
- 資源壓縮,包括js、css、html和圖片壓縮;
- 對於單頁面應用要考慮公共程式碼的提取和分離,可以充分利用路由做到按需載入。
“瀏覽器還有一個比較重要的操作是會快取站點資源,我們可以充分利用這個優勢來優化我們的網站,減少不必要的流量開支。可以在構建的過程中通過hash來命名資原始檔,只有內容發生了改動的檔案hash才會改變,這樣對於不變的檔案瀏覽器就可以從本地快取方便讀取。除了這種操作之外,還可以藉助http快取,利用服務端來設定快取策略,常見的快取策略有 強制快取
(Cache-control、Expires)和 協商快取
(ETag、Last-Modified),通過設定不同的header來達到控制瀏覽器快取的目的。”
“還有一點優化可能會被忽略,那就是前端的安全性。要注意防止XXS和XSRF攻擊,這裡給出一個比較詳細的 issues 供你參考,效能優化就和理財一模一樣,選擇正確的理財方式就能...”
還沒等蘇老說完,手機簡訊音響起。蘇老看著簡訊內容露出了滿意的微笑。。。