前端面试题

2025-05-25

前端面试题

作为程序员,我们的日子过得真不容易。尤其是在面试环节。为了找到一份新工作,我们不得不面对重重障碍,这可不是什么愉快的经历。

我敢打赌,所有读这篇文章的人都是优秀的程序员,但我们很多人在面临压力时都会遇到一个问题……在压力下表现不是我们的强项。这就是为什么我们没能成为奥运游泳运动员,也没能成为深海潜水员……

抛开玩笑不谈,我认为记录下我们为了找工作所经历的种种考验很重要。正因如此,我写了这篇文章,讲述我曾经在一家公司收到的一份家庭作业。

任务

我收到了以下要求

  • 创建一个按钮,点击后显示比特币的当前价格
  • 单击按钮后,按钮文本从“获取比特币价格”更改为“刷新比特币价格”
  • 单击按钮后,颜色从蓝色变为紫色
  • 单击按钮后,会显示一个字段,显示“当前价格:”,连续单击后,会显示第二个字段,显示“先前价格:”

这就是任务要求的全部内容,作业几乎没有任何限制。你可以随意使用你喜欢的库。你可以使用外部资源来补充代码,并且你有1个小时的时间来完成。

方法

我开始思考如何构建代码,并牢记最佳实践,并寻求函数式编程解决方案。在函数式编程中,我们将大问题分解为模块化解决方案。这意味着我们构建的是可重用且纯粹的函数。

逻辑

因此我开始划分工作,并提出了以下逻辑方法和伪代码:

按下按钮

我将使用 useState 来跟踪按钮状态。
我们也可以将此值传递给按钮,以更改其颜色并设置其文本。

// Button press

const [isPressed, setPressed] = useState(false)
Enter fullscreen mode Exit fullscreen mode

API 调用

我将为比特币调用创建一个模块化 API 调用,并使用 try catch 语句返回一个空数组,这样即使调用失败,页面仍会加载。

// API call

const getBitcoin = async() => {
 try{
      fetch(url)
    } catch {
      return []
    }

}
Enter fullscreen mode Exit fullscreen mode

设置量

考虑到这一点,我们需要一个数据结构来跟踪比特币价格。我认为最好的方法是使用一个队列,先输入一个价格,然后将其显示为当前价格;然后输入第二个价格,当前价格变为新价格,旧价格变为前一个价格,以此类推。

// setting amount

const [bitcoinPrices, setPrice] = useState([])

const setAmount = (data = {}) => {
  const newPrices = [data, ...bitcoinPrices].slice(2)
  setPrice(newPrices)
}

Enter fullscreen mode Exit fullscreen mode

这些将指导我创建应用程序。

用户界面

现在我们要为这个应用程序构建UI。补充一下,UI看起来有点奇怪,因为UI没有任何限制,这纯粹是为了展示逻辑技巧。

  <div className="App">
      <button>
        {isPressed ? "refresh Bitcoin price" : "get Bitcoin price"}
      </button>
      <div>
        <div>
          {bitcoinPrice.map((value, i) => (
            <div key={i}>
              {i === 0 ? "Current price: " : "Previous price: "} {value}
            </div>
          ))}
        </div>
      </div>
    </div>

Enter fullscreen mode Exit fullscreen mode

正如您所看到的,我输入了一个三元组,将按钮的文本从“获取比特币价格”更改为“刷新比特币价格”。

我正在映射 bitcoinPrice(状态变量)来设置值。我使用三元组来决定值前面的文本。如果索引为 0,则将其设置为“当前价格”,否则将其设置为“先前价格”。

课外点数

在这个挑战中,我必须在面试官的注视下,在自己的机器上构建它。我使用 创建了一个新的 React 项目npx create-react-app。面试官问我为什么要使用create-react-app

这类问题是分享知识的好工具。知道这类问题的答案是额外的课外加分项,所以如果你不知道也没关系,但这里有一个可以接受的答案

