前端面试题详解整理19|ESLint 的原理,反转链表,async/await 的理解,求一个数开方后的整数部分for...in 与 for...of 的区别

76 阅读12分钟

base北京 微软 前端实习面经

投递渠道: 牛客网友内推

时间线

  • 2024-02-21 一面
  • 2024-02-26 二面
  • 2024-02-27 三面

一面

  1. 自我介绍与实习经历
  2. 学业相关

1. 算法:反转链表

下面是使用 JavaScript 实现反转链表的代码:

class ListNode {
  constructor(val = 0, next = null) {
    this.val = val;
    this.next = next;
  }
}

function reverseList(head) {
  let prev = null;
  let current = head;

  while (current) {
    const nextNode = current.next;
    current.next = prev;
    prev = current;
    current = nextNode;
  }

  return prev;
}

这段代码定义了一个 ListNode 类来表示链表节点,然后实现了 reverseList 函数来反转链表。在 reverseList 函数中,我们使用了三个指针 prevcurrentnextNode,并使用循环来遍历链表,反转节点的指针关系。最后返回的 prev 就是反转后的链表的头节点。

使用示例:

// 创建一个链表:1 -> 2 -> 3 -> 4 -> 5
const node5 = new ListNode(5);
const node4 = new ListNode(4, node5);
const node3 = new ListNode(3, node4);
const node2 = new ListNode(2, node3);
const node1 = new ListNode(1, node2);

// 反转链表
const newHead = reverseList(node1);

// 输出反转后的链表
let current = newHead;
while (current) {
  console.log(current.val);
  current = current.next;
}

这段 JavaScript 代码与之前的 Python 代码逻辑相同,只是语法上有些差异。

3. 算法:求一个数开方后的整数部分

你可以使用 JavaScript 中的 Math.sqrt() 方法来求一个数的开方,并取其整数部分。然后使用 Math.floor() 方法将结果向下取整,得到整数部分。下面是一个示例:

function squareRootIntegerPart(num) {
  return Math.floor(Math.sqrt(num));
}

// 示例用法
const number = 16;
const result = squareRootIntegerPart(number);
console.log(result); // 输出:4

在这个示例中,我们定义了一个 squareRootIntegerPart 函数来计算一个数的开方后的整数部分。然后我们将 16 作为示例输入,并将结果打印出来,得到的结果是 4,即 16 的开方的整数部分是 4。

二面

  1. 实习工作内容, 如何完善前端开发规范的

1. for...in 与 for...of 的区别

for...infor...of 都是 JavaScript 中用来遍历数据结构(比如数组、对象、字符串等)的循环语句,但它们之间有一些重要的区别:

  1. for...in 循环

    • 用于遍历对象的可枚举属性,或者数组/类数组对象的索引。
    • 不仅遍历自身的可枚举属性,还会遍历继承的属性。
    • 示例:
      const obj = {a: 1, b: 2, c: 3};
      for (let key in obj) {
        console.log(key, obj[key]);
      }
      // 输出:
      // a 1
      // b 2
      // c 3
      
  2. for...of 循环

    • 用于遍历可迭代对象(iterable),例如数组、字符串、Map、Set 等。
    • 不会遍历对象的属性,只会遍历对象本身的元素。
    • 示例:
      const arr = [1, 2, 3];
      for (let item of arr) {
        console.log(item);
      }
      // 输出:
      // 1
      // 2
      // 3
      

综上所述,for...in 主要用于遍历对象的属性,而 for...of 主要用于遍历可迭代对象的元素。在实际应用中,根据需求选择合适的循环语句来遍历数据结构。

3. async/await 的理解

async/await 是 JavaScript 中用于处理异步操作的一种语法糖,它基于 Promise,并且让异步代码看起来更像同步代码,使得异步操作更加容易理解和管理。

