本篇內(nèi)容介紹了“分享高級web前端程序員面試問題”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
創(chuàng)新互聯(lián)憑借專業(yè)的設(shè)計(jì)團(tuán)隊(duì)扎實(shí)的技術(shù)支持、優(yōu)質(zhì)高效的服務(wù)意識和豐厚的資源優(yōu)勢,提供專業(yè)的網(wǎng)站策劃、成都做網(wǎng)站、成都網(wǎng)站建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站優(yōu)化、軟件開發(fā)、網(wǎng)站改版等服務(wù),在成都10余年的網(wǎng)站建設(shè)設(shè)計(jì)經(jīng)驗(yàn),為成都千余家中小型企業(yè)策劃設(shè)計(jì)了網(wǎng)站。
1. 寫 React/Vue 項(xiàng)目時為什么要在組件中寫 key,其作用是什么?
key 的作用是為了在 diff 算法執(zhí)行時更快的找到對應(yīng)的節(jié)點(diǎn),提高 diff 速度。
vue 和 react 都是采用 diff 算法來對比新舊虛擬節(jié)點(diǎn),從而更新節(jié)點(diǎn)。在 vue 的 diff 函數(shù)中??梢韵攘私庖幌?diff 算法。
在交叉對比的時候,當(dāng)新節(jié)點(diǎn)跟舊節(jié)點(diǎn)頭尾交叉對比沒有結(jié)果的時候,會根據(jù)新節(jié)點(diǎn)的 key 去對比舊節(jié)點(diǎn)數(shù)組中的 key,從而找到相應(yīng)舊節(jié)點(diǎn)(這里對應(yīng)的是一個 key => index 的 map 映射)。如果沒找到就認(rèn)為是一個新增節(jié)點(diǎn)。而如果沒有 key,那么就會采用一種遍歷查找的方式去找到對應(yīng)的舊節(jié)點(diǎn)。一種一個 map 映射,另一種是遍歷查找。相比而言。map 映射的速度更快。
vue 部分源碼如下:
// vue 項(xiàng)目 src/core/vdom/patch.js -488 行 // oldCh 是一個舊虛擬節(jié)點(diǎn)數(shù)組, if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
創(chuàng)建 map 函數(shù):
function createKeyToOldIdx (children, beginIdx, endIdx) { let i, key const map = {} for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i } return map }
遍歷尋找:
// sameVnode 是對比新舊節(jié)點(diǎn)是否相同的函數(shù) function findIdxInOld (node, oldCh, start, end) { for (let i = start; i < end; i++) { const c = oldCh[i] if (isDef(c) && sameVnode(node, c)) return i } }
2. 解析 [‘1’, ‘2’, ‘3’].map(parseInt)
第一眼看到這個題目的時候,腦海跳出的答案是 [1, 2, 3],但是真正的答案是 [1, NaN, NaN]。
首先讓我們回顧一下,map 函數(shù)的第一個參數(shù) callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個 callback 一共可以接收三個參數(shù),其中第一個參數(shù)代表當(dāng)前被處理的元素,而第二個參數(shù)代表該元素的索引。
而 parseInt 則是用來解析字符串的,使字符串成為指定基數(shù)的整數(shù)。
parseInt(string, radix)
接收兩個參數(shù),第一個表示被處理的值(字符串),第二個表示為解析時的基數(shù)。
了解這兩個函數(shù)后,我們可以模擬一下運(yùn)行情況
parseInt(‘1’, 0) //radix 為 0 時,且 string 參數(shù)不以“0x”和“0”開頭時,按照 10 為基數(shù)處理。這個時候返回 1;
parseInt(‘2’, 1) // 基數(shù)為 1(1 進(jìn)制)表示的數(shù)中,最大值小于 2,所以無法解析,返回 NaN;
parseInt(‘3’, 2) // 基數(shù)為 2(2 進(jìn)制)表示的數(shù)中,最大值小于 3,所以無法解析,返回 NaN。
map 函數(shù)返回的是一個數(shù)組,所以最后結(jié)果為 [1, NaN, NaN]。
最后附上 MDN 上對于這兩個函數(shù)的鏈接,具體參數(shù)大家可以到里面看:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/parseInt
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
什么是防抖和節(jié)流?有什么區(qū)別?如何實(shí)現(xiàn)?
防抖
觸發(fā)高頻事件后 n 秒內(nèi)函數(shù)只會執(zhí)行一次,如果 n 秒內(nèi)高頻事件再次被觸發(fā),則重新計(jì)算時間;
思路:
每次觸發(fā)事件時都取消之前的延時調(diào)用方法:
function debounce(fn) { let timeout = null; // 創(chuàng)建一個標(biāo)記用來存放定時器的返回值 return function () { clearTimeout(timeout); // 每當(dāng)用戶輸入的時候把前一個 setTimeout clear 掉 timeout = setTimeout(() => { // 然后又創(chuàng)建一個新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內(nèi)如果還有字符輸入的話,就不會執(zhí)行 fn 函數(shù) fn.apply(this, arguments); }, 500); }; } function sayHi() { console.log('防抖成功'); } var inp = document.getElementById('inp'); inp.addEventListener('input', debounce(sayHi)); // 防抖
2. 節(jié)流
高頻事件觸發(fā),但在 n 秒內(nèi)只會執(zhí)行一次,所以節(jié)流會稀釋函數(shù)的執(zhí)行頻率。
思路:
每次觸發(fā)事件時都判斷當(dāng)前是否有等待執(zhí)行的延時函數(shù)。
function throttle(fn) { let canRun = true; // 通過閉包保存一個標(biāo)記 return function () { if (!canRun) return; // 在函數(shù)開頭判斷標(biāo)記是否為 true,不為 true 則 return canRun = false; // 立即設(shè)置為 false setTimeout(() => { // 將外部傳入的函數(shù)的執(zhí)行放在 setTimeout 中 fn.apply(this, arguments); // 最后在 setTimeout 執(zhí)行完畢后再把標(biāo)記設(shè)置為 true(關(guān)鍵) 表示可以執(zhí)行下一次循環(huán)了。當(dāng)定時器沒有執(zhí)行的時候標(biāo)記永遠(yuǎn)是 false,在開頭被 return 掉 canRun = true; }, 500); }; } function sayHi(e) { console.log(e.target.innerWidth, e.target.innerHeight); } window.addEventListener('resize', throttle(sayHi));
4. 介紹下 Set、Map、WeakSet 和 WeakMap 的區(qū)別?
Set
成員唯一、無序且不重復(fù);
[value, value],鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名);
可以遍歷,方法有:add、delete、has。
WeakSet
成員都是對象;
成員都是弱引用,可以被垃圾回收機(jī)制回收,可以用來保存 DOM 節(jié)點(diǎn),不容易造成內(nèi)存泄漏;
不能遍歷,方法有 add、delete、has。
Map
本質(zhì)上是鍵值對的集合,類似集合;
可以遍歷,方法很多,可以跟各種數(shù)據(jù)格式轉(zhuǎn)換。
WeakMap
只接受對象最為鍵名(null 除外),不接受其他類型的值作為鍵名;
鍵名是弱引用,鍵值可以是任意的,鍵名所指向的對象可以被垃圾回收,此時鍵名是無效的;
不能遍歷,方法有 get、set、has、delete。
5. 介紹下深度優(yōu)先遍歷和廣度優(yōu)先遍歷,如何實(shí)現(xiàn)?
深度優(yōu)先遍歷(DFS)
深度優(yōu)先遍歷(Depth-First-Search),是搜索算法的一種,它沿著樹的深度遍歷樹的節(jié)點(diǎn),盡可能深地搜索樹的分支。當(dāng)節(jié)點(diǎn) v 的所有邊都已被探尋過,將回溯到發(fā)現(xiàn)節(jié)點(diǎn) v 的那條邊的起始節(jié)點(diǎn)。這一過程一直進(jìn)行到已探尋源節(jié)點(diǎn)到其他所有節(jié)點(diǎn)為止,如果還有未被發(fā)現(xiàn)的節(jié)點(diǎn),則選擇其中一個未被發(fā)現(xiàn)的節(jié)點(diǎn)為源節(jié)點(diǎn)并重復(fù)以上操作,直到所有節(jié)點(diǎn)都被探尋完成。
簡單的說,DFS 就是從圖中的一個節(jié)點(diǎn)開始追溯,直到最后一個節(jié)點(diǎn),然后回溯,繼續(xù)追溯下一條路徑,直到到達(dá)所有的節(jié)點(diǎn),如此往復(fù),直到?jīng)]有路徑為止。
DFS 可以產(chǎn)生相應(yīng)圖的拓?fù)渑判虮?,利用拓?fù)渑判虮砜梢越鉀Q很多問題,例如最大路徑問題。一般用堆數(shù)據(jù)結(jié)構(gòu)來輔助實(shí)現(xiàn) DFS 算法。
注意:深度 DFS 屬于盲目搜索,無法保證搜索到的路徑為最短路徑,也不是在搜索特定的路徑,而是通過搜索來查看圖中有哪些路徑可以選擇。
步驟:
訪問頂點(diǎn) v;
依次從 v 的未被訪問的鄰接點(diǎn)出發(fā),對圖進(jìn)行深度優(yōu)先遍歷;直至圖中和 v 有路徑相通的頂點(diǎn)都被訪問;
若此時途中尚有頂點(diǎn)未被訪問,則從一個未被訪問的頂點(diǎn)出發(fā),重新進(jìn)行深度優(yōu)先遍歷,直到所有頂點(diǎn)均被訪問過為止。
實(shí)現(xiàn):
Graph.prototype.dfs = function() { var marked = [] for (var i=0; i<this.vertices.length; i++) { if (!marked[this.vertices[i]]) { dfsVisit(this.vertices[i]) } } function dfsVisit(u) { let edges = this.edges marked[u] = true console.log(u) var neighbors = edges.get(u) for (var i=0; i<neighbors.length; i++) { var w = neighbors[i] if (!marked[w]) { dfsVisit(w) } } } }
測試:
graph.dfs() // 1 // 4 // 3 // 2 // 5
測試成功。
廣度優(yōu)先遍歷(BFS)
廣度優(yōu)先遍歷(Breadth-First-Search)是從根節(jié)點(diǎn)開始,沿著圖的寬度遍歷節(jié)點(diǎn),如果所有節(jié)點(diǎn)均被訪問過,則算法終止,BFS 同樣屬于盲目搜索,一般用隊(duì)列數(shù)據(jù)結(jié)構(gòu)來輔助實(shí)現(xiàn) BFS。
BFS 從一個節(jié)點(diǎn)開始,嘗試訪問盡可能靠近它的目標(biāo)節(jié)點(diǎn)。本質(zhì)上這種遍歷在圖上是逐層移動的,首先檢查最靠近第一個節(jié)點(diǎn)的層,再逐漸向下移動到離起始節(jié)點(diǎn)最遠(yuǎn)的層。
步驟:
創(chuàng)建一個隊(duì)列,并將開始節(jié)點(diǎn)放入隊(duì)列中;
若隊(duì)列非空,則從隊(duì)列中取出第一個節(jié)點(diǎn),并檢測它是否為目標(biāo)節(jié)點(diǎn);
若是目標(biāo)節(jié)點(diǎn),則結(jié)束搜尋,并返回結(jié)果;
若不是,則將它所有沒有被檢測過的字節(jié)點(diǎn)都加入隊(duì)列中;
若隊(duì)列為空,表示圖中并沒有目標(biāo)節(jié)點(diǎn),則結(jié)束遍歷。
實(shí)現(xiàn):
Graph.prototype.bfs = function(v) { var queue = [], marked = [] marked[v] = true queue.push(v) // 添加到隊(duì)尾 while(queue.length > 0) { var s = queue.shift() // 從隊(duì)首移除 if (this.edges.has(s)) { console.log('visited vertex: ', s) } let neighbors = this.edges.get(s) for(let i=0;i<neighbors.length;i++) { var w = neighbors[i] if (!marked[w]) { marked[w] = true queue.push(w) } } } }
測試:
graph.bfs(1) // visited vertex: 1 // visited vertex: 4 // visited vertex: 3 // visited vertex: 2 // visited vertex: 5
測試成功。
6. 異步筆試題
請寫出下面代碼的運(yùn)行結(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') }) async1() new Promise(function (resolve) { console.log('promise1') resolve() }).then(function () { console.log('promise2') }) console.log('script end')
題目的本質(zhì),就是考察setTimeout、promise、async await的實(shí)現(xiàn)及執(zhí)行順序,以及 JS 的事件循環(huán)的相關(guān)問題。
答案:
script start async1 start async2 promise1 script end async1 end promise2 settimeout
7. 將數(shù)組扁平化并去除其中重復(fù)數(shù)據(jù),最終得到一個升序且不重復(fù)的數(shù)組
Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})
8.JS 異步解決方案的發(fā)展歷程以及優(yōu)缺點(diǎn)。
回調(diào)函數(shù)(callback)
setTimeout(() => { // callback 函數(shù)體 }, 1000)
缺點(diǎn):回調(diào)地獄,不能用 try catch 捕獲錯誤,不能 return
回調(diào)地獄的根本問題在于:
缺乏順序性: 回調(diào)地獄導(dǎo)致的調(diào)試?yán)щy,和大腦的思維方式不符;
嵌套函數(shù)存在耦合性,一旦有所改動,就會牽一發(fā)而動全身,即(控制反轉(zhuǎn));
嵌套函數(shù)過多的多話,很難處理錯誤。
ajax('XXX1', () => { // callback 函數(shù)體 ajax('XXX2', () => { // callback 函數(shù)體 ajax('XXX3', () => { // callback 函數(shù)體 }) }) })
優(yōu)點(diǎn):解決了同步的問題(只要有一個任務(wù)耗時很長,后面的任務(wù)都必須排隊(duì)等著,會拖延整個程序的執(zhí)行)。
2. Promise
Promise 就是為了解決 callback 的問題而產(chǎn)生的。
Promise 實(shí)現(xiàn)了鏈?zhǔn)秸{(diào)用,也就是說每次 then 后返回的都是一個全新 Promise,如果我們在 then 中 return ,return 的結(jié)果會被 Promise.resolve() 包裝。
優(yōu)點(diǎn):解決了回調(diào)地獄的問題。
ajax('XXX1') .then(res => { // 操作邏輯 return ajax('XXX2') }).then(res => { // 操作邏輯 return ajax('XXX3') }).then(res => { // 操作邏輯 })
缺點(diǎn):無法取消 Promise ,錯誤需要通過回調(diào)函數(shù)來捕獲。
3. Generator
特點(diǎn):可以控制函數(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)點(diǎn)是:代碼清晰,不用像 Promise 寫一大堆 then 鏈,處理了回調(diào)地獄的問題;
缺點(diǎn):await 將異步代碼改造成同步代碼,如果多個異步操作沒有依賴性而使用 await 會導(dǎo)致性能上的降低。
async function test() { // 以下代碼沒有依賴性的話,完全可以使用 Promise.all 的方式 // 如果有依賴性的話,其實(shí)就是解決回調(diào)地獄的例子了 await fetch('XXX1') await fetch('XXX2') await fetch('XXX3') }
下面來看一個使用 await 的例子:
let a = 0 let b = async () => { a = a + await 10 console.log('2', a) // -> '2' 10 } b() a++ console.log('1', a) // -> '1' 1
對于以上代碼你可能會有疑惑,讓我來解釋下原因:
首先函數(shù) b 先執(zhí)行,在執(zhí)行到 await 10 之前變量 a 還是 0,因?yàn)?await 內(nèi)部實(shí)現(xiàn)了 generator ,generator 會保留堆棧中東西,所以這時候 a = 0 被保存了下來;
因?yàn)?await 是異步操作,后來的表達(dá)式不返回 Promise 的話,就會包裝成 Promise.reslove(返回值),然后會去執(zhí)行函數(shù)外的同步代碼;
同步代碼執(zhí)行完畢后開始執(zhí)行異步代碼,將保存下來的值拿出來使用,這時候 a = 0 + 10。
上述解釋中提到了 await 內(nèi)部實(shí)現(xiàn)了 generator,其實(shí) await 就是 generator 加上 Promise的語法糖,且內(nèi)部實(shí)現(xiàn)了自動執(zhí)行 generator。如果你熟悉 co 的話,其實(shí)自己就可以實(shí)現(xiàn)這樣的語法糖。
9. 談?wù)勀銓?TCP 三次握手和四次揮手的理解
“分享高級web前端程序員面試問題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
本文題目:分享高級web前端程序員面試問題
當(dāng)前地址:http://muchs.cn/article12/ghjddc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、定制網(wǎng)站、網(wǎng)站維護(hù)、網(wǎng)站改版、關(guān)鍵詞優(yōu)化、
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)