搜尋
首頁頭條大廠前端經典面試問題精選(附答案)

大廠前端經典面試問題精選(附答案)

【相關推薦:前端面試題(2020)】

1.寫React/Vue 專案時為什麼要在組件中寫key,其作用是什麼?

key 的作用是為了在 diff 演算法執行時更快的找到對應的節點,提高 diff 速度。

vue 和 react 都是採用 diff 演算法來比較新舊虛擬節點,從而更新節點。在 vue 的 diff 函數中。可以先了解一下 diff 演算法。

在交叉比較的時候,當新節點跟舊節點頭尾交叉對比沒有結果的時候,會根據新節點的key 去比較舊節點數組中的key,從而找到對應舊節點(這裡對應的是一個key => index 的map 映射)。如果沒找到就認為是一個新增節點。而如果沒有 key,那麼就會採取一種遍歷查找的方式去找到對應的舊節點。一種一個 map 映射,另一種是遍歷查找。相比而言。 map 映射的速度更快。

vue 部分原始碼如下:

// vue 项目  src/core/vdom/patch.js  -488 行
// oldCh 是一个旧虚拟节点数组, 
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
       idxInOld = isDef(newStartVnode.key)
         ? oldKeyToIdx[newStartVnode.key]
         : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

#建立map 函數:

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 是对比新旧节点是否相同的函数
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 函數的第一個參數callback:

var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])

這個callback 一共可以接收三個參數,其中第一個參數代表目前被處理的元素,而第二個參數代表該元素的索引。

而 parseInt 則是用來解析字串的,使字串成為指定基數的整數。

parseInt(string, radix)接收兩個參數,第一個表示被處理的值(字串),第二個表示為解析時的基數。

了解這兩個函數後,我們可以模擬一下運行情況;

parseInt('1', 0)  //radix 為0 時,且string 參數不以「0x」和「0」開頭時,依照10 為基數處理。這時候回傳1;

parseInt('2', 1)  // 基數為1(1 進位)表示的數中,最大值小於2,所以無法解析,回傳NaN;

parseInt('3', 2)  // 基數為2(2 進位)表示的數中,最大值小於3,所以無法解析,回傳NaN。

map 函數傳回的是一個數組,所以最後結果為 [1, NaN, NaN]。

3. 什麼是防手震和節流?有什麼區別?如何實現?

1)防手震

觸發高頻事件後n 秒內函數只會執行一次,如果n 秒內高頻事件再被觸發,則重新計算時間;

思路:

每次觸發事件時都取消先前的延時呼叫方法:

function debounce(fn) {
     let timeout = null; // 创建一个标记用来存放定时器的返回值
     return function () {
       clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
       timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
         fn.apply(this, arguments);
       }, 500);
     };
   }
   function sayHi() {
     console.log(&#39;防抖成功&#39;);
   }

   var inp = document.getElementById(&#39;inp&#39;);
   inp.addEventListener(&#39;input&#39;, debounce(sayHi)); // 防抖

2)節流

高頻事件觸發,但在n 秒內只會執行一次,所以節流會稀釋函數的執行頻率。

思路:

每次觸發事件時都會判斷目前是否有等待執行的延時函數。

function throttle(fn) {
     let canRun = true; // 通过闭包保存一个标记
     return function () {
       if (!canRun) return; // 在函数开头判断标记是否为 true,不为 true 则 return
       canRun = false; // 立即设置为 false
       setTimeout(() => { // 将外部传入的函数的执行放在 setTimeout 中
         fn.apply(this, arguments);
         // 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
         canRun = true;
       }, 500);
     };
   }
   function sayHi(e) {
     console.log(e.target.innerWidth, e.target.innerHeight);
   }
   window.addEventListener(&#39;resize&#39;, throttle(sayHi));

4. 介紹下 Set、Map、WeakSet 和 WeakMap 的差別?

1)Set

成員唯一、無序且不重複;

[value,  value],鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名);

#可以遍歷,方法有:add、delete、has。

2)WeakSet

成員都是物件;

成員都是弱引用,可以被垃圾回收機制回收,可以用來保存DOM節點,不容易造成記憶體洩漏;

不能遍歷,方法有add、delete、有。

3)Map

本質上是鍵值對的集合,類似集合;

可以遍歷,方法很多,可以跟各種數據格式轉換。

4)WeakMap

只接受物件最為鍵名(null 除外),不接受其他類型的值作為鍵名;

鍵名是弱引用,鍵值可以是任意的,鍵名所指向的物件可以被垃圾回收,此時鍵名是無效的;

不能遍歷,方法有get、set、has、delete。

5. 介紹下深度優先遍歷和廣度優先遍歷,如何實現?

