年底了,又到了跳槽季啦,該刷題走起了。這裡總結了一些被問到可能會懵逼的面試真題,有需要的可以看下~
1. 說說JavaScript中有哪些非同步程式設計方式?
1. 回撥函式
f1(f2);
複製程式碼
回撥函式是非同步程式設計的基本方法。其優點是易編寫、易理解和易部署;缺點是不利於程式碼的閱讀和維護,各個部分之間高度耦合 (Coupling),流程比較混亂,而且每個任務只能指定一個回撥函式。
2. 事件監聽
f1.on('done',f2);
複製程式碼
事件監聽即採用事件驅動模式,任務的執行不取決於程式碼的順序,而取決於某個事件是否發生。其優點是易理解,可以繫結多個事件,每個事件可以指定多個回撥函式,可以去耦合, 有利於實現模組化;缺點是整個程式都要變成事件驅動型,執行流程會變得不清晰。
3. 釋出/訂閱
f1: jQuery.publish("done");
f2: jQuery.subscribe("done", f2);
複製程式碼
假定存在一個"訊號中心",某個任務執行完成,就向訊號中心"釋出"(publish)一個訊號,其他任務可以向訊號中心"訂閱"(subscribe)這個訊號,從而知道什麼時候自己可以開始執行,這就叫做 "釋出/訂閱模式" (publish-subscribe pattern),又稱 "觀察者模式" (observer pattern)。該 方法的性質與"事件監聽"類似,但其優勢在於可以 通過檢視"訊息中心",瞭解存在多少訊號、每個訊號有多少訂閱者,從而監控程式的執行。
4. promise物件
f1().then(f2);
複製程式碼
Promises物件是CommonJS工作組提出的一種規範,目的是為非同步程式設計提供 統一介面 ;思想是, 每一個非同步任務返回一個Promise物件,該物件有一個then方法,允許指定回撥函式。其優點是回撥函式是鏈式寫法,程式的流程非常清晰,而且有一整套的配套方法, 可以實現許多強大的功能,如指定多個回撥函式、指定發生錯誤時的回撥函式, 如果一個任務已經完成,再新增回撥函式,該回撥函式會立即執行,所以不用擔心是否錯過了某個事件或訊號;缺點就是編寫和理解相對比較難。
2. 有哪些監控網頁卡頓的方法?
卡頓
網頁的 FPS
網頁內容在不斷變化之中,網頁的 FPS 是隻瀏覽器在渲染這些變化時的幀率。幀率越高,使用者感覺網頁越流暢,反之則會感覺卡頓。
監控卡頓方法
每秒中計算一次網頁的 FPS 值,獲得一列資料,然後分析。通俗地解釋就是,通過 requestAnimationFrame API 來定時執行一些 JS 程式碼,如果瀏覽器卡頓,無法很好地保證渲染的頻率,1s 中 frame 無法達到 60 幀,即可間接地反映瀏覽器的渲染幀率。
var lastTime = performance.now();
var frame = 0;
var lastFameTime = performance.now();
var loop = function(time) {
var now = performance.now();
var fs = (now - lastFameTime);
lastFameTime = now;
var fps = Math.round(1000/fs);
frame++;
if (now > 1000 + lastTime) {
var fps = Math.round( ( frame * 1000 ) / ( now - lastTime ) );
frame = 0;
lastTime = now;
};
window.requestAnimationFrame(loop);
}
複製程式碼
我們可以定義一些邊界值,比如連續出現3個低於20的 FPS 即可認為網頁存在卡頓。
3. 說說script 標籤中的defer 和 async 異同點?
defer
這個屬性的用途是表明指令碼在執行時不會影響頁面的構造。也就是說,指令碼會被延遲到整個頁面都解析完畢後再執行。因此,在script元素中設定defer屬性,相當於告訴瀏覽器立即下載,但延遲執行。
HTML5規範要求指令碼按照它們出現的先後順序執行,因此第一個延遲指令碼會先於第二個延遲指令碼執行,而這兩個指令碼會先於DOMContentLoaded事件執行。在現實當中,延遲指令碼並不一定會按照順序執行,也不一定會在DOMContentLoad時間觸發前執行,因此最好只包含一個延遲指令碼。
對於不支援的瀏覽器,如safari,並不會延遲執行,還是會阻塞瀏覽器渲染。
async
這個屬性與defer類似,都用於改變處理指令碼的行為。同樣與defer類似,async只適用於外部指令碼檔案,並告訴瀏覽器立即下載檔案。但與defer不同的是,標記為async的指令碼並不保證按照它們的先後順序執行。
第二個指令碼檔案可能會在第一個指令碼檔案之前執行。因此確保兩者之間互不依賴非常重要。指定async屬性的目的是不讓頁面等待兩個指令碼下載和執行,從而非同步載入頁面其他內容。
總結
async 和 defer 雖然都是非同步的,不過還有一些差異,使用 async 標誌的指令碼檔案一旦載入完成,會立即執行;而使用了 defer 標記的指令碼檔案,需要在 DOMContentLoaded 事件之前執行。
可以再擴充套件一下:link preload也可以用於js提前載入,和上述兩者的區別? 還有應用場景上的建議
4. margin:auto 為什麼可以實現垂直居中?
margin概念:
margin屬性為給定元素設定所有四個(上下左右)方向的外邊距屬性。這是四個外邊距屬性設定的簡寫。四個外邊距屬性設定分別是: margin-top,margin-right,margin-bottom和margin-left。指定的外邊距允許為負數。
margin的top和bottom屬性對非替換內聯元素無效,例如span
和 code
。
實現垂直居中
想要實現垂直方向的居中可以用絕對定位:
div {
width: 20px;
height: 20px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
複製程式碼
為什麼能實現垂直居中
塊狀水平元素,如div元素(下同),在預設情況下(非浮動、絕對定位等),水平方向會自動填滿外部的容器;如果有margin-left/margin-right,padding-left/padding-right,border-left-width/border-right-width等,實際內容區域會響應變窄。
但是,當一個絕對定位元素,其對立定位方向屬性同時有具體定位數值的時候,流體特性就發生了。具有流體特性絕對定位元素的margin:auto的填充規則和普通流體元素一模一樣,含有以下特性:
-
如果一側定值,一側auto,auto為剩餘空間大小;
-
如果兩側均是auto, 則平分剩餘空間
5. Cdn有哪些優化靜態資源載入速度的方法?
可以參考阿里雲團隊的《CDN之我見》。總結如下:
資源排程:CDN會根據使用者接入網路的ip尋找距離使用者最優路徑的伺服器。排程的方式主要有DNS排程、http 302排程、使用 HTTP 進行的 DNS 排程(多用於移動端); 快取策略和資料檢索:CDN伺服器使用高效的演算法和資料結構,快速的檢索資源和更新讀取快取; 網路優化:從OSI七層模型進行優化,達到網路優化的目的。 L1物理層:硬體裝置升級提高速度 L2資料鏈路層:尋找更快的網路節點、確保 Lastmile 儘量短 L3路由層:路徑優化,尋找兩點間最優路徑 L4傳輸層:協議TCP優化,保持長連線、TCP快速開啟 L7應用層:靜態資源壓縮、請求合併
6. fetch 和 ajax 的區別?
Ajax 技術的核心是XMLHttpRequest 物件(簡稱XHR)。 XHR 為向伺服器傳送請求和解析伺服器響應提供了流暢的介面。能夠以非同步方式從伺服器取得更多資訊,意味著使用者單擊後,可以不必重新整理頁面也能取得新資料。 看一個呼叫例子:
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.txt", true);
xhr.send(null);
複製程式碼
fetch號稱是ajax的替代品,它的API是基於Promise設計的,舊版本的瀏覽器不支援Promise,需要使用polyfill es6-promise,舉個例子:
// 原生XHR
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText) // 從伺服器獲取資料
}
}
xhr.send()
// fetch
fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
})
.then(data => console.log(data))
.catch(err => console.log(err))
複製程式碼
看起來好像是方便點,then鏈就像之前熟悉的callback。 在MDN上,講到它跟jquery ajax的區別,這也是fetch很奇怪的地方:
當接收到一個代表錯誤的 HTTP 狀態碼時,從 fetch()返回的 Promise 不會被標記為 reject, 即使該 HTTP 響應的狀態碼是 404 或 500。相反,它會將 Promise 狀態標記為 resolve (但是會將 resolve 的返回值的 ok 屬性設定為 false ), 僅當網路故障時或請求被阻止時,才會標記為 reject。 預設情況下, fetch 不會從服務端傳送或接收任何 cookies, 如果站點依賴於使用者 session,則會導致未經認證的請求(要傳送 cookies,必須設定 credentials 選項).
7. 求程式碼輸出值(函式/原型 指向問題)?
2
函式也是一個 object, 可以擁有屬性和方法, Foo.getName 被賦值為一個輸出 2 的函式, 所以輸出 2
Foo.getName = function () { console.log(2) }
Foo.getName() // 輸出 2
複製程式碼
4
getName() 父級作用域為 window, 相當於呼叫 window.getName(), Foo() 還未被執行, 不需要考慮 Foo 函式體內對 getName 的影響, 剩下最後兩個
var getName = function () { console.log(4) } // 函式表示式
function getName() { // 函式宣告
console.log(5)
}
複製程式碼
函式宣告會提升, 也就是即使在後邊宣告的函式, 在前別也能呼叫, 如:
getName() // 輸出 5
function getName() { // 函式宣告
console.log(5)
}
複製程式碼
但題中函式表示式會覆蓋函式宣告, 來看下邊這段:
getName() // 輸出 5, 函式宣告提升的結果
var getName = function () { console.log(4) } // 函式表示式
getName() // 輸出 4, 函式宣告提升後, 又被函式表示式覆蓋了
function getName() { // 函式宣告
console.log(5)
}
getName() // 還是輸出 4, 覆蓋的結果, 函式宣告提升了, 不按照文字的順序重新宣告的, 你可以想象它被移到了最前邊
複製程式碼
1
Foo().getName()
複製程式碼
我們一步步拆解, 上邊的語句相當於
var context = Foo();
context.getName();
複製程式碼
來看 Foo 的宣告:
function Foo() {
// 下邊這句沒有 var, let 或 const, 相當於 window.getName = xxx
getName = function () { console.log(1) }
return this // 這裡的 this 要看呼叫方式, 直接呼叫 Foo() 則 this 指向 window, new 呼叫, this 指向 new 出來的例項
}
複製程式碼
仔細看上邊的註釋, Foo 函式體內對 window.getName 進行了改寫, 這是下一個輸出的關鍵
1
如上邊分析的, Foo() 函式的執行, 對 window.getName 進行了改寫, window.getName 此時已經變為 function () { console.log(1) }
2
new Foo.getName()
複製程式碼
該語句先執行 Foo.getName, 與第一個結論一致, 輸出 2, 只是 new 會返回一個 object, 這個 object 指向 new 出來的例項, 但這裡這個例項沒被使用, 就不進一步分析了
3
new Foo().getName()
複製程式碼
拆解如下
var foo = new Foo()
foo.getName()
複製程式碼
如果你是一路看分析下來的, 就會明白 foo 這個例項, 就是 Foo 函式體裡的 this. 從原型的知識中, 我們可以知道, 如果呼叫一個例項的方法, 在例項方法中找不到, 就會從例項原型中找.
也就是會找到下邊這個方法, 並執行:
Foo.prototype.getName = function() { console.log(3) }
複製程式碼
3
new new Foo().getName()
複製程式碼
拆解如下
var foo = new Foo();
var bar = new foo.getName();
複製程式碼
從上邊 new Foo().getName()
的分析, 可以知道 foo.getName() 是在 foo 的原型裡邊的, 這裡 new 了一下原型裡邊的函式, 相當於先執行了, 再返回了一個新的例項. 這裡的執行, 也就是執行了下邊這個方法:
Foo.prototype.getName = function() { console.log(3) }
複製程式碼
只是 new 額外返回一個例項, 例項沒被使用, 沒什麼特殊的.
如果還有不明白的地方, 建議閱讀 《JavaScript 語言精髓》第四章 (本人看的是修訂版) 關於函式呼叫相關的內容. 相關電子書可以微信關注公眾號 前端Q, 回覆 ebook 閱讀. 此外, 建議購買正版.
8. 如何判斷左右小括號是否全部匹配。如 ( ( ))()((((()))))?
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
var stack = []
var map = {
'(' : ')',
'[': ']',
'{': '}'
}
for (var char of s) {
if(char in map) {
stack.push(char)
} else {
if( !stack.length || char != map[stack.pop()]) {
return false
}
}
}
// 如果最後stack 裡沒有元素了, 就一定是匹配的
return !stack.length
};
複製程式碼
9. 用css畫一個扇形?
width: 0;
height: 0;
border: solid 100px red;
border-color: red transparent transparent transparent;
border-radius: 100px;
複製程式碼
10. 用css實現已知或者未知寬度的垂直水平居中?
/
/ 1
.wraper {
position: relative;
.box {
position: absolute;
top: 50%;
left: 50%;
width: 100px;
height: 100px;
margin: -50px 0 0 -50px;
}
}
// 2
.wraper {
position: relative;
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
// 3
.wraper {
.box {
display: flex;
justify-content:center;
align-items: center;
height: 100px;
}
}
// 4
.wraper {
display: table;
.box {
display: table-cell;
vertical-align: middle;
}
}
//5
.container {
display: grid;
grid-auto-columns: 1fr;
grid-auto-rows: 200px;
background: #eee;
}
.parent {
background: grey;
justify-self: center;
align-self: center;
}
.child {
font-size: 30px;
}
//6、塊級元素:calc()
.parent {
width: 300px;
height: 300px;
border: 1px solid red;
position: relative;
}
.child {
width: 100px;
height: 100px;
background: blue;
padding: -webkit-calc((100% - 100px) / 2);
padding: -moz-calc((100% - 100px) / 2);
padding: -ms-calc((100% - 100px) / 2);
padding: calc((100% - 100px) / 2);
background-clip: content-box;
}
//7、margin:auto實現絕對定位元素的居中
.element {
width: 600px; height: 400px;
position: absolute; left: 0; top: 0; right: 0; bottom: 0;
margin: auto; /* 有了這個就自動居中了 */
}
//8、
.parent{
display: flex;
}
.parent{
display: flex;
width: 500px;
height: 500px;
background-color: pink;
}
.child{
flex: 0 0 auto;
margin: auto;
width: 100px;
height: 100px;
background-color: red;
複製程式碼
11. 如何理解迴流和重繪? ?
迴流:
當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然後再將計算的結果繪製出來。這個過程就是迴流(也叫重排)。
重繪:
當我們對 DOM 的修改導致了樣式的變化、卻並未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪製新的樣式(跳過了上圖所示的迴流環節)。這個過程叫做重繪。 由此我們可以看出,重繪不一定導致迴流,迴流一定會導致重繪。
常見的會導致迴流的元素:
- 常見的幾何屬性有 width、height、padding、margin、left、top、border 等等。
- 最容易被忽略的操作:獲取一些需要通過即時計算得到的屬性,當你要用到像這樣的屬性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 時,瀏覽器為了獲取這些值,也會進行迴流。
- 當我們呼叫了 getComputedStyle 方法,或者 IE 裡的 currentStyle 時,也會觸發迴流。原理是一樣的,都為求一個“即時性”和“準確性”。
避免方式:
- 避免逐條改變樣式,使用類名去合併樣式
- 將 DOM “離線”,使用DocumentFragment
- 提升為合成層,如使用
will-change
#divId {
will-change: transform;
}
複製程式碼
優點
- 合成層的點陣圖,會交由 GPU 合成,比 CPU 處理要快
- 當需要 repaint 時,只需要 repaint 本身,不會影響到其他的層
- 對於 transform 和 opacity 效果,不會觸發 layout 和 paint
注意:
部分瀏覽器快取了一個 flush 佇列,把我們觸發的迴流與重繪任務都塞進去,待到佇列裡的任務多起來、或者達到了一定的時間間隔,或者“不得已”的時候,再將這些任務一口氣出隊。但是當我們訪問一些即使屬性時,瀏覽器會為了獲得此時此刻的、最準確的屬性值,而提前將 flush 佇列的任務出隊。
12. 手寫一個Promise?
答案太長,你可以參考這個issues:github.com/LuckyWinty/…
13. 談談web安全問題及解決方案
答案太長,你可以參考這個issues:github.com/LuckyWinty/…
14. HTTPS和HTTP有什麼區別?
答案太長,你可以參考這個issues:github.com/LuckyWinty/…
15. Webpack效能優化你知道哪些?
答案太長,你可以參考這個issues:github.com/LuckyWinty/…
更多
本期彙總暫時到這裡,更多題目,可以關注: github.com/LuckyWinty/…
部落格關注:github.com/LuckyWinty/…
最後
- 歡迎掃碼加我,拉你進技術群,長期交流學習...
- 歡迎關注「前端Q」,認真學前端,做個有專業的技術人...