萬字長文,20-50K前端工程師部分面試題集錦 - 附答案

​ 如今20-50K的招聘,咱們先看看是什麼要求?html

螞蟻金服招聘要求:前端

蝦皮招聘:node

騰訊:mysql

明源雲:react

毫無疑問,這些公司都是招聘的大前端技術棧的職位,以前文章提到過2020年大前端最理想的技術棧,其實真的弄得很明白那些,出去面試基本上不會有什麼問題。nginx

小提示:若是發現小公司面試套你的技術和架構,迅速結束,開出天價薪資走人git

下面正式公佈部分面試題,以及答案

  • 出於對各個公司的尊重,不公佈是哪家公司的面試題,以及面試技巧。只公佈部分面試題和答案,以及分析問題的角度,學習方向,面試中考察的不只僅技術深度,還有廣度,每一個人不可能技術面面俱到,前端學習的東西太多,忘掉一部分也是正常。記住核心就是關鍵,這些都是一些基礎面試題,比較通用。github

  • 通常面試都會要作題,據我經驗看,通常都是6頁,三張紙。考察的大部分是前端技術棧,原生Javascript的內容,固然,有的外企的面試體驗更棒,技術一面規定是半個小時,國內公司可能有5輪,甚至六、7輪。web

  • 面試題我會概括成原生JavaScript、Node.js、React、Vue、通訊協議、運維部署、CI自動化部署、Docker、性能優化、前端架構設計、後端常見的架構等來分開寫面試

原生JavaScript篇

如下代碼跟我寫的有點不同,可是大體差很少,最終都是在紙上手寫實現

手寫一個深拷貝:

此處省略了一些其餘類型的處理,能夠在題目旁註釋、

手寫一個reduce:

Array.isArray的原理:

手寫一個的防抖函數:

手寫一個Promise:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
​
function MyPromise(fn) {
    const self = this;
    self.value = null;
    self.error = null;
    self.status = PENDING;
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];
​
    function resolve(value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === PENDING) {
            setTimeout(() => {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
            }, 0)
        }
    }
​
    function reject(error) {
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = REJECTED;
                self.error = error;
                self.onRejectedCallbacks.forEach((callback) => callback(self.error));
            }, 0)
        }
    }
    try {
        fn(resolve, reject);
    } catch (e) {
        reject(e);
    }
}
​
function resolvePromise(bridgepromise, x, resolve, reject) {
    if (bridgepromise === x) {
        return reject(new TypeError('Circular reference'));
    }
​
    let called = false;
    if (x instanceof MyPromise) {
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgepromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(bridgepromise, y, resolve, reject);
                }, error => {
                    if (called) return;
                    called = true;
                    reject(error);
                })
            } else {
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}
​
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
    if (self.status === FULFILLED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        })
    }
    if (self.status === REJECTED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            }, 0);
        });
    }
    if (self.status === PENDING) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push((error) => {
                try {
                    let x = onRejected(error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
}
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}
​
MyPromise.deferred = function() {
    let defer = {};
    defer.promise = new MyPromise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}
try {
    module.exports = MyPromise
} catch (e) {}

複製代碼
promisify原理:
promisify = function(fn) {
  return function() {
    var args = Array.from(arguments);
    return new MyPromise(function(resolve, reject) {
      fn.apply(
        null,
        args.concat(function(err) {
          err ? reject(err) : resolve(arguments[1]);
        })
      );
    });
  };
};
複製代碼
Redux核心源碼解析:

bindActionCreator源碼解析:

export default function bindActionCreator(actions, dispatch) {
    let newActions = {};
    for (let key in actions) {
        newActions[key] = () => dispatch(actions[key].apply(null, arguments));
    }
    return newActions;
}
複製代碼

核心:將多個action和dispatch傳入,合併成一個全新的actions對象

combineReducers源碼:
export default combineReducers = reducers => (state = {}, action) => Object.keys(reducers).reduce((currentState, key) => {
    currentState[key] = reducers[key](state[key], action);
    return currentState;
}, {});

複製代碼

核心:跟上面有點相似,遍歷生成一個全新的state,將多個state合併成一個state

createStore源碼結合applyMiddleware講述如何實現處理多箇中間件:

export default function createStore(reducer, enhancer) {
    if (typeof enhancer !== 'undefined') {
        return enhancer(createStore)(reducer)
    }
    let state = null
    const listeners = []
    const subscribe = (listener) => {
        listeners.push(listener)
    }
    const getState = () => state
    const dispatch = (action) => {
        state = reducer(state, action)
        listeners.forEach((listener) => listener())
    }
    dispatch({})
    return { getState, dispatch, subscribe }
}

複製代碼
applyMiddleware:
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer) => {
        const store = createStore(reducer)
        let dispatch = store.dispatch
        let chain = []
​
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
​
        return {
            ...store,
            dispatch
        }
    }
}

