前言
在阿里和騰訊工作了6年,當了3年的前端面試官,把期間我和我的同事常問的面試題和答案彙總在我 Github 的 Weekly-FE-Interview 中。希望對大家有所幫助。
如果你在bat面試的時候遇到了什麼不懂的問題,歡迎給我提issue,我會把題目彙總並將面試要點和答案寫好放在週刊裡,大家一起共同進步和成長,助力大家進入自己理想的企業。
專案地址是:github.com/airuikun/We…
常見面試題精選
以下是十道大廠一面的時候常見的面試題,如果全部理解並且弄透,在一面或者電話面的時候基本上能中1~2題。小夥伴可以先不急著看答案,先自己嘗試著思考一下和自己實現一下,然後再看答案。
第 1 題:http的狀態碼中,499是什麼?如何出現499,如何排查跟解決
499對應的是 “client has closed connection”,客戶端請求等待連結已經關閉,這很有可能是因為伺服器端處理的時間過長,客戶端等得“不耐煩”了。還有一種原因是兩次提交post過快就會出現499。 解決方法:
- 前端將timeout最大等待時間設定大一些
- nginx上配置proxy_ignore_client_abort on;
提問解答與更多解析:github.com/airuikun/We…
第 2 題:如何遍歷一個dom樹
function traversal(node) {
//對node的處理
if (node && node.nodeType === 1) {
console.log(node.tagName);
}
var i = 0,
childNodes = node.childNodes,
item;
for (; i < childNodes.length; i++) {
item = childNodes[i];
if (item.nodeType === 1) {
//遞迴先序遍歷子節點
traversal(item);
}
}
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
第 3 題:new操作符都做了什麼
四大步驟:
1、建立一個空物件,並且 this 變數引用該物件,// lat target = {};
2、繼承了函式的原型。// target.proto = func.prototype;
3、屬性和方法被加入到 this 引用的物件中。並執行了該函式func// func.call(target);
4、新建立的物件由 this 所引用,並且最後隱式的返回 this 。// 如果func.call(target)返回的res是個物件或者function 就返回它
function new(func) {
lat target = {};
target.__proto__ = func.prototype;
let res = func.call(target);
if (typeof(res) == "object" || typeof(res) == "function") {
return res;
}
return target;
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
第 4 題:手寫程式碼,簡單實現call
Function.prototype.call2 = function(context) {
var context = context || window; //因為傳進來的context有可能是null
context.fn = this;
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push("arguments[" + i + "]"); //不這麼做的話 字串的引號會被自動去掉 變成了變數 導致報錯
}
args = args.join(",");
var result = eval("context.fn(" + args + ")"); //相當於執行了context.fn(arguments[1], arguments[2]);
delete context.fn;
return result; //因為有可能this函式會有返回值return
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
第 5 題:手寫程式碼,簡單實現apply
Function.prototype.apply2 = function(context, arr) {
var context = context || window; //因為傳進來的context有可能是null
context.fn = this;
var args = [];
var params = arr || [];
for (var i = 0; i < params.length; i++) {
args.push("params[" + i + "]"); //不這麼做的話 字串的引號會被自動去掉 變成了變數 導致報錯
}
args = args.join(",");
var result = eval("context.fn(" + args + ")"); //相當於執行了context.fn(arguments[1], arguments[2]);
delete context.fn;
return result; //因為有可能this函式會有返回值return
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
第 6 題:手寫程式碼,簡單實現bind
Function.prototype.bind2 = function(context) {
var _this = this;
var argsParent = Array.prototype.slice.call(arguments, 1);
return function() {
var args = argsParent.concat(Array.prototype.slice.call(arguments)); //轉化成陣列
_this.apply(context, args);
};
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
第 7 題:講解一下HTTPS的工作原理
HTTPS在傳輸資料之前需要客戶端(瀏覽器)與服務端(網站)之間進行一次握手,在握手過程中將確立雙方加密傳輸資料的密碼資訊。TLS/SSL協議不僅僅是一套加密傳輸的協議,更是一件經過藝術家精心設計的藝術品,TLS/SSL中使用了非對稱加密,對稱加密以及HASH演算法。握手過程的簡單描述如下:
-
瀏覽器將自己支援的一套加密規則傳送給網站。
-
網站從中選出一組加密演算法與HASH演算法,並將自己的身份資訊以證照的形式發回給瀏覽器。證照裡面包含了網站地址,加密公鑰,以及證照的頒發機構等資訊。
-
獲得網站證照之後瀏覽器要做以下工作:
-
a) 驗證證照的合法性(頒發證照的機構是否合法,證照中包含的網站地址是否與正在訪問的地址一致等),如果證照受信任,則瀏覽器欄裡面會顯示一個小鎖頭,否則會給出證照不受信的提示。
-
如果證照受信任,或者是使用者接受了不受信的證照,瀏覽器會生成一串隨機數的密碼,並用證照中提供的公鑰加密。
-
使用約定好的HASH計算握手訊息,並使用生成的隨機數對訊息進行加密,最後將之前生成的所有資訊傳送給網站。
-
-
網站接收瀏覽器發來的資料之後要做以下的操作:
-
a) 使用自己的私鑰將資訊解密取出密碼,使用密碼解密瀏覽器發來的握手訊息,並驗證HASH是否與瀏覽器發來的一致。
-
b) 使用密碼加密一段握手訊息,傳送給瀏覽器。
-
-
瀏覽器解密並計算握手訊息的HASH,如果與服務端發來的HASH一致,此時握手過程結束,之後所有的通訊資料將由之前瀏覽器生成的隨機密碼並利用對稱加密演算法進行加密。
提問解答與更多解析:github.com/airuikun/We…
第 8 題:講解一下https對稱加密和非對稱加密。
對稱加密: 傳送方和接收方需要持有同一把金鑰,傳送訊息和接收訊息均使用該金鑰。相對於非對稱加密,對稱加密具有更高的加解密速度,但雙方都需要事先知道金鑰,金鑰在傳輸過程中可能會被竊取,因此安全性沒有非對稱加密高。
非對稱加密: 接收方在傳送訊息前需要事先生成公鑰和私鑰,然後將公鑰傳送給傳送方。傳送放收到公鑰後,將待傳送資料用公鑰加密,傳送給接收方。接收到收到資料後,用私鑰解密。 在這個過程中,公鑰負責加密,私鑰負責解密,資料在傳輸過程中即使被截獲,攻擊者由於沒有私鑰,因此也無法破解。 非對稱加密演算法的加解密速度低於對稱加密演算法,但是安全性更高。
提問解答與更多解析:github.com/airuikun/We…
第 9 題: 簡單實現專案程式碼按需載入,例如import { Button } from 'antd',打包的時候只打包button
原理很簡單,就是將
import { Select, Pagination, Button } from 'xxx-ui';
複製程式碼
通過babel轉化成
import Button from `xxx-ui/src/components/ui-base/Button/Button`;
import Pagination from `xxx-ui/src/components/ui-base/Pagination/Pagination`;
import Select from `xxx-ui/src/components/ui-base/Select/Select`;
複製程式碼
自定義擴充一個babel外掛,程式碼如下:
visitor: {
ImportDeclaration (path, { opts }) {
const specifiers = path.node.specifiers;
const source = path.node.source;
// 判斷傳入的配置引數是否是陣列形式
if (Array.isArray(opts)) {
opts.forEach(opt => {
assert(opt.libraryName, 'libraryName should be provided');
});
if (!opts.find(opt => opt.libraryName === source.value)) return;
} else {
assert(opts.libraryName, 'libraryName should be provided');
if (opts.libraryName !== source.value) return;
}
const opt = Array.isArray(opts) ? opts.find(opt => opt.libraryName === source.value) : opts;
opt.camel2UnderlineComponentName = typeof opt.camel2UnderlineComponentName === 'undefined'
? false
: opt.camel2UnderlineComponentName;
opt.camel2DashComponentName = typeof opt.camel2DashComponentName === 'undefined'
? false
: opt.camel2DashComponentName;
if (!types.isImportDefaultSpecifier(specifiers[0]) && !types.isImportNamespaceSpecifier(specifiers[0])) {
// 遍歷specifiers生成轉換後的ImportDeclaration節點陣列
const declarations = specifiers.map((specifier) => {
// 轉換元件名稱
const transformedSourceName = opt.camel2UnderlineComponentName
? camel2Underline(specifier.imported.name)
: opt.camel2DashComponentName
? camel2Dash(specifier.imported.name)
: specifier.imported.name;
// 利用自定義的customSourceFunc生成絕對路徑,然後建立新的ImportDeclaration節點
return types.ImportDeclaration([types.ImportDefaultSpecifier(specifier.local)],
types.StringLiteral(opt.customSourceFunc(transformedSourceName)));
});
// 將當前節點替換成新建的ImportDeclaration節點組
path.replaceWithMultiple(declarations);
}
}
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
第 10 題:簡單手寫實現promise
// 簡易版本的promise
// 第一步: 列出三大塊 this.then resolve/reject fn(resolve,reject)
// 第二步: this.then負責註冊所有的函式 resolve/reject負責執行所有的函式
// 第三步: 在resolve/reject裡面要加上setTimeout 防止還沒進行then註冊 就直接執行resolve了
// 第四步: resolve/reject裡面要返回this 這樣就可以鏈式呼叫了
// 第五步: 三個狀態的管理 pending fulfilled rejected
// *****promise的鏈式呼叫 在then裡面return一個promise 這樣才能then裡面加上非同步函式
// 加上了catch
function PromiseM(fn) {
var value = null;
var callbacks = [];
//加入狀態 為了解決在Promise非同步操作成功之後呼叫的then註冊的回撥不會執行的問題
var state = 'pending';
var _this = this;
//註冊所有的回撥函式
this.then = function (fulfilled, rejected) {
//如果想鏈式promise 那就要在這邊return一個new Promise
return new PromiseM(function (resolv, rejec) {
//異常處理
try {
if (state == 'pending') {
callbacks.push(fulfilled);
//實現鏈式呼叫
return;
}
if (state == 'fulfilled') {
var data = fulfilled(value);
//為了能讓兩個promise連線起來
resolv(data);
return;
}
if (state == 'rejected') {
var data = rejected(value);
//為了能讓兩個promise連線起來
resolv(data);
return;
}
} catch (e) {
_this.catch(e);
}
});
}
//執行所有的回撥函式
function resolve(valueNew) {
value = valueNew;
state = 'fulfilled';
execute();
}
//執行所有的回撥函式
function reject(valueNew) {
value = valueNew;
state = 'rejected';
execute();
}
function execute() {
//加入延時機制 防止promise裡面有同步函式 導致resolve先執行 then還沒註冊上函式
setTimeout(function () {
callbacks.forEach(function (cb) {
value = cb(value);
});
}, 0);
}
this.catch = function (e) {
console.log(JSON.stringify(e));
}
//經典 實現非同步回撥
fn(resolve, reject);
}
複製程式碼
提問解答與更多解析:github.com/airuikun/We…
結語
本人還寫了一些前端進階知識的文章,如果覺得不錯可以點個star。
blog專案地址是:github.com/airuikun/bl…
我是小蝌蚪,騰訊高階前端工程師,跟著我一起每週攻克幾個前端技術難點。希望在小夥伴前端進階的路上有所幫助,助力大家進入自己理想的企業。