likes
comments
collection
share

新年 10 个面试题,我曾 10 次拷问我的灵魂

作者站长头像
站长
· 阅读数 2

大家好,这里是大家的林语冰。坚持阅读,自律打卡,每天一次,进步一点

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 10 Interview Questions Every JavaScript Developer Should Know in 2024

本期共享的是 —— 新年里前端面试需要掌握的十大面试题,知识面虽小,但思路清晰。

JS 的世界日新月异,多年来面试趋势也与时俱进。本文科普了新年每个 JS 开发者必知必会十大基本问题,涵盖了从闭包到 TDD(测试驱动开发)的一系列主题,为大家提供应对现代 JS 挑战的知识和信心。

1. 闭包到底是什么鬼物?

闭包让我们有权从内部函数访问外部函数的作用域。当函数嵌套时,内部函数可以访问外部函数作用域中声明的变量,即使外部函数返回后也是如此:

const createCat = cat => {
  return {
    getCat: () => cat,
    setCat: newCat => {
      cat = newCat
    }
  }
}

const myCat = createCat('薛定谔')
console.log(myCat.getCat()) // 薛定谔

myCat.setCat('龙猫')
console.log(myCat.getCat()) // 龙猫

闭包变量是对外部作用域变量的实时引用,而不是拷贝。这意味着,如果变更外部作用域变量,那么变更会反映在闭包变量中,反之亦然,这意味着,在同一外部函数中声明的其他函数将可以访问这些变更。

闭包的常见用例包括但不限于:

  • 数据隐藏
  • 柯里化和偏函数(经常用于改进函数组合,比如形参化 Express 中间件或 React 高阶组件)
  • 与事件处理程序和回调共享数据

数据隐藏

封装是面向对象编程的一个重要特征。封装允许我们向外界隐藏类的实现细节。JS 中的闭包允许我们声明对象的私有变量:

// 数据隐藏
const createGirlFans = () => {
  let fans = 0
  return {
    increment: () => ++fans,
    decrement: () => --fans,
    getFans: () => fans
  }
}

柯里化函数和偏函数:

// 一个柯里化函数一次接受多个参数。
const add = a => b => a + b

// 偏函数是已经应用了某些参数的函数,
// 但没有完全应用所有参数。
const increment = add(1) // 偏函数

increment(2) // 3

2. 纯函数是什么鬼物?

纯函数在函数式编程中兹事体大。纯函数是可预测的,这使得它们比非纯函数更易理解、调试和测试。纯函数遵循两个规则:

  1. 确定性 —— 给定相同的输入,纯函数会始终返回相同的输出。
  2. 无副作用 —— 副作用是在被调用函数外部可观察到的、不是其返回值的任何 App 状态更改。

非确定性函数依赖于以下各项的函数,包括但不限于:

  • 随机数生成器
  • 可以改变状态的全局变量
  • 可以改变状态的参数
  • 当前系统时间

副作用包括但不限于:

  • 修改任何外部变量或对象属性(比如全局变量或父函数作用域链中的变量)
  • 打印到控制台
  • 写入屏幕、文件或网络
  • 报错。相反,该函数应该返回表明错误的结果
  • 触发任何外部进程

在 Redux 中,所有 reducer 都必须是纯函数。如果不是,App 的状态不可预测,且时间旅行调试等功能无法奏效。reducer 函数中的杂质还可能导致难以追踪的错误,包括过时的 React 组件状态。

3. 函数组合是什么鬼物?

函数组合是组合两个或多个函数,产生新函数或执行某些计算的过程:(f ∘ g)(x) = f(g(x))

const compose = (f, g) => x => f(g(x))

const g = num => num + 1
const f = num => num * 2

const h = compose(f, g)

h(20) // 42

React 开发者可通过函数组合来清理大型组件树。我们可以将它们组合,创建一个新的高阶组件,而不是嵌套组件,该组件可以通过附加功能强化传递给它的任何组件。

4. 函数式编程是什么鬼物?

函数式编程是一种使用纯函数作为主要组合单元的编程范式。组合在软件开发中兹事体大,几乎所有编程范式都是根据它们使用的组合单元来命名的:

  • 面向对象编程使用对象作为组合单元
  • 过程式编程使用过程作为组合单元
  • 函数式编程使用函数作为组合单元