複製代碼

核心:當發現傳入createStore的第二個或者第三個參數存在時候(這裏沒有像原生redux支持SSR代碼注水,不支持第二個參數initState),就去返回它的調用結果

整個Redux這裏是最繞的,這裏不作過度的源碼講解,其實核心就是一點:

實現多箇中間件原理,就是將dispatch看成最後一個函數傳入,利用compose這個工具函數,最終實現多箇中間件同時起做用,當你源碼看得比較多的時候會發現,大多數的源碼是跟redux類似

compose工具函數實現:
export default function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
複製代碼

核心:其實就是一個reduce函數實現,每次返回一個新的函數,再將新的參數傳入

redux下次會專門出個文章講解,它的源碼過重要了~

原生JavaScript考察點比較多,這裏只列出一部分,還有像結合TypeScript一塊兒問的,組合繼承,對象建立模式、設計模式等,可是那些本次不作講解

Node.js篇幅:

簡述Node.js的EventLoop:

現場出題,項目裏有下面這段代碼,輸出是什麼,穩定嗎,說明緣由:
setTimeout(() => {
  console.log(1);
});
//----若干代碼邏輯
new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log(2);
});

複製代碼

答案:先輸出2,再輸出1,可是不穩定。由於定時器的執行時間不肯定,node.js的輪詢至關於一個定時器,一直從上往下6個階段輪詢,此時若是中間代碼比較耗時,還沒運行到Promise時候,已經輪詢到第一階段,定時器的回調就會被觸發。

Node.js爲何處理異步IO快?

答:Node 底層採用線程池的原理管理異步 IO,因此咱們一般所的 單線程是指 Node 中 JavaScript 的執行是單線程的,但 Node 自己是多線程的。Node.js 中異步 IO 是經過事件循環的方式實現的,異步 IO 事件主要來源於網絡請求和文件 IO。可是正由於如此,Node.js處理不少計算密集型的任務,就比較吃力,固然有多進程方式能夠解決這個問題。(本身給本身挖坑)

之前聽過一個很形象的回答:Java是一百個服務員對應一百個用餐客人,Node是一個服務員對應一百個用餐客人(由於客人不須要分分鐘服務,可能只要三分鐘----好像,東哥?)

Node.js有cluster、fork兩種模式多進程,那麼這兩種狀況下,主進程負責TCP通訊,怎樣纔可讓子進程共享用戶的Socket對象?

答案:cluster模式,多實例、自動共享端口連接、自動實現負載均衡。fork模式實現的多進程,單實例、多進程,能夠經過手動分發socket對象給不一樣子進程進行定製化處理、實現負載均衡

Node.js多進程維護,以及通訊方式:

答案:原生的cluster和fork模式都有API封裝好的進行通訊。若是是execfile這樣形式調起第三方插件形式,想要與第三方插件進行通訊,能夠本身封裝一個相似promisyfy形式進行通訊,維護這塊,子進程能夠監聽到異常,一旦發現異常,馬上通知主進程,殺死這個異常的子進程,而後從新開啓一個子進程~

簡單談談,Node.js搭建TCP、restful、websocket、UDP服務器,遇到過哪些問題,怎麼解決的

答案:這裏涉及的問題比較多,考察全方位的通訊協議知識,須要出個專題後期進行編寫

看你簡歷上寫,對koa源碼系統學習過,請簡述核心洋蔥圈的實現:

答案:洋蔥圈的實現,有點相似Promise中的then實現,每次經過use方法定義中間件函數時候,就會把這個函數存入一個隊列中,全局維護一個ctx對象,每次調用next(),就會調用隊列的下一個任務函數。僞代碼實現~:

use (fn) {
    // this.fn = fn 改爲:
    this.middlewares.push(fn) // 每次use,把當前回調函數存進數組
}
compose(middlewares, ctx){ // 簡化版的compose,接收中間件數組、ctx對象做爲參數
    function dispatch(index){ // 利用遞歸函數將各中間件串聯起來依次調用
        if(index === middlewares.length) return // 最後一次next不能執行,否則會報錯
        let middleware = middlewares[index] // 取當前應該被調用的函數
        middleware(ctx, () => dispatch(index + 1)) // 調用並傳入ctx和下一個將被調用的函數,用戶next()時執行該函數
    }
    dispatch(0)
}
​
複製代碼

