常见javascript面试题(下)

79 阅读7分钟

1. JS 事件轮询机制

JS 引擎主线程将所有 js 代码放入执行栈中依次执行,遇到同步代码从上到下依次执行。遇到异步代码,将任务交给浏览器分线程管理模块去执行,JS 引擎主线程会继续剩下代码

例如:

  1. JS 引擎主线程遇到定时器代码,交给浏览器定时器管理模块计时,当定时器到点,浏览器定时器管理模块会将回调函数放入宏任务队列等待执行
  2. JS 引擎主线程遇到 DOM 事件代码,交给浏览器 DOM 事件管理模块绑定事件,等用户触发事件,将回调函数放入宏任务队列等待执行

等 JS 引擎主线程执行完全局所有代码,此时就会开启事件轮询(event loop)

此时异步任务队列有两个:分别是宏任务队列和微任务队列

宏任务:

  1. 定时器(setTimeout/setInterval)
  2. DOM 事件
  3. ajax

微任务:

  1. Promise.then/catch/finally
  2. async await
  3. MutationObserver(监视 DOM 元素变化,一旦发生变化就会执行某个回调)

具体流程:

  1. 取出微任务队列中回调函数,依次执行
  2. 等微任务队列中回调函数全部执行完毕,取出宏任务队列中第一个回调函数,执行
  3. 等宏任务队列中第一个回调函数执行完毕,继续依次执行微任务队列中所有回调函数
  4. 等微任务队列中回调函数全部执行完毕,取出宏任务队列中下一个回调函数,执行
  5. 等宏任务队列中第一个回调函数执行完毕,继续依次执行微任务队列中所有回调函数
  6. 以此反复

2. Web Worker

  1. 概念

Web Worker 是 H5 新特性,允许我们开辟分线程,运行 js 代码。

  1. 使用
  • const worker = new Worker('./xxx.js') 创建分线程执行 js 脚本
  • 主线程通过 worker.onmessage 事件接受分线程的消息
  • 主线程通过 worker.postMessage 方法向分线程发送消息
  • 分线程通过 self.onmessage 事件接受主线程的消息
  • 分线程通过 self.postMessage 方法向主线程发送消息
  1. 应用

主要用于 js 中大量计算工作,比如大文件上传中计算文件的 hash,使用了 web worker

3. 说出 ES6 常用新语法

  1. 简单的语法
  • const 与 let
  • 解构赋值
  • 形参默认值
  • 扩展运算符: ...
  • 模板字符串
  • 模块化语法
  • 对象的属性与方法简写
  • 新的基本数据类型 Symbol
  1. 比较复杂的语法
  • 箭头函数
    • 箭头函数没有 arguments,需要使用 ...args 获取实参列表
    • 箭头函数没有显示原型属性,不能被 new 调用
    • 箭头函数编码更加简洁,还能简写:
      • 参数只有一个,可以省略()
      • 语句只有一条,可以省略{},这条语句会作为函数的返回值(默认加 return)
    • 箭头函数没有自己的 this,它的 this 指向外层函数的 this
    • 说到 this 指向,平时我专门总结过 this 指向
      • ...(此处可以说 this 指向)
  • promise
    • promise + async & await 异步编程最终方案
    • 都属于微任务 引导 js 事件循环机制
  • Proxy(引申 vue2 和 vue3 响应式原理)
    • Proxy 语法简介
    • vue2 是通过 Object.defineProperty 实现的响应式,问题是新增数据不是响应式的
    • vue3 是通过 Proxy 实现的响应式,解决了这个问题
  • Map / Set / WeakMap / WeakSet (引申 vue3 响应式原理)
    • 他们都是新的存储数据的结构
    • Map 相当于对象,存储键值对,特点:key 可以是任意类型
    • Set 相当于数组,特点:值是唯一的 数组去重:[...new Set(arr)]
    • WeakMap 和 Map 相似,有两点不同:1. WeakMap 的 key 只能是对象类型 2. 一旦 key 对应的对象,外面没有引用,整个数据被垃圾回收机制回收
    • WeakSet 和 Set 相似,有两点不同:1. WeakSet 的值只能是对象类型 2. 一旦值对应的对象,外面没有引用,整个数据被垃圾回收机制回收
  • 还有 class 与 extends (拓展:原型、原型链、ES5继承)
    • 主要用来做继承的,继承方案有:构造函数+原型继承,组合式继承,class&extends 继承

