高級前端面試題目大全(一)
原文鏈接:http://www.reibang.com/p/7c6e4d21bf77
第 1 題:(滴滴、餓了么)寫 React / Vue 項目時為什么要在列表組件中寫 key帖蔓,其作用是什么叠骑?
1. 更準確
因為帶key就不是就地復(fù)用了,在isSameNode()函數(shù) a.key === b.key對比中可以避免就地復(fù)用
的情況。所以會更加準確绍哎。
2. 更快
利用key的唯一性生成map對象來獲取對應(yīng)節(jié)點,比遍歷方式更快。主要是為了提升diff【同級比較】的效率携龟。自己想一下自己要實現(xiàn)前后列表的diff,如果對列表的每一項增加一個key勘高,即唯一索引峡蟋,那就可以很清楚的知道兩個列表誰少了誰沒變。而如果不加key的話华望,就只能一個個對比了蕊蝗。
vue和react都是采用diff算法來對比新舊虛擬節(jié)點,從而更新節(jié)點赖舟。在vue的diff函數(shù)中(建議先了解一下diff算法過程)蓬戚。 在交叉對比中,當新節(jié)點跟舊節(jié)點頭尾交叉對比沒有結(jié)果時宾抓,會根據(jù)新節(jié)點的key去對比舊節(jié)點數(shù)組中的key子漩,從而找到相應(yīng)舊節(jié)點(這里對應(yīng)的是一個key => index 的map映射)。如果沒找到就認為是一個新增節(jié)點石洗。而如果沒有key幢泼,那么就會采用遍歷查找的方式去找到對應(yīng)的舊節(jié)點。一種一個map映射讲衫,另一種是遍歷查找缕棵。相比而言。map映射的速度更快涉兽。 vue部分源碼如下:
解析:第 1 題
第 2 題:['1', '2', '3'].map(parseInt)
what & why ?
首先讓我們回顧一下招驴,map函數(shù)的第一個參數(shù)callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個callback一共可以接收三個參數(shù),其中第一個參數(shù)代表當前被處理的元素花椭,而第二個參數(shù)代表該元素的索引忽匈。
而parseInt則是用來解析字符串的,使字符串成為指定基數(shù)的整數(shù)矿辽。
parseInt(string, radix)
接收兩個參數(shù)丹允,第一個表示被處理的值(字符串)萎馅,第二個表示為解析時的基數(shù)伟桅。
了解這兩個函數(shù)后,我們可以模擬一下運行情況
parseInt('1', 0) //radix為0時讳苦,且string參數(shù)不以“0x”和“0”開頭時宾娜,按照10為基數(shù)處理批狐。這個時候返回1
parseInt('2', 1) //基數(shù)為1(1進制)表示的數(shù)中,最大值小于2,所以無法解析嚣艇,返回NaN
parseInt('3', 2) //基數(shù)為2(2進制)表示的數(shù)中承冰,最大值小于3,所以無法解析食零,返回NaN
map函數(shù)返回的是一個數(shù)組困乒,所以最后結(jié)果為[1, NaN, NaN]
解析:第 2 題
第 3 題:(挖財)什么是防抖和節(jié)流?有什么區(qū)別贰谣?如何實現(xiàn)娜搂?
JavaScript專題之跟著underscore學(xué)防抖
JavaScript專題之跟著 underscore 學(xué)節(jié)流
防抖
觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻事件再次被觸發(fā)吱抚,則重新計算時間
思路
每次觸發(fā)事件時都取消之前的延時調(diào)用方法
/* 防抖 */
function dou(fn, wait) {
var time = null;
return function () {
clearTimeout(time)
// time = setTimeout(function () {
// console.log(this)//window
// fn.apply(this, arguments)//這樣的話 this為window和直接 fn()調(diào)用是一樣的效果百宇,因為他們的this都是window
// }, wait);
time = setTimeout(() => {
// console.log(this)//div
fn.apply(this, arguments)//確保dou函數(shù)的this(上下文還是div)
}, wait);
}
}
function demo() {
console.log('防抖啦')
}
// 用句柄事件綁定調(diào)用dou事件,所以this為div節(jié)點對象
document.querySelector('div').addEventListener('scroll', dou(demo, 1000))
節(jié)流
高頻事件觸發(fā)秘豹,但在n秒內(nèi)只會執(zhí)行一次携御,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率
思路
每次觸發(fā)事件時都判斷當前是否有等待執(zhí)行的延時函數(shù)
/* 節(jié)流 */
function throttle(func, wait) {
var previous = 0;
return function () {
var now = +new Date();
if (now - previous > wait) {
func.apply(this, arguments);
previous = now;
}
}
}
function getUserAction() {
console.log(`每秒1秒內(nèi)打印一次`)
}
document.querySelector('div').addEventListener('click', throttle(getUserAction, 1000))
解析:第 3 題
第 4 題:介紹下 Set、Map憋肖、WeakSet 和 WeakMap 的區(qū)別因痛?
Set
成員唯一、無序且不重復(fù)
[value, value]岸更,鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名)
可以遍歷膊升,方法有:add怎炊、delete、has
WeakSet
成員都是對象
成員都是弱引用廓译,可以被垃圾回收機制回收评肆,可以用來保存DOM節(jié)點,不容易造成內(nèi)存泄漏
不能遍歷非区,方法有add瓜挽、delete、has
Map
本質(zhì)上是鍵值對的集合征绸,類似集合
可以遍歷久橙,方法很多可以跟各種數(shù)據(jù)格式轉(zhuǎn)換
WeakMap
只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名
鍵名是弱引用管怠,鍵值可以是任意的淆衷,鍵名所指向的對象可以被垃圾回收,此時鍵名是無效的
不能遍歷渤弛,方法有g(shù)et祝拯、set、has她肯、delete
解析:第 4 題
第 5 題:介紹下深度優(yōu)先遍歷和廣度優(yōu)先遍歷佳头,如何實現(xiàn)鹰贵?
解析:第 5 題
第 6 題:請分別用深度優(yōu)先思想和廣度優(yōu)先思想實現(xiàn)一個拷貝函數(shù)?
解析:第 6 題
第 7 題:ES5/ES6 的繼承除了寫法以外還有什么區(qū)別康嘉?
先看ES5的繼承(原型鏈繼承)
function a() {
this.name = 'a';
}
a.prototype.getName = function getName() {
return this.name
}
function b() {}
b.prototype = new a();
console.log(b.prototype.__proto__ === a.prototype); // true
console.log(b.__proto__ === a); // false
console.log(b.__proto__); // [Function]
ES6繼承
class A {
constructor(a) {
this.name = a;
}
getName() {
return this.name;
}
}
class B extends A{
constructor() {
super();
}
}
console.log(B.prototype.__proto__ === A.prototype); // true
console.log(B.__proto__ === A); // true
console.log(B.__proto__); // [Function: A]
對比代碼可以知道碉输,子類的繼承都是成功的,但是問題出在凄鼻,子類的 __proto__
指向不一樣腊瑟。
ES5
的子類和父類一樣,都是先創(chuàng)建好块蚌,再實現(xiàn)繼承的闰非,所以它們的指向都是 [Function]
。
ES6
則得到不一樣的結(jié)果峭范,它指向父類财松,那么我們應(yīng)該能推算出來,它的子類是通過 super
來改造的纱控。
根據(jù) es6--阮一峰 在class繼承里面的說法辆毡,是這樣子的:
引用阮一峰的 ECMAScript6入門
的class繼承篇
子類必須在
constructor
方法中調(diào)用super
方法,否則新建實例時會報錯甜害。這是因為子類自己的this
對象舶掖,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實例屬性和方法尔店,然后再對其進行加工眨攘,加上子類自己的實例屬性和方法。如果不調(diào)用super
方法嚣州,子類就得不到this
對象鲫售。ES5 的繼承,實質(zhì)是先創(chuàng)造子類的實例對象
this
该肴,然后再將父類的方法添加到this
上面(Parent.apply(this)
)情竹。ES6 的繼承機制完全不同,實質(zhì)是先將父類實例對象的屬性和方法匀哄,加到this
上面(所以必須先調(diào)用super
方法)秦效,然后再用子類的構(gòu)造函數(shù)修改this
。
1拱雏、class 聲明會提升棉安,但不會初始化賦值。Foo 進入暫時性死區(qū)铸抑,類似于 let贡耽、const 聲明變量。
2、class 聲明內(nèi)部會啟用嚴格模式蒲赂。
3阱冶、class 的所有方法(包括靜態(tài)方法和實例方法)都是不可枚舉的。
4滥嘴、class 的所有方法(包括靜態(tài)方法和實例方法)都沒有原型對象 prototype木蹬,所以也沒有[[construct]],不能使用 new 來調(diào)用若皱。
5镊叁、必須使用 new 調(diào)用 class。
6走触、class 內(nèi)部無法重寫類名
解析:第 7 題
第 8 題:setTimeout晦譬、Promise、Async/Await 的區(qū)別
其中settimeout的回調(diào)函數(shù)放到宏任務(wù)隊列里互广,等到執(zhí)行棧清空以后執(zhí)行敛腌; promise.then里的回調(diào)函數(shù)會放到相應(yīng)宏任務(wù)的微任務(wù)隊列里,等宏任務(wù)里面的同步代碼執(zhí)行完再執(zhí)行惫皱;async函數(shù)表示函數(shù)里面可能會有異步方法像樊,await后面跟一個表達式,async方法執(zhí)行時旅敷,遇到await會立即執(zhí)行表達式生棍,然后把表達式后面的代碼放到微任務(wù)隊列里,讓出執(zhí)行棧讓同步代碼先執(zhí)行媳谁。
解析:第 8 題
第 9 題:(頭條足绅、微醫(yī))Async/Await 如何通過同步的方式實現(xiàn)異步
async await 用于把異步請求變?yōu)橥秸埱蟮姆绞?第一個請求的返回值作為后面一個請求的參數(shù),其中每一個參數(shù)都是一個promise對象
例如:這種情況工作中會經(jīng)常遇到
(async () => {
var a = await A();
var b = await B(a);
var c = await C(b);
var d = await D(c);
})();
解析:第 9 題
第 10 題:(頭條)異步筆試題
請寫出下面代碼的運行結(jié)果
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
復(fù)制代碼
解析:第 10 題
第 11 - 20 題
第 11 題:(攜程)算法手寫題
已知如下數(shù)組:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
編寫一個程序?qū)?shù)組扁平化去并除其中重復(fù)部分數(shù)據(jù),最終得到一個升序且不重復(fù)的數(shù)組
var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
// 方法一
console.log(Array.from(new Set(arr.flat(Infinity))).sort((a, b) => a - b))
// 方法二
console.log(Array.from(new Set(arr.toString().split(','))).map(Number).sort((a, b) => a - b))
// 方法三
// 第一步:扁平化
let newArr = [];
function flat(originArr) {
if ({}.toString.call(originArr) === '[object Array]') {
for (let i of originArr) {
if ({}.toString.call(i) === '[object Array]') {
arguments.callee(i)
} else {
newArr.push(i)
}
}
}
return newArr;
}
console.log(flat(arr))
// 第二步:去重
var newArr1 = [];
for (let i of newArr) {
if (!newArr1.includes(i)) newArr1.push(i);
}
// 第三步:排序 可以采用相關(guān)算法處理
console.log(newArr1.sort((a, b) => a - b))
解析:第 11 題
第 12 題:(滴滴韩脑、挖財、微醫(yī)粹污、憾味啵康)JS 異步解決方案的發(fā)展歷程以及優(yōu)缺點。
1. 回調(diào)函數(shù)(callback)
setTimeout(() => {
// callback 函數(shù)體
}, 1000)
缺點:回調(diào)地獄壮吩,不能用 try catch 捕獲錯誤进苍,不能 return`
回調(diào)地獄的根本問題在于
缺乏順序性: 回調(diào)地獄導(dǎo)致的調(diào)試困難,和大腦的思維方式不符
嵌套函數(shù)存在耦合性鸭叙,一旦有所改動觉啊,就會牽一發(fā)而動全身,即(控制反轉(zhuǎn),嵌套函數(shù)過多的多話沈贝,很難處理錯誤
ajax('XXX1', () => {
// callback 函數(shù)體
ajax('XXX2', () => {
// callback 函數(shù)體
ajax('XXX3', () => {
// callback 函數(shù)體
})
})
})
優(yōu)點:解決了同步的問題(只要有一個任務(wù)耗時很長杠人,后面的任務(wù)都必須排隊等著,會拖延整個程序的執(zhí)行。)
2. Promise
Promise就是為了解決callback的問題而產(chǎn)生的嗡善。
Promise 實現(xiàn)了鏈式調(diào)用辑莫,也就是說每次 then 后返回的都是一個全新 Promise,如果我們在 then 中 return 罩引,return 的結(jié)果會被Promise.resolve() 包裝
優(yōu)點:解決了回調(diào)地獄的問題
ajax('XXX1')
.then(res => {
// 操作邏輯
return ajax('XXX2')
}).then(res => {
// 操作邏輯
return ajax('XXX3')
}).then(res => {
// 操作邏輯
})
缺點:無法取消 Promise 各吨,錯誤需要通過回調(diào)函數(shù)來捕獲
3. Generator
特點:可以控制函數(shù)的執(zhí)行,可以配合 co 函數(shù)庫使用
function* fetch() {
yield ajax('XXX1', () => { })
yield ajax('XXX2', () => { })
yield ajax('XXX3', () => { })
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
4. Async / await
async袁铐、await 是異步的終極解決方案
優(yōu)點是:代碼清晰揭蜒,不用像 Promise 寫一大堆 then 鏈,處理了回調(diào)地獄的問題
缺點:await 將異步代碼改造成同步代碼剔桨,如果多個異步操作沒有依賴性而使用 await 會導(dǎo)致性能上的降低屉更。
async function test() {
// 以下代碼沒有依賴性的話,完全可以使用 Promise.all 的方式
// 如果有依賴性的話领炫,其實就是解決回調(diào)地獄的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
解析:第 12 題
第 13 題:(微醫(yī))Promise 構(gòu)造函數(shù)是同步執(zhí)行還是異步執(zhí)行偶垮,那么 then 方法呢?
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
執(zhí)行結(jié)果是:1243
promise構(gòu)造函數(shù)是同步執(zhí)行的帝洪,then方法是異步執(zhí)行的
解析:第 13 題
第 14 題:(兌吧)情人節(jié)福利題似舵,如何實現(xiàn)一個 new
先理清楚 new 關(guān)鍵字調(diào)用函數(shù)都的具體過程,那么寫出來就很清楚了
首先創(chuàng)建一個空的對象葱峡,空對象的 ___proto____屬性指向構(gòu)造函數(shù)的原型對象
把上面創(chuàng)建的空對象賦值構(gòu)造函數(shù)內(nèi)部的this砚哗,用構(gòu)造函數(shù)內(nèi)部的方法修改空對象
如果構(gòu)造函數(shù)返回一個非基本類型的值,則返回這個值砰奕,否則上面創(chuàng)建的對象
function _new(fn, ...arg) {
var obj = Object.create(fn.prototype);
const result = fn.apply(obj, ...arg);
return Object.prototype.toString.call(result) == '[object Object]' ? result : obj;
}
解析:第 14 題
第 15 題:(網(wǎng)易)簡單講解一下http2的多路復(fù)用
解析:第 15 題
第 16 題:談?wù)勀銓CP三次握手和四次揮手的理解
解析:第 16 題
第 17 題:A蛛芥、B 機器正常連接后,B 機器突然重啟军援,問 A 此時處于 TCP 什么狀態(tài)
如果A 與 B 建立了正常連接后仅淑,從未相互發(fā)過數(shù)據(jù),這個時候 B 突然機器重啟胸哥,問 A 此時處于 TCP 什么狀態(tài)涯竟?如何消除服務(wù)器程序中的這個狀態(tài)?(超綱題空厌,了解即可)
解析:第 17 題
第 18 題:(微醫(yī))React 中 setState 什么時候是同步的庐船,什么時候是異步的?
解析:第 18 題
第 19 題:React setState 筆試題嘲更,下面的代碼輸出什么筐钟?
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
復(fù)制代碼
解析:第 19 題
第 20 題:介紹下 npm 模塊安裝機制,為什么輸入 npm install 就可以自動安裝對應(yīng)的模塊赋朦?
解析:第 20 題