因此這裏說,源碼看多了會發現,其實大都差很少,都是你抄個人,我抄你的,輪子上搭積木

你對TCP系統學習過,請你簡述下SYN flood攻擊:

小提示:個人TCP是跟張師傅學習的,在某金的小冊上有賣。~推薦購買

答案:攻擊方僞造源地址發送SYN報文,服務端此時回覆syn+ack,可是真正的IP地址收到這個包以後,有可能直接回復了RST包,可是若是不回覆RST包,那就更嚴重了,可能服務端會在幾十秒後才關閉這個socket連接(時間根據每一個系統不同)

抓包可見~:

TCP能夠快速握手嗎?

答案:能夠 -- 內容來自 張師傅的小冊

TCP連接和UDP的區別,何時選擇使用UDP連接?

www.xiuchuang.com/question/40…

總結就是:TCP面向連接,UDP面向消息,TCP的ACK做用是確認已經收到上一個包,UDP只管發送,一些無人機的操做,就用UDP連接,每一個無人機就是一個服務器,跟地面通信。

通訊協議仍是要系統學習,通訊這裏也問了大概半個小時,包括密鑰交換等

看你簡歷上有寫本身實現了一個mini-react,請簡述實現原理,以及diff算法實現

倉庫地址:github.com/JinJieTan/m…

答案:利用了babel,將虛擬dom轉換成了我想要的對象格式,而後實現了異步setState、component diff 、 element diff 、props 更新等。相似PReact的將真實dom和虛擬dom對比的方式進行diff,這裏結合代碼講了大概半個小時~ 你們能夠看源碼,這個對於學習React是很是好的資料,當時我花了半個多月學習

看你對Vue的源碼有系統學習過,請簡述下Vue2.x版本的數據綁定:

答案:Vue裏面的{{}}寫法, 會用正則匹配後,拿到數據跟data裏的作對比-解析指令,觀察數據變化是利用defineProperty來實現,由於監聽不到數組的變化,因此尤大大隻重寫了6個數組API。源碼解析,後面就是拼細節,主要講一些核心點的實現。

爲何Vue的nextTick不穩定?

答案:Vue的nextTick原理是:

優雅降級:

首選promise.then

而後是setImmediate

而後是一個瀏覽器目前支持很差的API

最後是setTimeout

dom真正更新渲染好的時間,不能真正肯定,不管是框架仍是原生,都存在這個問題。因此用nextTick並不能保證拿到最新的dom

談談你對微前端的見解,以及實踐:

答案:將Vue和React一塊兒開發,其實一點都不難,只要本身能造出Redux這樣的輪子,熟悉兩個框架原理,就能一塊兒開發,難的是將這些在一個合適的場景中使用。以前看到網上有微前端的實踐,可是並非那麼完美,固然,相似Electron這樣的應用,混合開發很正常,微前端並非只單單多個框架混合開發,更可能是多個框架引入後解決了什麼問題、帶來的問題怎麼解決?畢竟5G還沒徹底普及,數據傳輸仍是不那麼快。過大的包容易帶來客戶端的過長白屏時間(本身給本身挖坑)

你有提到白屏時間,有什麼辦法能夠減小嗎?都是什麼原理

答案: GZIP,SSR同構、PWA應用、預渲染、localStorage緩存js文件等、

下面就是細分拆解答案,無限的連帶問題,這裏很是耗時,這些內容大都網上能搜到,我這裏就不詳細說

其中有問到PWA的原理,個人回答是:

Service Worker 有一套本身的聲明週期,當安裝而且處於激活狀態時候,網站在https或者localhost的協議時候,能夠攔截過濾發出的請求,會先把請求克隆一份(請求是流,消費就沒有了),而後判斷請求的資源是否在 Service Worker 緩存中,若是存在那麼能夠直接從 Service Worker 緩存中取出,若是不存在,那麼就真正的發出這個請求。

看你的技術棧對Electron比較熟悉,有使用過React-native,請你談談使用的感覺?

答案:React-native的坑仍是比較多,可是目前也算擁有成熟的生態了,開發簡單的APP可使用它。可是複雜的應用仍是原生比較好,Electron目前很是受歡迎,它基本上能夠完成桌面應用的大部分需求,重型應用開發也是徹底沒問題的,能夠配合大量C# C++插件等。

