【QCon】精华内容上线92%,全面覆盖“人工智能+”的典型案例!>>> 了解详情
写点什么

大厂前端高频面试问题与答案精选

  • 2019-02-21
  • 本文字数:5980 字

    阅读完需:约 20 分钟

大厂前端高频面试问题与答案精选

近日,GitHub 上一位名为木易杨(yygmind)的开发者,在 GitHub 中建了一个名为 Advanced-Frontend/Daily-Interview-Question 项目,该项目每天会更新一道前端大厂面试题,并邀请开发者在 issue 区中作答,以下是我们从该项目中挑选的 9 道题和答案,希望能给大家一些帮助。


GitHub 链接:


https://github.com/Advanced-Frontend/Daily-Interview-Question

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    }  }
复制代码


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/1

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)接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。


  • 了解这两个函数后,我们可以模拟一下运行情况


  1. parseInt(‘1’, 0) //radix 为 0 时,且 string 参数不以“0x”和“0”开头时,按照 10 为基数处理。这个时候返回 1;

  2. parseInt(‘2’, 1) //基数为 1(1 进制)表示的数中,最大值小于 2,所以无法解析,返回 NaN;

  3. parseInt(‘3’, 2) //基数为 2(2 进制)表示的数中,最大值小于 3,所以无法解析,返回 NaN。



本题链接:


https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/4

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('防抖成功');    }
var inp = document.getElementById('inp'); inp.addEventListener('input', 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('resize', throttle(sayHi));
复制代码


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/5

4.介绍下 Set、Map、WeakSet 和 WeakMap 的区别?

Set


  • 成员唯一、无序且不重复;

  • [value, value],键值与键名是一致的(或者说只有键值,没有键名);

  • 可以遍历,方法有:add、delete、has。


WeakSet


  • 成员都是对象;

  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏;

  • 不能遍历,方法有 add、delete、has。


Map


  • 本质上是键值对的集合,类似集合;

  • 可以遍历,方法很多,可以跟各种数据格式转换。


WeakMap


  • 只接受对象最为键名(null 除外),不接受其他类型的值作为键名;

  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;

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


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/6

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('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
复制代码


测试成功。


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/9

6.异步笔试题

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


// 今日头条面试题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')

复制代码


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


答案:


script startasync1 startasync2promise1script endasync1 endpromise2settimeout
复制代码


过程详解链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/7

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

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


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/8

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

1. 回调函数(callback)


setTimeout(() => {    // callback 函数体}, 1000)
复制代码


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


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


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

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

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


ajax('XXX1', () => {    // callback 函数体    ajax('XXX2', () => {        // callback 函数体        ajax('XXX3', () => {            // callback 函数体        })    })})
复制代码


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


2. Promise


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


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


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


ajax('XXX1')  .then(res => {      // 操作逻辑      return ajax('XXX2')  }).then(res => {      // 操作逻辑      return ajax('XXX3')  }).then(res => {      // 操作逻辑  })
复制代码


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


3. Generator


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


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 是异步的终极解决方案。


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


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


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


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


let a = 0let b = async () => {  a = a + await 10  console.log('2', a) // -> '2' 10}b()a++console.log('1', a) // -> '1' 1
复制代码


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


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

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

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


上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/11

9.谈谈你对 TCP 三次握手和四次挥手的理解


本题链接:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/15


更多内容,请关注前端之巅。



会议推荐


2019 年 6 月,GMTC 全球大前端技术大会 2019 即将到来。小程序、Flutter、移动 AI、工程化、性能优化…大前端的下一站在哪里?点击下图了解更多详情。



2019-02-21 10:1710060

评论 1 条评论

发布
用户头像
2019-11-07 21:55
回复
没有更多了
发现更多内容

语音直播系统——开发推送通知需要遵守的原则

开源直播系统源码

Snowflake vs. Databricks谁更胜一筹?2022年最新战报

雨果

数据中台 数据平台 snowflake DaaS数据即服务 Databricks

WEB服务如何平滑的上下线

转转技术团队

nginx Java web

通过14个入门实战案例教大家快速学习Python编程语言

小院里的霍大侠

Python Python初学者 程序员‘ 编程入门 兴趣编程

Java——AOP案例之测量业务层接口执行效率

王小凡

Java 程序员 apo

程序员培训学习后好找工作吗

小谷哥

以科技传递温度,vivo亮相数字中国建设峰会

Geek_2d6073

为啥国内大厂都把云计算当成香饽饽,这个万亿市场你真的了解吗

雨果

DaaS数据即服务

一骑入秦川——浅聊Beego AutoRouter是如何工作

Regan Yue

Go 源码刨析 Go web 签约计划第三季 Beego

学习Typescript(一)

bo

前端 ts 7月月更

es6 class 继承的重点

掘金安东尼

JavaScript 前端 ES6 7月月更

在web前端培训机构应该如何学习前端

小谷哥

C# 中的转译字符'/b'

陈言必行

7月月更

缓存一致性与内存屏障

蝉沐风

volatile 内存屏障 缓存一致性 MESI

C# 窗体应用常用基础控件讲解(适合萌新)

IC00

C# 7月月更

续上,Python爬虫在 fiddler 中调用本地 JS 代码文件

梦想橡皮擦

Python 爬虫 7月月更

web前端技术前景如何?

小谷哥

音乐体验天花板!14个网易云音乐的情感化设计细节

张姣发

创新 交互设计 网易云音乐

OpenAtom OpenHarmony分论坛,今天14:00见!附大事记精彩发布

OpenHarmony开发者

OpenHarmony

YonBuilder赋能创新,用友第四届开发者大赛“金键盘奖”开启竞逐!

科技热闻

学习Java开发技术建议

小谷哥

Qt | 关于Qt Creator打开项目编译不过的问题

YOLO.

qt 7月月更

如何使用TDengine Sink Connector?

TDengine

tdengine Kafk

数据分析如何解决商业问题?这里有份超详细攻略

博文视点Broadview

华硕无双,这可能是屏幕最好的平价高刷轻薄笔记本

科技热闻

深入理解MySQL主从复制原理以及集群部署过程

jiangxl

MySQL 数据库 运维 dba 签约计划第三季

Java 多商户外贸版 PC 端功能,出场就是这么硬核!

CRMEB

什么是私域流量?

源字节1号

软件开发

字节前端面试题总结

buchila11

前端面试

尚硅谷Elasticsearch8.X视频教程

小谷哥

腾讯云联合中国工联院发布工业AI质检标准化研究成果加速制造业智能化转型

科技热闻

大厂前端高频面试问题与答案精选_大前端_yygmind_InfoQ精选文章