函数式编程是一种声明式编程范式,这意味着,程序是根据它们做什么,而不是如何做来编写的。这使得函数式程序比命令式程序更容理解、调试和测试。它们往往更加简洁,这降低了代码复杂性,并使其更易维护。

函数式编程的其他关键方面包括但不限于:

  • 不变性 —— 不可变数据结构比可变数据结构更易推理
  • 高阶函数 —— 将其他函数作为参数或返回函数作为结果的函数
  • 避免共享可变状态 —— 共享可变状态使程序难以理解、调试和测试。这也使得推断程序的正确性更加头大

5. Promise 是什么鬼物?

JS 中的 Promise 是一个表示异步操作最终完成或失败的对象,它充当最初未知值的占位符,通常是因为该值的计算尚未完成。

Promise 的主要特征包括但不限于:

  • 有状态Promise 处于以下三种状态之一:
    • 待定:初始状态,既未成功也未失败
    • 已完成:操作成功完成
    • 拒绝:操作失败
  • 不可变:一旦 Promise 被完成或拒绝,其状态就无法改变。它变得不可变,永久保留其结果。这使得 Promise 在异步流控制中变得可靠。
  • 链接Promise 可以链接起来,这意味着,一个 Promise 的输出可以用作另一个 Promise 的输入。这通过使用 .then() 表示成功或使用 .catch() 处理失败来链接,从而允许优雅且可读的顺序异步操作。链接是函数组合的异步等价物。
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功!')
    // 我们也可以在失败时 reject 新错误。
  }, 1000)
})

promise
  .then(value => {
    console.log(value) // 成功
  })
  .catch(error => {
    console.log(error)
  })

6. TS 是什么鬼物?

TS 是 JS 的超集,由微软开发和维护。近年来,TS 的人气与日俱增,如果您是一名 JS 工程师,您最终很可能需要使用 TS。它为 JS 添加了静态类型,JS 是一种动态类型语言。静态类型可以辅助开发者在开发过程的早期发现错误,提高代码质量和可维护性。

TS 的主要特点包括但不限于:

  • 静态类型:定义变量和函数参数的类型,确保整个代码一致性。
  • 给力的 IDE 支持:IDE(集成开发环境)可以提供更好的自动补全、导航和重构,使开发过程更加高效。
  • 编译:TS 代码被转译为 JS,使其与任何浏览器或 JS 环境兼容。在此过程中,类型错误会被捕获,使代码更鲁棒。
  • 接口:接口允许我们指定对象和函数必须满足的抽象契约。
  • 与 JS 的兼容性:Ts 与现有 JS 代码高度兼容。JS 代码可以逐步迁移到 JS,使现有项目能够顺利过渡。
interface User {
  id: number
  name: string
}

type GetUser = (userId: number) => User

const getUser: GetUser = userId => {
  // 从数据库或 API 请求用户数据
  return {
    id: userId,
    name: '人猫神话'
  }
}

防范 bug 的最佳方案是代码审查、TDD 和 lint 工具(比如 ESLint)。TS 并不能替代这些做法,因为类型正确性并无法保证程序的正确性。即使应用了所有其他质量措施后,TS 偶尔也会发现错误。但它的主要好处是通过 IDE 支持,提供改进的开发体验。

7. Web Components 是什么鬼物?

WC(Web 组件)是一组 Web 平台 API,允许我们创建新的自定义、可重用、封装的 HTML 标签,在网页和 Web App 中使用。WC 是使用 HTML、CSS 和 JS 等开放 Web 技术构建的。它们是浏览器的一部分,不需要外部库或框架。

WC 对于拥有一大坨可能使用不同框架的工程师的大型团队特别有用。WC 允许我们创建可在任何框架或根本没有框架中使用的可重用组件。举个栗子,Adobe(PS 那个公司)的某个设计系统是使用 WC 构建的,并与 React 等流行框架顺利集成。

WC 由来已久,但最近人气爆棚,尤其是在大型组织中。它们被所有主要浏览器支持,并且是 W3C 标准。

8. React Hook 是什么鬼物?

Hook 是让我们无需编写类即可使用状态和其他 React 功能的函数。Hook 允许我们通过调用函数而不是编写类方法,来使用状态、上下文、引用和组件生命周期事件。函数的额外灵活性使我们更好地组织代码,将相关功能分组到单个钩子调用中,并通过在单独的函数调用中实现不相关功能,分离不相关的功能。Hook 提供了一种给力且富有表现力的方式来在组件内编写逻辑。

