Skip to content

2023-07-15 前端面试题

2023-07-15-js 基础.doc

  1. JS 数据类型都有哪些?
  2. JS 闭包是什么?应用场景?
  3. Vue2 data 为什么是函数?
  4. Vue2 生命周期钩子的都有哪些?
  5. JS 写一个 EventBus?
  6. JS 写一个快速排序?
  7. JS 事件循环机制?
  8. JS MutationObserver?

1. JavaScript 数据类型

在 JavaScript 中,数据类型分为两类:基本数据类型和引用数据类型。

基本数据类型有:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol
  • BigInt(ES2020 引入)

引用数据类型有:

  • Object
  • Array
  • Function

2. JavaScript 闭包

闭包是 JavaScript 的一个重要特性,指的是一个函数与其相关的引用环境组合而成的实体。闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,函数在每次创建时生成闭包。

应用场景:闭包常用于实现 JavaScript 层的私有方法和私有变量,也用于实现数据隐藏和封装。

3. Vue2 中 data 为什么是函数?

这是为了防止多个组件实例之间数据互相影响。每个 Vue 组件实例应该管理和维护自己的数据状态,因此需要一个自己独立的 data 对象。如果 data 是一个对象,则当我们复用组件时,所有实例会共享同一个 data 对象,从而导致一个组件的状态更改可能影响其他所有实例。如果 data 是一个函数,每个实例会返回一个全新的数据副本,实例之间的状态相互独立,互不干扰。

4. Vue2 生命周期钩子

Vue 的生命周期钩子主要包括:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeDestroy
  • destroyed
  • errorCaptured (2.5.0+ 新增)
  • activated (对于 keep-alive 组件)
  • deactivated (对于 keep-alive 组件)

5. JavaScript EventBus 实现

函数的实现方式

javascript
// 面试题:实现 EventBus 类
// on、emit、off、once
function EventBus() {
  this.events = new Map(); // {key => [func, func, ...]}

  this.on = function (name, func) {
    // 1. 判断name 是否有值
    // 2. 如果有那么直接设置值
    // 3. 没有的话添加一个值
    // 4. key 是 传入的name
    // 5. value [func, func]
    let arr = this.events.get(name);
    if (Array.isArray(arr)) {
      arr.push({ func });
    } else {
      arr = [{ func }];
    }
    this.events.set(name, arr);
  };
  this.emit = function (name, ...reset) {
    const arr = this.events.get(name);

    if (Array.isArray(arr)) {
      arr.forEach(({ func, once }) => {
        func(...reset);
        if (once) {
          this.off(name, func);
        }
      });
    }
  };
  this.off = function (name, func) {
    let arr = this.events.get(name);
    if (Array.isArray(arr)) {
      arr = arr.filter(({ func: item }) => item !== func);
    }

    this.events.set(name, arr);
  };

  this.once = function (name, func) {
    let arr = this.events.get(name);
    if (Array.isArray(arr)) {
      arr.push({ once: true, func });
    } else {
      arr = [{ once: true, func }];
    }
    this.events.set(name, arr);
  };
}

Class 类的实现方式

javascript
// 面试题:实现 EventBus 类
// on、emit、off、once
class EventBus {
  constructor() {
    this.events = new Map(); // {key => [func, func, ...]}
  }
  on(name, func) {
    // 1. 判断name 是否有值
    // 2. 如果有那么直接设置值
    // 3. 没有的话添加一个值
    // 4. key 是 传入的name
    // 5. value [func, func]
    let arr = this.events.get(name);
    if (Array.isArray(arr)) {
      arr.push({ func });
    } else {
      arr = [{ func }];
    }
    this.events.set(name, arr);
  }
  emit(name, ...reset) {
    const arr = this.events.get(name);

    if (Array.isArray(arr)) {
      arr.forEach(({ func, once }) => {
        func(...reset);
        if (once) {
          this.off(name, func);
        }
      });
    }
  }
  off(name, func) {
    let arr = this.events.get(name);
    if (Array.isArray(arr)) {
      arr = arr.filter(({ func: item }) => item !== func);
    }

    this.events.set(name, arr);
  }

  once(name, func) {
    let arr = this.events.get(name);
    if (Array.isArray(arr)) {
      arr.push({ once: true, func });
    } else {
      arr = [{ once: true, func }];
    }
    this.events.set(name, arr);
  }
}

const eventBus = new EventBus();

function sayHello(name) {
  console.log(name + " hello!");
}

function add(a, b) {
  console.log(a + b);
}