以下是对 async/await 的一些基本理解:

  1. async 函数

    • async 函数是一个特殊的函数,它在函数前面加上 async 关键字来定义。
    • async 函数内部可以使用 await 关键字来等待 Promise 对象的状态改变,即等待异步操作的结果。
  2. await 表达式

    • await 关键字只能在 async 函数内部使用。
    • await 右侧通常是一个返回 Promise 对象的表达式,可以是一个异步函数调用、Promise 对象或者任何符合 Promise 标准的对象。
    • await 表达式会暂停 async 函数的执行,直到 await 右侧的 Promise 对象状态变为 resolved(成功)或者 rejected(失败)。
  3. 错误处理

    • async/await 中,可以使用 try...catch 语句来捕获异步操作中的错误。
    • 如果 await 右侧的 Promise 对象状态变为 rejected,那么 await 表达式会抛出一个异常,可以通过 try...catch 捕获该异常并进行处理。
  4. 返回值

    • async 函数总是返回一个 Promise 对象,无论函数内部有没有异步操作。
    • 如果 async 函数显式地使用 return 返回一个值,那么该值会被 Promise 对象 resolve。
  5. 与 Promise 的区别

    • 相较于原始的 Promise 链式调用,async/await 更加简洁、易读,尤其是对于多个异步操作的连续执行。
    • async/await 更贴近同步编程的写法,减少了回调地狱(callback hell)的问题。

下面是一个简单的示例,演示了如何使用 async/await 处理异步操作:

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result = await resolveAfter2Seconds();
  console.log(result); // 等待2秒后输出:resolved
}

asyncCall();

在这个示例中,asyncCall 函数中使用了 await 关键字等待 resolveAfter2Seconds 函数的 Promise 对象结果。当 resolveAfter2Seconds 返回的 Promise 对象状态变为 resolved 时,await 表达式会返回其结果,并继续执行后续代码。

  1. 是否了解 AST ? 是否了解 ESLint 的原理

我了解抽象语法树(Abstract Syntax Tree,AST)和 ESLint 的原理。

  1. 抽象语法树(AST)

    • 抽象语法树是源代码的抽象语法结构的树状表现形式。它由节点构成,每个节点表示源代码中的一种结构,比如语句、表达式、变量声明等。
    • AST 可以被用来分析、转换和理解代码。在编译器、解析器、静态分析工具等领域都有广泛的应用。
  2. ESLint 的原理

    • ESLint 是一个用于静态代码分析的工具,它可以帮助开发者发现和修复代码中的问题,规范代码风格,并且提供了可扩展的插件系统。
    • ESLint 的工作原理主要是基于 AST。它将源代码解析成 AST,然后使用预定义的规则对 AST 进行遍历和检查,发现代码中的问题并给出相应的警告或错误信息。
    • ESLint 的规则可以自定义,也可以使用社区提供的现成规则集。用户可以根据项目的需求选择合适的规则,也可以根据团队的编码规范制定自定义规则。
    • ESLint 可以集成到代码编辑器、构建工具等开发环境中,实现实时检查和反馈,帮助开发者在开发过程中保持代码质量和一致性。

总的来说,AST 是 ESLint 实现静态代码分析的基础,而 ESLint 则是在 AST 的基础上实现了具体的代码规范检查功能。

看代码说结果:

  1. 具名导入与默认导出
// utils.js
const sum = (a, b) => a + b;
export default sum;
export function minus(a, b) {
  return a - b;
}

// index.js
import * as sum from "./utils";
// 如何调用相关函数
// sum.default(1, 2)
// sum.minus(1, 2)

在这段代码中,utils.js 文件中定义了一个默认导出 sum 和一个具名导出 minus。在 index.js 文件中,使用 import * as sum 导入了 utils.js 文件中的所有导出,并将它们作为 sum 对象的属性。因此,你可以按照以下方式调用相关函数:

// 调用默认导出的函数
sum.default(1, 2);

// 调用具名导出的函数
sum.minus(1, 2);

在调用默认导出的函数时,需要使用 sum.default() 语法。而调用具名导出的函数时,则直接使用 sum.minus() 语法。

  1. var / let / const 的变量提升

