十幾道含答案的大廠面試題總結

winty發表於2019-12-30

年底了,又到了跳槽季啦,該刷題走起了。這裡總結了一些被問到可能會懵逼的面試真題,有需要的可以看下~

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屬性對非替換內聯元素無效,例如spancode

實現垂直居中

想要實現垂直方向的居中可以用絕對定位:

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 時,也會觸發迴流。原理是一樣的,都為求一個“即時性”和“準確性”。
避免方式:
  1. 避免逐條改變樣式,使用類名去合併樣式
  2. 將 DOM “離線”,使用DocumentFragment
  3. 提升為合成層,如使用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」,認真學前端,做個有專業的技術人...

GitHub

相關文章