HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

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

  • 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:1710171

评论 1 条评论

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

POW+POS模式挖矿+利息分红混合模式系统开发

V\TG【ch3nguang】

挖矿系统开发

拥抱变革 向新而行丨瑞友信创新品隆重上市暨战略发布盛典圆满落幕

瑞友

信创 信创重器 天下共享 瑞友公司 信创解决方案

行业首创 !Graph RAG:基于知识图谱的检索增强技术与优势对比(附 Demo)

悦数图数据库

图数据库

面部表情识别技术在医疗领域的应用

来自四九城儿

《华为云DTSE》期刊2023年第二季—HDC.Cloud 2023专刊

华为云开发者联盟

云计算 华为云 华为云开发者联盟 企业号 8 月 PK 榜

云米科技的互联网家电太贴心啦!幸福感直线拉升

新消费日报

React 的七个值得推荐的动画库

EquatorCoco

前端 React 动画效果

龙蜥白皮书精选:SysAK—大规模复杂场景的系统运维利器

OpenAnolis小助手

操作系统 白皮书 系统运维 龙蜥社区 SysAK

用友Fast by BIP&华为云联合产品重磅首发,成就数智企业!

用友BIP

推荐程序员提高生产力的工具

互联网工科生

版本控制 生产力 单元测试 功能测试

Microsoft Word 2019 for mac(文字处理软件word mac)中文正式版

mac

windows 苹果mac Word 2019 文字处理软件

杭州悦数入选 Gartner®《中国数据分析与人工智能技术成熟度曲线,2023》

悦数图数据库

图数据库

面部表情识别技术在教育领域的应用前景

来自四九城儿

mac电脑3D图形必备Maya 2024 最新中文激活版

胖墩儿不胖y

玛雅2024下载 Mac软件推 图形软件

技术分享 | RCU :内核小“马达”,让你的产品弯道超车

鼎道智联

代码人生

面部表情识别的未来发展趋势和应用前景

来自四九城儿

架构设计:Docker容器化部署

高端章鱼哥

Docker 架构 容器化部署

从数据孤岛到企业xP&A的演化

智达方通

全面预算管理 全面预算管理系统 扩展规划和分析

一步到位,详解 Milvus 2.3

Zilliz

非结构化数据 Milvus Zilliz 向量数据库

一体化智能可观测平台助力车企数智化转型

博睿数据

关于低代码开发平台的思考

树上有只程序猿

软件开发 低代码 数字化 企业级应用程序开发 JNPF

用友Fast by BIP 助力大型企业“两快两省”实现财务数智化转型!

用友BIP

大厂简历优化+面试题目+面经

程序员万金游

多线程并发 #java #面试 #资料 #Spring

这一天,中国企业一同吹响数字化集结号

脑极体

b2b 企业节

英特尔吕冬:英特尔与玩家共同见证电竞首次入亚

E科讯

Principle for Mac(动画交互设计软件) v6.25中文版

mac

苹果mac Windows软件 Principle 动画交互式软件

低代码的探索之路

高端章鱼哥

软件开发 低代码 企业数字化转型 JNPF

软件测试/测试开发丨Pytest和Allure报告 学习笔记

测试人

Python 程序员 软件测试 pytest Allure

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