在 JavaScript 中,varletconst 三种声明变量的方式在变量提升方面存在一些差异。

  1. var 变量提升

    • 使用 var 声明的变量会存在变量提升,即变量声明会被提升到所在作用域的顶部,但初始化的赋值不会提升。
    • 这意味着,在变量声明之前访问变量会得到 undefined,而不会抛出错误。
    • 示例:
      console.log(foo); // 输出:undefined
      var foo = 10;
      
  2. letconst 变量提升

    • 使用 letconst 声明的变量也会存在变量提升,但是在访问变量之前会处于 "暂时性死区"(Temporal Dead Zone,TDZ)。
    • TDZ 是指在当前作用域内,变量虽然已经被声明,但由于尚未初始化赋值,因此无法访问该变量。
    • 在 TDZ 内访问变量会导致 ReferenceError 错误。
    • 示例:
      console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
      let foo = 10;
      

总的来说,var 在变量提升时会将变量声明提升到作用域顶部,而 letconst 在变量提升时会进入 TDZ,无法访问,直到变量被赋值初始化之后才能正常访问。

let 与 const 都存在变量提升, 提升到块级作用域顶部, 但同时也存在暂时性死区的概念

  1. 给函数的属性赋值是否会报错
function fn() {}
fn.name = "fn";

不会报错。在 JavaScript 中,函数是一种特殊的对象,因此可以像普通对象一样给函数添加属性。在你的示例中,给函数 fn 添加了一个 name 属性,赋值为字符串 "fn"。这种做法是合法的,并且不会报错。

这样的用法在一些场景下很有用,比如给函数添加一些自定义的元数据,或者在调试时标识函数。

算法题:给定一个数字, 你可以将该数字中的任意一种数字替换为另一种数字, 例如你可以将该数中的所有 0 替换为 1. 你只能将一种数字替换为另一种数字, 请问通过该替换操作能得到的最大值与最小值之差为多少?

要找到通过替换操作能得到的最大值与最小值之差,可以通过以下步骤实现:

  1. 将数字转换为字符串,以便于逐位处理。
  2. 找到当前数字中的最大位数和最小位数。
  3. 对于最大值,将最小位数替换为最大值,对于最小值,将最大位数替换为最小值。
  4. 计算替换后的数字的差值。

下面是一个示例 JavaScript 代码:

function findDifference(number, replaceFrom, replaceTo) {
    // 将数字转换为字符串
    let numberStr = number.toString();

    // 初始化最大值和最小值
    let maxNumber = Number(numberStr.replace(new RegExp(replaceFrom, 'g'), replaceTo));
    let minNumber = Number(numberStr.replace(new RegExp(replaceTo, 'g'), replaceFrom));

    // 计算差值
    let difference = maxNumber - minNumber;
    return difference;
}

// 示例用法
const number = 328515;
const replaceFrom = '3';
const replaceTo = '9';
const difference = findDifference(number, replaceFrom, replaceTo);
console.log("最大值与最小值之差为:" + difference);

在这个示例中,我们将数字 328515 中的所有 3 替换为 9,然后计算替换后的最大值和最小值之差。

三面

  1. 自我介绍与实习经历
  2. 是否了解过性能优化, 有哪些方式
  3. 原生实现 tooltip 要实现一个原生的 tooltip,你可以使用 HTML、CSS 和 JavaScript 来创建一个简单的提示框。下面是一个基本的示例:

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tooltip Example</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>

<div class="tooltip">
  Hover over me
  <span class="tooltiptext">Tooltip text</span>
</div>

<script src="script.js"></script>
</body>
</html>

CSS(styles.css):

.tooltip {
  position: relative;
  display: inline-block;
  border-bottom: 1px dotted black; /* 下划线样式 */
}

.tooltip .tooltiptext {
  visibility: hidden;
  width: 120px;
  background-color: black;
  color: white;
  text-align: center;
  border-radius: 6px;
  padding: 5px 0;

  /* 位置 */
  position: absolute;
  z-index: 1;
  bottom: 100%;
  left: 50%;
  margin-left: -60px; /* 让提示框居中 */
}

.tooltip:hover .tooltiptext {
  visibility: visible;
}