深度優先遍歷(DFS)

#深度優先遍歷(Depth-First-Search),是搜尋演算法的一種,它沿著樹的深度遍歷樹的節點,盡可能地深入搜尋樹的分支。當節點 v 的所有邊都已被探尋過,將回溯到發現節點 v 的那條邊的起始節點。這個過程一直進行到已探尋來源節點到其他所有節點為止,如果還有未被發現的節點,則選擇其中一個未被發現的節點為來源節點並重複以上操作,直到所有節點都被探尋完成。

简单的说,DFS 就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。

DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现 DFS 算法。

注意:深度 DFS 属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。

步骤:

访问顶点 v;

依次从 v 的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和 v 有路径相通的顶点都被访问;

若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止。

实现:

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

测试成功。

广度优先遍历(BFS)

广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现 BFS。

BFS 从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层。

步骤:

创建一个队列,并将开始节点放入队列中;

若队列非空,则从队列中取出第一个节点,并检测它是否为目标节点;

若是目标节点,则结束搜寻,并返回结果;

若不是,则将它所有没有被检测过的字节点都加入队列中;

若队列为空,表示图中并没有目标节点,则结束遍历。

实现:

Graph.prototype.bfs = function(v) {
   var queue = [], marked = []
   marked[v] = true
   queue.push(v) // 添加到队尾
   while(queue.length > 0) {
       var s = queue.shift() // 从队首移除
       if (this.edges.has(s)) {
           console.log(&#39;visited vertex: &#39;, 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. 异步笔试题

请写出下面代码的运行结果:

// 今日头条面试题
async function async1() {
   console.log(&#39;async1 start&#39;)
   await async2()
   console.log(&#39;async1 end&#39;)
}
async function async2() {
   console.log(&#39;async2&#39;)
}
console.log(&#39;script start&#39;)
setTimeout(function () {
   console.log(&#39;settimeout&#39;)
})
async1()
new Promise(function (resolve) {
   console.log(&#39;promise1&#39;)
   resolve()
}).then(function () {
   console.log(&#39;promise2&#39;)
})
console.log(&#39;script end&#39;)

题目的本质,就是考察setTimeout、promise、async await的实现及执行顺序,以及 JS 的事件循环的相关问题。

答案:

script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout

7. 将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b})

8.JS 异步解决方案的发展历程以及优缺点。

1)回调函数(callback)

setTimeout(() => {
   // callback 函数体
}, 1000)

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:

缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符;

嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转);

嵌套函数过多的多话,很难处理错误。

ajax(&#39;XXX1&#39;, () => {
   // callback 函数体
   ajax(&#39;XXX2&#39;, () => {
       // callback 函数体
       ajax(&#39;XXX3&#39;, () => {
           // callback 函数体
       })
   })
})

优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行)。

2)Promise

Promise 就是为了解决 callback 的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。

优点:解决了回调地狱的问题。

ajax(&#39;XXX1&#39;)
 .then(res => {
     // 操作逻辑
     return ajax(&#39;XXX2&#39;)
 }).then(res => {
     // 操作逻辑
     return ajax(&#39;XXX3&#39;)
 }).then(res => {
     // 操作逻辑
 })

缺点:无法取消 Promise ,错误需要通过回调函数来捕获。

3)Generator

特点:可以控制函数的执行,可以配合 co 函数库使用。

function *fetch() {
   yield ajax(&#39;XXX1&#39;, () => {})
   yield ajax(&#39;XXX2&#39;, () => {})
   yield ajax(&#39;XXX3&#39;, () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()

4)Async/await

async、await 是异步的终极解决方案。

优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

async function test() {
 // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
 // 如果有依赖性的话,其实就是解决回调地狱的例子了
 await fetch(&#39;XXX1&#39;)
 await fetch(&#39;XXX2&#39;)
 await fetch(&#39;XXX3&#39;)
}

下面来看一个使用 await 的例子:

let a = 0
let b = async () => {
 a = a + await 10
 console.log(&#39;2&#39;, a) // -> &#39;2&#39; 10
}
b()
a++
console.log(&#39;1&#39;, a) // -> &#39;1&#39; 1

对于以上代码你可能会有疑惑,让我来解释下原因:

首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来;

因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码;

同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10。

上述解釋中提到了 await 內部實作了 generator,其實 await 是 generator 加上 Promise的語法糖,且內部實作了自動執行 generator。如果你熟悉 co 的話,其實自己就可以實現這樣的語法糖。

9. 談談你對 TCP 三次握手和四次揮手的理解

大廠前端經典面試問題精選(附答案)

陳述
本文轉載於:前端大学。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
1 個月前By尊渡假赌尊渡假赌尊渡假赌

熱工具

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境