create-react-app 是一个预设了整个 React 应用程序的包,它安装了babel编译器,用于将 JSX 代码解析为纯 ES5 JavaScript,它安装了webpack捆绑器并预设了它来捆绑应用程序,安装React并预设了虚拟 DOM,设置了测试React testing library,所以我们可以直接开始编码。

它的另一个好处是你可以边安装边聊天。希望你聊天结束的时候安装已经完成了。

处决

在面试官的密切关注下,我开始构建应用程序。首先是一个草稿版本来开始:


function App() {
const [isPressed, setPressed] = useState(false)
const [bitcoinPrice, setBitcoinPrice] = useState([])

const getBitcoin = async() => {
     try {
    const call = await fetch("https://api.coinbase.com/v2/prices/BTC-USD/buy", {
      method: "GET",
      authorization:
        "Bearer abd90df5f27a7b170cd775abf89d632b350b7c1c9d53e08b340cd9832ce52c2c",
    });
    const response = await call.json();

    return await response;
  } catch {
    return [];
  }
};

const setAmount = () => {
 const data = await getBitcoin()

  if(!data) return

console.log(data) 
// {"data":{"base":"BTC","currency":"USD","amount":"19891.09"}}

const {data: { amount }} = data

// New amount is pushed in front of the old amounts that have been deconstructed. 
// We then slice the array at 2 so we get rid of any old data we don't want to use anymore
const newPrices = [amount, ...bitcoinPrice].slice(2)

setBitcoinPrice(newPrices)

}



return(
  <div className="App">
      <button onClick={() => {
if(!isPressed) setPressed(true)
setAmount()
>
        {isPressed ? "refresh Bitcoin price" : "get Bitcoin price"}
      </button>
      <div>
        <div>
          {bitcoinPrice.map((value, i) => (
            <div key={i}>
              {i === 0 ? "Current price: " : "Previous price: "} {value}
            </div>
          ))}
        </div>
      </div>
    </div>
)

}

export default App

Enter fullscreen mode Exit fullscreen mode

我在这里构建了应用程序的草稿。我觉得单个页面上的代码很多,但它执行了所有逻辑所需的功能,然而在用户界面上,按钮还没有改变颜色,所以我们不得不用一个库来解决这个问题。

我问面试官是否可以安装库。他说可以,但必须解释一下库的用例。

我决定实现styled-components一个 CSS 库,允许我直接在 CSS 中发送和使用 JavaScript。如果你需要使用三元运算符设置元素的颜色,这将非常方便。

样式

我创建了以下 css 文件来设置样式,Button并实现了三元运算符,以便在按下按钮后将背景颜色更改为紫色,我还发现列表项是水平对齐的,所以我决定使用它来解决这个问题,正如您在组件flexbox中看到的那样List

import styled from "styled-components";

export const Button = styled.button`
  background-color: ${({ isPressed }) => (isPressed ? "blue" : "purple")};
`;

export const List = styled.div`
  display: flex;
  flex-direction: column;
`;
Enter fullscreen mode Exit fullscreen mode

通过这种方式,我将组件导入主文件,并实现了新样式的组件,如下所示:

return(
  <div className="App">
      <Button isPressed={isPressed} onClick={() => {
if(!isPressed) setPressed(true)
setAmount()
>
        {isPressed ? "refresh Bitcoin price" : "get Bitcoin price"}
      </Button>
      <div>
        <List>
          {bitcoinPrice.map((value, i) => (
            <div key={i}>
              {i === 0 ? "Current price: " : "Previous price: "} {value}
            </div>
          ))}
        </List>
      </div>
    </div>
)
Enter fullscreen mode Exit fullscreen mode

现在我对样式很满意,可以继续优化我的代码了

优化

在面试过程中,我开始思考我的代码是否遵循了函数式编程的最佳实践。我确实在 setAmount 函数中发现了一个缺陷。

const setAmount = () => {
 const data = await getBitcoin()

  if(!data) return

console.log(data) 
// {"data":{"base":"BTC","currency":"USD","amount":"19891.09"}}

const {data: { amount }} = data

// New amount is pushed in front of the old amounts that have been deconstructed. 
// We then slice the array at 2 so we get rid of any old data we don't want to use anymore
const newPrices = [amount, ...bitcoinPrice].slice(2)

setBitcoinPrice(newPrices)

}
Enter fullscreen mode Exit fullscreen mode

这个函数不是纯函数……这意味着,相同的输入并不总是会得到相同的输出。它也不应该修改任何外部变量,也不应该包含任何子函数。我觉得可以通过组合来优化。

合成或管道

组合函数意味着我们有一个高阶函数,它可以对单个输入执行多个函数。这意味着我们给一个函数传入参数,然后该函数利用这些参数按顺序执行多个函数,从右到左读取。例如

const purchaseItem = compose( 
  subtractFunds,
  emptyBasket, 
  moveToPurchase,  
  putInBasket)({name: "Laptop", price: 15})

Enter fullscreen mode Exit fullscreen mode

管道的概念是一样的,只不过是反过来的。我们不是从右到左,而是从左到右。

const purchaseItem = pipe( 
   putInBasket,
  moveToPurchase,  
  emptyBasket, 
subtractFunds,
 )({name: "Laptop", price: 15})

Enter fullscreen mode Exit fullscreen mode

我找到了一个很棒的异步 compose 解决方案。这意味着我创建了一个 compose 函数,它接收 Promise 并将其转换为实际数据。

export const compose =
  (...functions) =>
  (input) =>
    functions.reduceRight(
      (chain, func) => chain.then(func),
      Promise.resolve(input)
    );

Enter fullscreen mode Exit fullscreen mode

这几行代码创建了一个 HOF(高阶函数)来解析 Promise

第一个函数
我们在链的第一个函数中赋予它 x 个函数数量,我们对其使用扩展运算符。所以我们可以拥有任意数量的函数。

第二个函数
我们在第二个函数中接受输入,因此我们可以将输入传递到函数中,如上面的示例所示。

第三个函数
是实际函数返回的内容,它是一个接受链和函数的减速器。链用于为函数创建解析,并通过这些链解析给定的输入。

我知道...做完这个后休息一下。

解决方案

解决方案可以归结为将几个核心概念应用于核心问题,如上所述,我们有以下要求:

  • 创建一个按钮,点击后显示比特币的当前价格
  • 单击按钮后,按钮文本从“获取比特币价格”更改为“刷新比特币价格”
  • 单击按钮后,颜色从蓝色变为紫色
  • 单击按钮后,会显示一个字段,显示“当前价格:”,连续单击后,会显示第二个字段,显示“先前价格:”

以下范例用于解决该问题:

  • 模块化
  • 函数式编程
  • 构成

这是针对该解决方案实现的架构的快照

建筑学

以下文件突出显示:

  • App.js - 存储应用程序
  • style.js - 这是我们的styled-components
  • /utils - 包含实用功能的文件夹,在本例中为 compose 功能
  • /API - 包含 API 调用的文件夹,在本例中为比特币 API

我选择这种文件结构是因为它提供了扩展空间。实用函数确实很常用,将它们存储在一个易于访问的文件夹中,对开发应用程序的团队非常有利。设置 webpack 从该/src文件夹导入会更好,因为这些文件夹可以在任何地方访问,而不必写相对路径。

让我们仔细看看 App.js

import React, { useState } from "react";
import { getBitcoin } from "./API";
import { compose } from "./utils";
import { Button, List } from "./styles";
import "./App.css";

function App() {
  const [hasBeenPressed, setPressed] = useState(false);
  const [bitcoinPrice, setBitcoinPrice] = useState([]);

  const storeAmount = ({ data }) => {
    if (!data) return;

    const { amount } = data;

    const bitcoinPrices = [amount, ...bitcoinPrice].slice(2);

    setBitcoinPrice(bitcoinPrices);
  };

  const bitcoinCall = compose(storeAmount, getBitcoin);

  return (
    <div className="App">
      <Button
        isPressed={hasBeenPressed}
        onClick={() => {
          if (!hasBeenPressed) setPressed(true);
          bitcoinCall();
        }}
      >
        {hasBeenPressed ? "refresh Bitcoin price" : "get Bitcoin price"}
      </Button>
      <div>
        <List>
          {bitcoinPrice.map((value, i) => (
            <div key={i}>
              {i === 0 ? "Current price: " : "Previous price: "} {value}
            </div>
          ))}
        </List>
      </div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

正如您所看到的,我模块化了一些代码,例如我们现在导入了 compose 函数以及比特币的 API 调用,因为比特币 API 调用返回一个承诺,我们使用特殊的 compose 函数来解决该承诺并将数据传递给该setAmount函数。

  const [bitcoinPrice, setBitcoinPrice] = useState([]);

  const storeAmount = ({ data }) => {
    if (!data) return;

    const { amount } = data;

    const bitcoinPrices = [amount, ...bitcoinPrice].slice(2);

    setBitcoinPrice(bitcoinPrices);
  };

  const bitcoinCall = compose(storeAmount, getBitcoin);
Enter fullscreen mode Exit fullscreen mode

正如你所看到的,我利用样式组件来创建按钮和列表组件

按钮组件

<Button
        isPressed={hasBeenPressed}
        onClick={() => {
          if (!hasBeenPressed) setPressed(true);
          bitcoinCall();
        }}
      >
        {hasBeenPressed ? "refresh Bitcoin price" : "get Bitcoin price"}
      </Button>

Enter fullscreen mode Exit fullscreen mode

正如您所见,我将 hasBeenPressed 传递给 Button 组件,该组件用于样式组件中更改背景。我还使用它通过三元运算符来设置文本。

列表组件


 <List>
          {bitcoinPrice.map((value, i) => (
            <div key={i}>
              {i === 0 ? "Current price: " : "Previous price: "} {value}
            </div>
          ))}
        </List>
Enter fullscreen mode Exit fullscreen mode

列表仅用于样式目的,确保 div 居中并位于列方向。

助手

我总是喜欢为我的辅助函数创建文件夹,这样结构更清晰,也更易于维护。这些是我创建的文件夹,我通常使用这种index.js格式,因为方便访问。

/实用程序

export const compose =
  (...functions) =>
  (input) =>
    functions.reduceRight(
      (chain, func) => chain.then(func),
      Promise.resolve(input)
    );

Enter fullscreen mode Exit fullscreen mode
/API

export const getBitcoin = async () => {
  try {
    const call = await fetch("https://api.coinbase.com/v2/prices/BTC-USD/buy", {
      method: "GET",
      authorization:
        "Bearer abd90df5f27a7b170cd775abf89d632b350b7c1c9d53e08b340cd9832ce52c2c",
    });
    const response = await call.json();

    return await response;
  } catch {
    return [];
  }
};

Enter fullscreen mode Exit fullscreen mode

命名导出也很容易再次导入,它还分离了不同的功能,而不是导出默认值并加载不同的文件。

谢谢

非常感谢您阅读本教程,我希望它能让您了解如何解决这样的问题。

我知道这是编写代码的一种主观方式,但我相信,如果你有一个遵循的基础,并且很好地解释你为什么做你所做的事情,那么你就是一个优秀的程序员。

如果您想连接,可以在这里进行:

Github
领英

文章来源:https://dev.to/lucvankerkvoort/front-end-interview-question-5392
PREV
⚔️ 跨微前端通信 📦
NEXT
在网络上学习 Web 开发 | 资源路线图