前端面试题 你们都知道吗?

83 阅读7分钟

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

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

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

// 防抖——触发高频事件后 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。

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

深度优先遍历——是指从某个顶点出发,首先访问这个顶点,然后找出刚访问

这个结点的第一个未被访问的邻结点,然后再以此邻结点为顶点,继续找它的

下一个顶点进行访问。重复此步骤,直至所有结点都被访问完为止。

广度优先遍历——是从某个顶点出发,首先访问这个顶点,然后找出刚访问这

个结点所有未被访问的邻结点,访问完后再访问这些结点中第一个邻结点的所

有结点,重复此方法,直到所有结点都被访问完为止。

//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
}

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

let _toString = Object.prototype.toStringlet 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 的继承除了写法以外还有什么区别?

  1. ES5 的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到 this 上(Parent.apply(this))

  2. ES6 的继承机制完全不同,实质上是先创建父类的实例对象 this(所以必须先调用父类的 super()方法),然后再用子类的构造函数修改 this。

  3. ES5 的继承时通过原型或构造函数机制来实现。

  4. ES6 通过 class 关键字定义类,里面有构造方法,类之间通过 extends 关键字实现继承。

  5. 子类必须在 constructor 方法中调用 super 方法,否则新建实例报错。因为子类没有自己的 this 对象,而是继承了父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类得不到 this 对象。

  6. 注意 super 关键字指代父类的实例,即父类的 this 对象。

  7. 注意:在子类构造函数中,调用 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 方法呢?

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三次握手:

1、客户端发送syn包到服务器,等待服务器确认接收。

2、服务器确认接收syn包并确认客户的syn,并发送回来一个syn+ack的包给客户端。

3、客户端确认接收服务器的syn+ack包,并向服务器发送确认包ack,二者相互建立联系后,完成tcp三次握手。

四次握手

就是中间多了一层:等待服务器再一次响应回复相关数据的过程

三次握手之所以是三次是保证client和server均让对方知道自己的接收和发送能力没问题而保证的最小次数。

第一次client => server 只能server判断出client具备发送能力

第二次 server => client client就可以判断出server具备发送和接受能力。此时client还需让server知道自己接收能力没问题于是就有了第三次

第三次 client => server 双方均保证了自己的接收和发送能力没有问题

其中,为了保证后续的握手是为了应答上一个握手,每次握手都会带一个标识 seq,后续的ACK都会对这个seq进行加一来进行确认。

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

1、由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更

新 state 。

2、React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事

件,setTimeout/setInterval 等。

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

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 命令

  1. 查询 node_modules 目录之中是否已经存在指定模块
  2. 若存在,不再重新安装
  3. 若不存在
  4. npm 向 registry 查询模块压缩包的网址
  5. 下载压缩包,存放在根目录下的.npm 目录里
  6. 解压压缩包到当前项目的 node_modules 目录