Node.js的消息隊列應用場景是什麼?原理是什麼

答案:咱們公司以前用的kafka,消息隊列的核心概念,異步,提供者,消費者。例如IM應用,天天都會有高峯期,可是咱們不可能爲了高峯期配置那麼多服務器,那樣就是浪費,因此使用消息隊列,在多長時間內流量達到多少,就控制消費頻率,例如客戶端是流的提供者,有一箇中間件消費隊列,咱們的服務器是消費者,每次消費一個任務就回復一個ACK給消費隊列,消費頻率由咱們控制,這樣任務不會丟失,服務器也不會掛。 還有一個異步問題,一個用戶下單購買一件商品,可能要更新庫存,已購數量,支付,下單等任務。不可能同步進行,這時候須要異步並行,事務方式處理。這樣既不耽誤時間,也能確保全部的任務成功纔算成功,否則沒有支付成功,可是已購數量增加了就有問題。

此處省略、、、一萬字

用戶就是要上傳10個G的文件,服務器存儲容許的狀況下,你會怎麼處理保證總體架構順暢,不影響其餘用戶?

答案:我會準備兩個服務器上傳接口,前端或者原生客戶端上傳文件能夠拿到文件大小,根據文件大小,分發不一樣的對應服務器接口處理上傳,大文件能夠進行斷點續傳,原理是md5生成惟一的hash值,將分片的hash數組先上傳到後端,而後將文件分片上傳,對比hash值,相同的則丟棄。不一致的話,根據數組內容進行buffer拼接生成文件。

關於服務器性能,大文件上傳的服務器容許被阻塞,小文件的服務器不會被阻塞。

談談你對前端、客戶端架構的認識?

答案:前端的架構,首先明確項目的兼容性,面向瀏覽器編程,是否作成PC、移動端的響應式佈局。根據項目規模、後期可能迭代的需求制定技術方案,若是比較重型的應用應該選用原生開發,儘可能少使用第三方庫。

客戶端架構:是否跨平臺,明確兼容系統,例如是否兼容XP ,若是兼容XP就選擇nw.js,再而後根據項目複雜度招聘相應技術梯度人員,安排系統學習相關內容,招聘人員或者購買定製開發相關原生插件內容。

雖說只是談談,可是感受面試的職位越高級、輪數越日後,越考驗你的架構能力,前面考察基礎,後面考察你的技術廣度以及邏輯思惟,可否在複雜的應用中保持清醒頭腦,定位性能這類型的細節能力。不少人基礎面試面得很好,可是拿不到offer,緣由就是沒有這種架構能力,只能本身寫代碼,不能帶領你們學習、寫代碼。這也是我在面試時偶然聽到某個大公司HR之間的對話,原話是:他面試還能夠,看起來是很老實(某個以前的面試者),可是他對以前項目總體流程並非那麼清楚,連本身作的項目,先後端流程都不清楚,感受不合適。

介紹一下Redis,爲何快,怎麼作持久化存儲,什麼叫緩存擊穿?

答案:Redis將數據存儲在內存中,key-value形式存儲,因此獲取也快。支持的key格式相對於memorycache更多,並且支持RDB快照形式、AOF

1.RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤,實際操做過程是fork一個子進程,先將數據集寫入臨時文件,寫入成功後,再替換以前的文件,用二進制壓縮存儲。RDB是Redis默認的持久化方式,會在對應的目錄下生產一個dump.rdb文件,重啓會經過加載dump.rdb文件恢復數據。

二、優勢

1)只有一個文件dump.rdb,方便持久化;

2) 容災性好,一個文件能夠保存到安全的磁盤;

3) 性能最大化,fork子進程來完成寫操做,讓主進程繼續處理命令,因此是IO最大化(使用單獨子進程來進行持久化,主進程不會進行任何IO操做,保證了redis的高性能) ;

4)若是數據集偏大,RDB的啓動效率會比AOF更高。

1)數據安全性低。(RDB是間隔一段時間進行持久化,若是持久化之間redis發生故障,會發生數據丟失。因此這種方式更適合數據要求不是特別嚴格的時候)

2)因爲RDB是經過fork子進程來協助完成數據持久化工做的,所以,若是當數據集較大時,可能會致使整個服務器中止服務幾百毫秒,甚至是1秒鐘。