eventBus.on("say-hello", sayHello);
eventBus.on("add", add);
eventBus.emit("say-hello", "Lizhongyi1");
eventBus.off("say-hello", sayHello);
eventBus.emit("say-hello", "Lizhongyi2");
eventBus.emit("add", 1, 2);
eventBus.emit("add", 3, 2);
eventBus.emit("add", 4, 2);
eventBus.emit("add", 9, 2);

6. JavaScript 快速排序

javascript
// 快速排序(Quick Sort)是一种使用分治法(Divide and Conquer)策略来实现的排序算法,由C. A. R. Hoare在1960年提出。在平均情况下,排序n个项目需要O(n log n)次比较,在最坏的情况下需要O(n^2)次比较。

// left right swaIndex

function quickStart(arr, left = 0, right = arr.length - 1) {
  if (left < right) {
    const swaIndex = pivot(arr, left, right);
    quickStart(arr, left, swaIndex - 1);
    quickStart(arr, swaIndex + 1, right);
  }

  return arr;
}

// 根据pivot对数组进行划分,并返回pivot的最终位置
function pivot(arr, start = 0, end = arr.length - 1) {
  const fistItem = arr[start];
  let swaIndex = start;

  for (let index = start + 1; index <= end; index++) {
    if (fistItem > arr[index]) {
      swaIndex++;
      //   const item1 = arr[index];
      //   arr[index] = arr[swaIndex];
      //   arr[swaIndex] = item1;
      [arr[swaIndex], arr[index]] = [arr[index], arr[swaIndex]];
    }
  }

  //   const item1 = arr[start];
  //   arr[start] = arr[swaIndex];
  //   arr[swaIndex] = item1;
  [arr[swaIndex], arr[start]] = [arr[start], arr[swaIndex]];

  return swaIndex;
}

const input = [5, 2, 1, 4, 6, 3, 7, -1, 4, 2, 2, 3];

const output = quickStart(input);

console.log(output); // [1, 2 , 3, 4, 5, 6, 7]

7. JavaScript 事件循环机制

JavaScript 是单线程的,所以为了防止阻塞主线程,引入了事件循环(Event Loop)机制。事件循环是 JS 实现异步的一种方法,它的运行机制如下:

  • 同步任务都在主线程上执行,形成一个执行栈。
  • 主线程之外,还存在一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
  • 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
  • 主线程不断重复上面的第三步。

宏任务(macro-task)和微任务(micro-task)是事件循环的重要概念。宏任务包括:script(全局任务)、setTimeout、setInterval、setImmediate(Node.js 环境)等;微任务包括:Promise、process.nextTick(Node.js 环境)、MutationObserver(html5 新特性)等。

事件循环的顺序,决定了 JavaScript 代码的执行顺序。过程如下:

  • 执行一个宏任务,这通常是 script(全局任务),在这个过程中如果遇到微任务,就将它添加到微任务的任务队列中。
  • 当前宏任务执行完成后,会查看微任务队列,并将其中的所有微任务一次执行完毕。
  • 当所有微任务完成后,有可能需要进行 UI 渲染。
  • 然后开始下一个宏任务,执行相应的任务队列。

8. JS MutationObserver

JavaScript 中的 MutationObserver API 用于监视 DOM(文档对象模型)的变化。它设计的目的是观察 DOM 树结构的变化。当 DOM 发生诸如添加或移除元素,或者修改属性等变化时,它会被调用。

这意味着,通过使用 MutationObserver,开发者可以定义当特定部分的 web 页面(即 DOM 树的某个部分)发生改变时需要执行的特定动作或操作。这对于处理动态内容、编写用户脚本,或者与 DOM 元素交互的情况非常有用。

以下是一个简单的 MutationObserver 的使用示例:

javascript
// 选择需要观察变动的节点
const targetNode = document.getElementById("some-id");
// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };
// 当观察到变动时执行的回调函数
const callback = function (mutationsList, observer) {
  for (let mutation of mutationsList) {
    if (mutation.type === "childList") {
      console.log("A child node has been added or removed.");
    } else if (mutation.type === "attributes") {
      console.log("The " + mutation.attributeName + " attribute was modified.");
    }
  }
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上面的配置开始观察目标节点
observer.observe(targetNode, config);
// 之后,可停止观察
// observer.disconnect();

在这个例子中,我们监视 id 为"some-id"的元素。我们关注其属性、子节点列表以及子树的变动。当我们观察到这些变化时,我们在回调函数中进行了处理。