大厂前端经典面试问题精选(附答案)

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 掉


      // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,

      //就不会执行 fn 函数

      timeout = setTimeout(() => {

        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));

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

1)Set

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

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

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

2)WeakSet

成员都是对象;

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

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

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('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. 异步笔试题

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

// 今日头条面试题

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')

题目的本质,就是考察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('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 = 0

let 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 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来;

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

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

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

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

更多web前端开发知识,请查阅 HTML中文网 !!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • ## 框架和库的区别?> 框架(framework):一套完整的软件设计架构和**解决方案**。> > 库(lib...
    Rui_bdad阅读 2,895评论 1 4
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,028评论 0 2
  • 如何控制alert中的换行?\n alert(“p\np”); 请编写一个JavaScript函数 parseQu...
    heyunqiang99阅读 1,084评论 0 6
  • 单例模式 适用场景:可能会在场景中使用到对象,但只有一个实例,加载时并不主动创建,需要时才创建 最常见的单例模式,...
    Obeing阅读 2,058评论 1 10
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,707评论 0 5