Skip to content

字节前端面试题

写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

  • key 是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对应的 vnode 节点

什么是防抖和节流?有什么区别?如何实现?

javascript

// 防抖——触发高频事件后 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)); // 防抖
// 节流——高频事件触发,但在 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));

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

  • Set——对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用
  • WeakSet——成员都是对象;成员都是弱引用,可以被垃圾回收机制回收,可以
  • 用来保存 DOM 节点,不容易造成内存泄漏;
  • Map——本质上是键值对的集合,类似集合;可以遍历,方法很多,可以跟各种数据格式转换。
  • WeakMap——只接受对象最为键名(null 除外),不接受其他类型的值作为键名;键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;不能遍历,方法有 get、set、has、delete。

介绍下深度优先遍历和广度优先遍历,如何实现?

  • 深度优先遍历——是指从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的下一个顶点进行访问。重复此步骤,直至所有结点都被访问完为止。
  • 广度优先遍历——是从某个顶点出发,首先访问这个顶点,然后找出刚访问这个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所有结点,重复此方法,直到所有结点都被访问完为止。
javascript

//1.深度优先遍历的递归写法 
function deepTraversal (node) {
  let nodes = []
  if (node != null) {
    nodes.push[node]
    let childrens = node.children
    for (let i = 0;
      i < childrens.length; i++) deepTraversal(childrens[i])
  }
  return nodes
}
//2.深度优先遍历的非递归写法
function deepTraversal (node) {
  let nodes = []
  if (node != null) {
    let stack = []
    //同来存放将来要访问的节点
    stack.push(node)
    while (stack.length != 0) {
      let item = stack.pop()
      //正在访问的节点
      nodes.push(item)
      let childrens = item.children
      for (
        let i = childrens.length - 1;
        i >= 0;
        i--
      )
        //将现在访问点的节点的子节点存入 stack,供将来访问
        stack.push(childrens[i])
    }
  }
  return nodes
}
//3.广度优先遍历的递归写法
function wideTraversal (node) {
  let nodes = [],
    i = 0
  if (node != null) {
    nodes.push(node)
    wideTraversal(node.nextElementSibling)
    node = nodes[i++]
    wideTraversal(node.firstElementChild)
  }
  return nodes
}
//4.广度优先遍历的非递归写法
 function wideTraversal (node) {
  let nodes = [], i = 0
  while (node != null) {
    nodes.push(node)
    node = nodes[i++]
    let childrens = node.children
    for (let i = 0;
      i < childrens.length;
      i++) {
      nodes.push(childrens[i])
    }
  }
  return nodes
}

请分别用深度优先思想和广度优先思想实现一个拷贝函数?

javascript
let _toString = Object.prototype.toString
let map = {
  array: 'Array',
  object: 'Object',
  function: 'Function',
  string: 'String',
  null: 'Null',
  undefined: 'Undefined',
  boolean: 'Boolean',
  number: 'Number'
}
let getType = (item) => {
  return _toString.call(item).slice(8, -1)
}
let isTypeOf = (item, type) => {
  return map[type] && map[type] === getType(item)
}

ES5 / ES6 的继承除了写法以外还有什么区别?

  • ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.apply(this))
  • ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必须先调用父类的 super()方法),然后再用子类的构造函数修改 this。
  • ES5 的继承时通过原型或构造函数机制来实现。
  • ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。
  • 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象。
  • 注意 super 关键字指代父类的实例,即父类的 this 对象。
  • 注意:在子类构造函数中,调用 super 后,才可使用 this 关键字,否则报错。function 声明会提升,但不会初始化赋值。Foo 进入暂时性死区,类似于 let、const 声明变量。

Async / Await 如何通过同步的方式实现异步

  • async 起什么作用——输出的是一个 Promise 对象

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

1、回调函数(callback)

  • 优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队
  • 等着,会拖延整个程序的执行。)
  • 缺点:回调地狱,不能用 try catch 捕获错误,不能 return

2、Promise

  • 优点:解决了回调地狱的问题
  • 缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3、Generator

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

4、Async/await

  • 优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
  • 缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?

javascript
const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
// 执行结果是:1243,promise 构造函数是同步执行的,then 方法是异步执行的

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

TCP三次握手

  • 客户端发送syn包到服务器,等待服务器确认接收。
  • 服务器确认接收syn包并确认客户的syn,并发送回来一个syn+ack的包给客户端。
  • 客户端确认接收服务器的syn+ack包,并向服务器发送确认包ack,二者相互建立联系后,完成tcp三次握手。

四次握手

  • 就是中间多了一层:等待服务器再一次响应回复相关数据的过程
  • 三次握手之所以是三次是保证client和server均让对方知道自己的接收和发送能力没问题而保证的最小次数。
  • 第一次client => server 只能server判断出client具备发送能力
  • 第二次 server => client client就可以判断出server具备发送和接受能力。此时client还需让server知道自己接收能力没问题于是就有了第三次
  • 第三次 client => server 双方均保证了自己的接收和发送能力没有问题
  • 其中,为了保证后续的握手是为了应答上一个握手,每次握手都会带一个标识 seq,后续的ACK都会对这个seq进行加一来进行确认。

React 中 setState 什么时候是同步的,什么时候是异步的?

  • 由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更新 state 。
  • React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事件,setTimeout/setInterval 等。

React setState 笔试题,下面的代码输出什么?

javascript

class Example extends React.Component {
  constructor() {
    super()
    this.state = {
      val: 0,
    }
  }

  componentDidMount() {
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)
    // 第 1 次 log
    this.setState({ val: this.state.val + 1 })
    console.log(this.state.val)
    // 第 2 次 log
    setTimeout(() => {
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
      // 第 3 次 log
      this.setState({ val: this.state.val + 1 })
      console.log(this.state.val)
      // 第 4 次 log
    }, 0)
  }

  render() {
    return null
  }
}

// 答:
// 0, 0, 1, 2

介绍下 npm 模块安装机制?

  • 发出 npm install 命令
  • 查询 node_modules 目录之中是否已经存在指定模块
  • 若存在,不再重新安装
  • 若不存在
  • npm 向 registry 查询模块压缩包的网址
  • 下载压缩包,存放在根目录下的.npm 目录里
  • 解压压缩包到当前项目的 node_modules 目录

Welcome to the site