AOF持久化是以日誌的形式記錄服務器所處理的每個寫、刪除操做,查詢操做不會記錄,以文本的方式記錄,文件中能夠看到詳細的操做記錄。她的出現是爲了彌補RDB的不足(數據的不一致性),因此它採用日誌的形式來記錄每一個寫操做,並追加到文件中。Redis 重啓的會根據日誌文件的內容將寫指令從前到後執行一次以完成數據的恢復工做。

二、優勢

1)數據安全性更高,AOF持久化能夠配置appendfsync屬性,其中always,每進行一次命令操做就記錄到AOF文件中一次。

2)經過append模式寫文件,即便中途服務器宕機,能夠經過redis-check-aof工具解決數據一致性問題。

3)AOF機制的rewrite模式。(AOF文件沒被rewrite以前(文件過大時會對命令進行合併重寫),能夠刪除其中的某些命令(好比誤操做的flushall))

三、缺點

1)AOF文件比RDB文件大,且恢復速度慢;數據集大的時候,比rdb啓動效率低。

2)根據同步策略的不一樣,AOF在運行效率上每每會慢於RDB。

具體可看、

baijiahao.baidu.com/s?id=163170…

Redis能夠配合session等作服務端持久化存儲、還介紹了下session的場景,

介紹下緩存擊穿和穿透:

緩存穿透,是指查詢一個數據庫必定不存在的數據。正常的使用緩存流程大體是,數據查詢先進行緩存查詢,若是key不存在或者key已通過期,再對數據庫進行查詢,並把查詢到的對象,放進緩存。若是數據庫查詢對象爲空,則不放進緩存。

緩存擊穿,是指一個key很是熱點,在不停的扛着大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿破緩存,直接請求數據庫,就像在一個屏障上鑿開了一個洞。

介紹下你會用的自動化構建的方式:

答案

Jenkins自動化構建

本身搭建Node.js服務器,實現Jenkins

Docker配合Travis CI實現自動化構建

細化答案:

Jenkins自動化構建:

配置,自動同步某個分支代碼,打包構建。

本身搭建Node.js服務器,實現Jenkins:

本身搭建Node.js的服務器,在GitLab上指定webhook地址,分支代碼更新觸發事件,服務器接受到post請求,裏面附帶分支的信息,執行本身的shell腳本命令,指定文件夾,構建打包。

服務器上使用Docker-compose指定鏡像,每次代碼推送到gitHub,經過本身編寫的yml和dockerfile文件構建打包,服務器自動拉取最新鏡像而且發佈到正式環境

代碼實現:

.travis.yml

language: node_js node_js:

  • '12' services:
  • docker ​ before_install:
  • npm install
  • npm install -g parcel-bundler ​ script:
  • parcel build ./index.js
  • echo "DOCKER_PASSWORD" | docker login -u "DOCKER_USERNAME" --password-stdin
  • docker build -t jinjietan/mini-react:latest .
  • docker push jinjietan/mini-react:latest

dockerfile:

FROM nginx COPY ./index.html /usr/share/nginx/html/ COPY ./dist /usr/share/nginx/html/dist EXPOSE 80

問完Redis,確定會問數據庫,mysql mongodb sqlite都問了,這裏就暫時不寫了

數據庫須要系統的學習,特別是mysql,我這裏就不班門弄斧了,推薦某金上面的小孩子的小冊。零蛋學mysql(本文沒有收取任何廣告費,本身買了看完才推薦給你們)

附帶的一些問題:

Linux常見操做

雲端部署

等。

固然有人會問20-50K的問題怎麼這麼簡單,由於每一個問題都是串起來的,須要你的知識面足夠廣,才能一路面下去,直到拿到offer。並且每一個問題都是有坑,例如pm2 start 若是不指定參數到底會啓動多少個進程? 在雲端和本身的電腦上是不同的,這些都須要你去有實際操做經驗才能應對。

本文的初衷,不爲了面試而背面試題,只是讓你們知道應該學習什麼方向。純粹爲了面試去背誦面試題,是很容易被識破,只有不斷積累學習,你纔會輕易拿下offer。

座右銘:你想要在外人看來絕不費力,那你就要平時不斷努力~

若是以爲寫得不錯,歡迎點個在看,關注一下小編的公衆號:前端巔峯,讚揚一下更好啦。咱們有大前端交流羣,裏面妹子多多~大牛多多,歡迎加入~ 微信公衆號發送加羣:便可加入~

文章裏面有寫得不對的地方,能夠在下面指出,個人答案也不必定對,上面部分答案因爲太長,網上覆制了一部分,可是大部分是本身寫的~

相關文章
相關標籤/搜索