4. 说说 ES6 的 promise

  1. 概念

promise 是一个异步编程解决方案之一,用来解决异步回调地狱问题。

  1. promise 对象内部有 3 种状态
  • pending 初始化状态
  • resolved / fulfilled 成功状态
  • rejected 失败状态

状态只能变化一次,只能有以下两种变化:

  • pending --> resolved
  • pending --> rejected

状态发生变化后不能在改了。

  1. 如何改变 promise 的状态
  • 调用 resolve(), 改成成功状态
  • 调用 reject(), 改为失败状态
  1. 生成一个 promise

new Promise()会生成 promise 实例对象,传入一个回调函数作为参数,回调函数有接收 resolve、reject 两个参数

它们用来改变 promise 对象状态的

  1. promise 实例对象上的方法

promise 实例对象上可以使用三个方法:then、catch、finally

  • 当 promise 对象变成成功状态,触发 then 第一个回调
  • 当 promise 对象变成失败状态,触发 catch 第一个回调
  • 当 promise 对象不管变成成功还是失败状态,都会触发 finally 第一个回调

三个方法:then、catch、finally 都返回一个 promise 对象,所以 promise 可以链式调用

返回的 promise 状态:

  • 如果报错了,返回失败状态
  • 如果内部返回了 promise,就看这个 promise 的状态
  • 如果没有发生上述情况,返回成功状态
  1. Promise 构造函数上的方法
  • Promise.resolve() 一般返回一个成功的 promise,也有可能返回失败的 promise
  • Promise.reject() 一定返回一个失败的 promise
    • 二次封装 axios,响应拦截器使用了 Promise.reject()
  • Promise.all([]) 所有 promise 对象成功才成功,只要有一个失败就失败
  • Promise.allSettled() 不管成功或失败都能得到结果,成功得到成功结果,失败得到失败的原因
    • 同时发送多个请求,可以使用 all 或 allSettled
  • Promise.race() 返回第一个状态发生变化的 promise(不管成功/失败)
    • 对某个请求做一个超时限制
  1. 面试题
  • 需求 1:先请求 a、再请求 b、最后请求 c,怎么做? async await a() await b() await c()
  • 需求 2:同时请求 a、b、c,怎么做?

Promise.all([a, b, c])

  • 需求 3:Promise.all 一旦有一个失败了,就全失败了, 不太好(一个失败,其他白做了)

const result = await Promise.allSettled([a, b, c])

result.filter(item => item.status === 'fulfilled')

5. 谈谈模块化语法

  1. Commonjs 模块化语法
  • 主要用于服务器端(Nodejs)
  • 语法:
    • 引入 require
      • 自定义模块,模块路径必须以./或../开头
      • 别人的默认,模块路径直接写模块名称即可
    • 暴露 exports/module.exports
      • 模块最终暴露的是 module.exports 指向的值
  1. ES6 模块化语法
  • 主要用于浏览器端
  • 语法:
    • 引入:import
    • 暴露:export
  • 总结
    • 如果模块采用默认暴露:import xxx from 'xxx'
    • 如果模块采用分别/统一暴露:
      • 如果需要引入模块部分内容:import { xxx } from 'xxx'
      • 如果需要引入模块全部内容:import * as xxx from 'xxx'

6. 谈谈箭头函数

  1. 箭头函数没有自己的 this,它的 this 指向外层函数的 this
  2. 箭头函数没有 arguments,需要使用 ...args 获取实参列表
  3. 箭头函数没有显示原型属性,不能被 new 调用
  4. 箭头函数编码更加简洁,还能简写:
  • 参数只有一个,可以省略()
  • 语句只有一条,可以省略{},这条语句会作为函数的返回值(默认加 return)

7. 浅度克隆和深度克隆

  1. 深浅克隆有什么区别?
  • 浅克隆:基本类型克隆值,引用类型克隆地址值。
    • 问题:源对象和克隆新对象的引用类型还是同一个地址,修改这个数据都会发生变化
  • 深克隆:将所有值全都克隆新的,不会有上述问题
  1. 有哪些是浅克隆?
  • Object.assign()
  • arr.slice()
  • [...arr]
  1. 有哪些是深克隆?
  • JSON.parse(JSON.stringify(xxx)) : 函数、日期等类型没法克隆
  • 自定义实现深克隆: 函数、日期等类型没法克隆
  • lodash/cloneDeep