JavaScript(script.js):

// 如果需要动态更改提示框文本,可以使用以下代码
// const tooltipText = document.querySelector('.tooltiptext');
// tooltipText.textContent = 'New tooltip text';

这个示例中,当鼠标悬停在 "Hover over me" 文本上时,会显示一个提示框,并在提示框中显示 "Tooltip text" 文本。你可以根据需要自定义提示框的样式和行为。

3. CSS 如何绘制一个三角形

你可以使用 CSS 的 border 属性来绘制一个简单的三角形。下面是一种常见的方法:

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Triangle Example</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>

<div class="triangle"></div>

</body>
</html>

CSS(styles.css):

.triangle {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid red; /* 修改颜色和大小 */
}

在这个示例中,我们创建了一个 .triangle 类,使用 border 属性设置了三个边框。通过调整 border-widthborder-colorborder-style 可以控制三角形的大小、颜色和形状。

5. 算法:手写堆排序

堆排序(Heap Sort)是一种高效的排序算法,它利用了堆的数据结构来实现排序过程。堆是一种特殊的二叉树,它满足堆的性质:父节点的值总是大于等于(最大堆)或小于等于(最小堆)其子节点的值。

堆排序的基本思想是先将待排序的序列构建成一个堆,然后不断地将堆顶元素(最大元素或最小元素)与堆的最后一个元素交换,并调整堆,使得剩余元素仍然满足堆的性质,最终完成排序。

下面是堆排序的 JavaScript 实现:

function heapSort(arr) {
    // 构建最大堆
    buildMaxHeap(arr);

    // 不断取出堆顶元素(最大元素),并调整堆
    for (let i = arr.length - 1; i > 0; i--) {
        // 将堆顶元素(最大元素)与堆的最后一个元素交换
        [arr[0], arr[i]] = [arr[i], arr[0]];
        // 调整堆,使得剩余元素仍然满足堆的性质
        heapify(arr, 0, i);
    }
}

// 构建最大堆
function buildMaxHeap(arr) {
    // 从最后一个非叶子节点开始,依次向前调整堆
    for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
        heapify(arr, i, arr.length);
    }
}

// 调整堆
function heapify(arr, index, size) {
    let largest = index; // 当前节点、左子节点和右子节点中的最大值索引
    const left = 2 * index + 1; // 左子节点索引
    const right = 2 * index + 2; // 右子节点索引

    // 找到当前节点、左子节点和右子节点中的最大值索引
    if (left < size && arr[left] > arr[largest]) {
        largest = left;
    }
    if (right < size && arr[right] > arr[largest]) {
        largest = right;
    }

    // 如果最大值不是当前节点,则交换它们,并递归调整堆
    if (largest !== index) {
        [arr[index], arr[largest]] = [arr[largest], arr[index]];
        heapify(arr, largest, size);
    }
}

// 示例用法
const arr = [4, 10, 3, 5, 1];
heapSort(arr);
console.log(arr); // 输出:[1, 3, 4, 5, 10]

在这个实现中,heapSort 函数用于执行堆排序,buildMaxHeap 函数用于构建最大堆,heapify 函数用于调整堆。通过这三个函数协作,可以实现对数组的原地排序。

7. 算法:lc 168. Excel 表列名称

好的,下面是使用 JavaScript 实现的解答:

function convertToTitle(n) {
    let result = '';
    while (n > 0) {
        n--;
        result = String.fromCharCode(n % 26 + 65) + result;
        n = Math.floor(n / 26);
    }
    return result;
}

// 测试代码
console.log(convertToTitle(1));   // 输出:'A'
console.log(convertToTitle(28));  // 输出:'AB'
console.log(convertToTitle(701)); // 输出:'ZY'

这段代码的思路与 Python 实现相同。在 JavaScript 中,我们使用 String.fromCharCode 方法将 ASCII 码转换为字符,并且 ASCII 码中 A 对应的是 65,而不是 97(即 'A' 的 ASCII 码是 65)。

作者:zbwer
链接:www.nowcoder.com/discuss/595…
来源:牛客网