前端面试题
作为程序员,我们的日子过得真不容易。尤其是在面试环节。为了找到一份新工作,我们不得不面对重重障碍,这可不是什么愉快的经历。
我敢打赌,所有读这篇文章的人都是优秀的程序员,但我们很多人在面临压力时都会遇到一个问题……在压力下表现不是我们的强项。这就是为什么我们没能成为奥运游泳运动员,也没能成为深海潜水员……
抛开玩笑不谈,我认为记录下我们为了找工作所经历的种种考验很重要。正因如此,我写了这篇文章,讲述我曾经在一家公司收到的一份家庭作业。
任务
我收到了以下要求
- 创建一个按钮,点击后显示比特币的当前价格
- 单击按钮后,按钮文本从“获取比特币价格”更改为“刷新比特币价格”
- 单击按钮后,颜色从蓝色变为紫色
- 单击按钮后,会显示一个字段,显示“当前价格:”,连续单击后,会显示第二个字段,显示“先前价格:”
这就是任务要求的全部内容,作业几乎没有任何限制。你可以随意使用你喜欢的库。你可以使用外部资源来补充代码,并且你有1个小时的时间来完成。
方法
我开始思考如何构建代码,并牢记最佳实践,并寻求函数式编程解决方案。在函数式编程中,我们将大问题分解为模块化解决方案。这意味着我们构建的是可重用且纯粹的函数。
逻辑
因此我开始划分工作,并提出了以下逻辑方法和伪代码:
按下按钮
我将使用 useState 来跟踪按钮状态。
我们也可以将此值传递给按钮,以更改其颜色并设置其文本。
// Button press
const [isPressed, setPressed] = useState(false)
API 调用
我将为比特币调用创建一个模块化 API 调用,并使用 try catch 语句返回一个空数组,这样即使调用失败,页面仍会加载。
// API call
const getBitcoin = async() => {
try{
fetch(url)
} catch {
return []
}
}
设置量
考虑到这一点,我们需要一个数据结构来跟踪比特币价格。我认为最好的方法是使用一个队列,先输入一个价格,然后将其显示为当前价格;然后输入第二个价格,当前价格变为新价格,旧价格变为前一个价格,以此类推。
// setting amount
const [bitcoinPrices, setPrice] = useState([])
const setAmount = (data = {}) => {
const newPrices = [data, ...bitcoinPrices].slice(2)
setPrice(newPrices)
}
这些将指导我创建应用程序。
用户界面
现在我们要为这个应用程序构建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>
正如您所看到的,我输入了一个三元组,将按钮的文本从“获取比特币价格”更改为“刷新比特币价格”。
我正在映射 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
我在这里构建了应用程序的草稿。我觉得单个页面上的代码很多,但它执行了所有逻辑所需的功能,然而在用户界面上,按钮还没有改变颜色,所以我们不得不用一个库来解决这个问题。
我问面试官是否可以安装库。他说可以,但必须解释一下库的用例。
我决定实现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;
`;
通过这种方式,我将组件导入主文件,并实现了新样式的组件,如下所示:
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>
)
现在我对样式很满意,可以继续优化我的代码了
优化
在面试过程中,我开始思考我的代码是否遵循了函数式编程的最佳实践。我确实在 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)
}
这个函数不是纯函数……这意味着,相同的输入并不总是会得到相同的输出。它也不应该修改任何外部变量,也不应该包含任何子函数。我觉得可以通过组合来优化。
合成或管道
组合函数意味着我们有一个高阶函数,它可以对单个输入执行多个函数。这意味着我们给一个函数传入参数,然后该函数利用这些参数按顺序执行多个函数,从右到左读取。例如
const purchaseItem = compose(
subtractFunds,
emptyBasket,
moveToPurchase,
putInBasket)({name: "Laptop", price: 15})
管道的概念是一样的,只不过是反过来的。我们不是从右到左,而是从左到右。
const purchaseItem = pipe(
putInBasket,
moveToPurchase,
emptyBasket,
subtractFunds,
)({name: "Laptop", price: 15})
我找到了一个很棒的异步 compose 解决方案。这意味着我创建了一个 compose 函数,它接收 Promise 并将其转换为实际数据。
export const compose =
(...functions) =>
(input) =>
functions.reduceRight(
(chain, func) => chain.then(func),
Promise.resolve(input)
);
这几行代码创建了一个 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;
正如您所看到的,我模块化了一些代码,例如我们现在导入了 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);
正如你所看到的,我利用样式组件来创建按钮和列表组件
按钮组件
<Button
isPressed={hasBeenPressed}
onClick={() => {
if (!hasBeenPressed) setPressed(true);
bitcoinCall();
}}
>
{hasBeenPressed ? "refresh Bitcoin price" : "get Bitcoin price"}
</Button>
正如您所见,我将 hasBeenPressed 传递给 Button 组件,该组件用于样式组件中更改背景。我还使用它通过三元运算符来设置文本。
列表组件
<List>
{bitcoinPrice.map((value, i) => (
<div key={i}>
{i === 0 ? "Current price: " : "Previous price: "} {value}
</div>
))}
</List>
列表仅用于样式目的,确保 div 居中并位于列方向。
助手
我总是喜欢为我的辅助函数创建文件夹,这样结构更清晰,也更易于维护。这些是我创建的文件夹,我通常使用这种index.js
格式,因为方便访问。
/实用程序
export const compose =
(...functions) =>
(input) =>
functions.reduceRight(
(chain, func) => chain.then(func),
Promise.resolve(input)
);
/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 [];
}
};
命名导出也很容易再次导入,它还分离了不同的功能,而不是导出默认值并加载不同的文件。
谢谢
非常感谢您阅读本教程,我希望它能让您了解如何解决这样的问题。
我知道这是编写代码的一种主观方式,但我相信,如果你有一个遵循的基础,并且很好地解释你为什么做你所做的事情,那么你就是一个优秀的程序员。
如果您想连接,可以在这里进行:
文章来源:https://dev.to/lucvankerkvoort/front-end-interview-question-5392