注册
web

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

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



免责声明


本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 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 点半》每日更新,坚持阅读,自律打卡,每天一次,进步一点


26-cat.gif


作者:人猫神话
来源:juejin.cn/post/7334653735359348777

0 个评论

要回复文章请先登录注册