重要的 React Hook 包括但不限于:

  • useState —— 允许我们向函数式组件添加状态。状态变量在重新渲染之间保留。
  • useEffect —— 允许我们在函数式组件中执行副作用。它将 componentDidMount/componentDidUpdate/componentWillUnmount 的功能组合到单个函数调用中,减少了代码,并创建了比类组件更好的代码组织。
  • useContext —— 允许我们使用函数式组件中的上下文。
  • useRef —— 允许我们创建在组件的生命周期内持续存在的可变引用。
  • 自定义 Hook —— 封装可重用逻辑。这使得在不同组件之间共享逻辑变得容易。

Hook 的规则:Hook 必须在 React 函数的顶层使用(不能在循环、条件或嵌套函数内),且能且只能在 React 函数式组件或自定义 Hook 中使用。

Hook 解决了类组件的若干常见痛点,比如需要在构造函数中绑定方法,以及需要将功能拆分为多个生命周期方法。它们还使得在组件之间共享逻辑以及重用有状态逻辑,而无需更改组件层次结构更容易。

9. 如何在 React 中创建点击计数器?

我们可以使用 useState 钩子在 React 中创建点击计数器,如下所示:

import React, { useState } from 'react'

const ClickCounter = () => {
  const [count, setCount] = useState(0) // 初始化为 0

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count => count + 1)}>Click me</button>
    </div>
  )
}

export default ClickCounter

粉丝请注意,当我们从现有状态派生新值时,将函数传递给 setCount 是最佳实践,确保我们始终使用最新状态。

10. TDD 是什么鬼物?

TDD(测试驱动开发)是一种软件开发方法,其中测试是在实际代码之前编写的。它围绕一个简短的重复开发周期,旨在确保代码满足指定的要求且没有错误。TDD 在提高代码质量、减少错误和提高开发者生产力方面,可以发挥至关重要的作用。

开发团队生产力最重要的衡量标准之一是部署频率。持续交付的主要障碍之一是对变化的恐惧。TDD 通过确保代码始终处于可部署状态,辅助减少这种恐惧。这使得部署新功能和错误修复更容易,提高了部署频率。

测试先行多了一大坨福利,包括但不限于:

  • 更好的代码覆盖率:测试先行更有可能覆盖所有极端情况。
  • 改进的 API 设计:测试迫使我们在编写代码之前考虑 API 设计,这有助于避免将实现细节泄漏到 API 中。
  • 更少的 bug:测试先行可以辅助在开发过程中尽早发现错误,这样更容易修复。
  • 更好的代码质量:测试先行迫使我们编写模块化、松耦合的代码,这样更容易维护和重用。

TDD 的关键步骤包括但不限于:

  1. 编写测试:此测试最初会失败,因为相应的功能尚不存在。
  2. 编写实现:足以通过测试。
  3. 自信重构:一旦测试通过,就可以自信重构代码。重构是在不改变其外部行为的情况下,重构现有代码的过程。其目的是清理代码、提高可读性并降低复杂性。测试到位后,如果我们犯错了,我们会立即因测试失败而收到警报。

重复:针对每个功能需求重复该循环,逐步构建软件,同时确保所有测试继续通过。

学习曲线:TDD 是一项需要相当长的时间才能培养的技能和纪律。经过大半年的 TDD 体验后,我们可能仍觉得 TDD 难如脱单,且妨碍了生产力。虽然但是,使用 TDD 两年后,我们可能会发现它已经成为第二天性,并且比以前更有效率。

耗时:为每个小功能编写测试一开始可能会感觉很耗时,但长远来看,这通常会带来回报,减少错误并简化维护。我常常告诫大家,“如果你认为自己没有时间进行 TDD,那么你真的没有时间跳过 TDD。”

本期话题是 —— 你遭遇灵魂拷问的回头率最高的面试题是哪一道?

欢迎在本文下方群聊自由言论,文明共享。谢谢大家的点赞,掰掰~

《前端 9 点半》每日更新,坚持阅读,自律打卡,每天一次,进步一点

新年 10 个面试题,我曾 10 次拷问我的灵魂