如何准备 React 面试——前端技术面试指南
前端技术面试往往是潜在雇主用来评估你在 Web 开发方面技术的机会。
面试官通常会提问你在 HTML、CSS 和 JavaScript 方面的经验和技术,他们也会针对如:React、Angular、Vue 等框架进行提问。
也会有就特定领域给你出写代码的测试。
我们一起来看看前端技术面试在 React 和 JavaScript 方面最常被问到的问题。
面试官考察什么
当参与前端 Web 开发职位的面试,请准备好你会被问到各种编程语言、工具和框架方面的技巧和经验。
面试官希望你对 Web 开发最新的趋势和技术有比较深刻的认识。
请准备好描述你过去的项目,以及你是如何解决各种挑战的。
确保在讲解开发过程的时候展示你的解决问题的技能。
最后,不要忘了强调你的优势。
前端技术面试最常被问到的问题
前端技术面试问题一般很直截了当。如果你过去半年一直在写代码,你应该很熟悉大部分将被问到的概念。
一旦你利用一段时间练习对的问题,你就对面试有一个比较清晰的认识。
让我们一起来看看常被问到的问题。
Map、ForEach、Filter和Reduce
有关数组方法
是最常被问到的问题(通常是面试一开始)。面试官希望了解你在操作数组方面的熟悉程度。
.map()
方法
.map()
方法迭代整个数组,并通过计算你在函数内编写的逻辑,返回一个新数组。
let arr = [
{ id: 1, age: 12, name: 'Manu' },
{ id: 2, age: 24, name: 'Quincy' },
{ id: 3, age: 22, name: 'Abbey' },
]
let names = arr.map((el) => el.name)
console.log(names)
// 输出: [ 'Manu', 'Quincy', 'Abbey' ]
.forEach()
方法
.forEach()
和.map()
类似,但是不返回新数组。
let arr = [
{ id: 1, age: 12, name: 'Manu' },
{ id: 2, age: 24, name: 'Quincy' },
{ id: 3, age: 22, name: 'Abbey' },
]
arr.forEach((el) => el.age+= 10);
console.log(arr);
// 输出: 22 32 44
.filter()
方法
.filter()
方法正如其名,是基于布尔条件过滤出数组中的值。
如果布尔条件为真,结果会被返回,并且添加到最终的数组;如果布尔条件为假,则会被跳过。.filter()
和.map()
方法一样返回新数组。
let arr = [
{ id: 1, age: 12, name: 'Manu' },
{ id: 2, age: 24, name: 'Quincy' },
{ id: 3, age: 22, name: 'Abbey' },
]
let tooYoung = arr.filter((el) => el.age <= 14);
console.log(tooYoung);
// 输出: [ { id: 1, age: 12, name: 'Manu' } ]
.reduce()
方法
简单来说,.reduce()
方法需要考虑previous value(先前值)
、当前值(currentValue)和 accumulator(累加器)
.
.reduce()
方法的返回类型始终是一个值。当你想要处理数组所有值并且最终累积一个结果的时候,reduce可以派上用场。
// 计算三个人的年龄总和
let arr = [
{ id: 1, age: 12, name: 'Manu' },
{ id: 2, age: 24, name: 'Quincy' },
{ id: 3, age: 22, name: 'Abbey' },
]
let totalAge = arr.reduce((acc, currentObj) => acc + currentObj.age, 0)
console.log(totalAge)
// 输出: 57
在上述代码中,currentObj
是被迭代的对象。同时,acc
存储结果,并且输出totalAge数组最终的结果。
如何实现Polyfills
如何通过polyfills 来实现map和filter数组,也是一个重要的面试问题。
polyfill是一个代码块 (在JavaScript的web架构中)。通常用于在旧的浏览器中原生地实现现代的功能。
简单来说,polyfill就是使用自定义的原生JavaScript函数来实现功能。比方说创建你自己的 .map()
或者 .filter()
方法。
如何实现.map()
的polyfill
let data = [1, 2, 3, 4, 5];
Array.prototype.myMap = function (cb) {
let arr = [];
for (let i = 0; i < this.length; i++) {
arr.push(cb(this[i], i, this));
}
return arr;
};
const mapLog = data.myMap((el) => el * 2);
console.log(mapLog);
myMap
方法接受一个在mymap
函数体内执行的回调(cb)
。在函数体内使用原生的for
循环,并在 this.length
范围内迭代,this.length
是被myMap
调用的数组的长度。
由于 map()
的语法是arr.map(currentElement, index, array)
,所以 myMap()
函数也要考虑这些参数。
同时,因为map()
返回一个新数组,所以我们创建一个空数组,并且把结果推入,并在最后返回这个数组。
如何实现.filter()
polyfill
let data = [1, 2, 3, 4, 5];
Array.prototype.myFilter = function (cb) {
let arr = [];
for (let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) {
arr.push(this[i]);
}
}
return arr;
};
const filterLog = data.myFilter((el) => el < 4);
console.log(filterLog);
在实现上,.filter()
和.map()
非常相似,但因为filter
是基于布尔值来实现过滤, 所以我们在函数内添加了if()
条件句来过滤条件,并将结果推入到数组。
什么是防抖
这是一个著名的面试问题,并在现实世界也有非常多的用例。
防抖
是防止函数被过于频繁调用的一种方法,取而代之的是在上次调用之后等待一段时间之后再调用。
Amazon就是一个很好的例子,不论你在查找框中输入什么,都会等待至少0.5秒之后,结果才会输出,这就是防抖。
我们来看一个实现防抖的例子:基于用户的输入来生成用户名。
import "./styles.css";
let inputEle = document.getElementById("inputElement");
let username = document.getElementById("username");
let generateUsername = (e) => {
username.innerHTML = e.target.value.split(" ").join("-");
};
let debounce = function (cb, delay) {
let timer;
return function () {
let context = this;
clearTimeout(timer);
timer = setTimeout(() => {
cb.apply(context, arguments);
}, delay);
};
};
inputEle.addEventListener("keyup", debounce(generateUsername, 300));
在上述代码中,我们尝试基于用户的输入来生成自定义的用户名。用户开始打字之后,我们并不希望马上生成用户名,而是等待300毫秒之后再生成。 这里实际上是在模仿一个API调用,假设用户输入任意内容,然后必须调用后端API来抓取一个响应。
debounce()
函数接受两个值,cb
和delay
。cb
是当定时器(timer)到时间之后执行的回调函数。
我们使用setTimeout()
来创建定时器,也就是说在setTimeout函数体内部的函数会在一定时间之后执行。
apply
方法使用最初调用它的对象
来调用回调函数,并在调用中应用参数(arguments)和上下文(context)。
什么是闭包
闭包的MDN文档的定义:
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript中,闭包会随着函数的创建而被同时创建。
让我们来通过一个例子了解闭包是怎么运作的:
function start() {
var name = "Manu"; // name是一个由start()创建的本地变量
function displayName() {
// displayName()是内部函数,一个`闭包`
alert(name); //使用了父函数中的变量
}
displayName();
}
start(); // 弹出内容为"Manu"的警告框
在上述代码块中,闭包在start()
和 displayName()
之间形成。displayName()
函数可以访问定义在start()
函数的 name
变量。
简言之,内部函数了解周围环境(词法作用域)。
我写过一整篇博文讲解如何战胜JavaScript面试,如果你想深入了解JavaScript的面试过程,欢迎阅读。
React Hooks
当提到React Hooks的时候,前端编程面试中最常见的几个问题包括:
useState()
useReducer()
useEffect()
useRef()
- 自定义钩子和实现
useState()
钩子是如何运作的
想要管理组件的状态(state),可以使用useState()
。
举例如下:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [title, setTitle] = useState("freeCodeCamp");
const handleChange = () => {
setTitle("FCC");
};
return (
<div className="App">
<h1>{title} useState</h1>
<button onClick={handleChange}>Change Title</button>
</div>
);
}
useState()
方法包含两个值,state
变量以及一个函数
用于修改state变量。
在上述代码块中,我们创建了一个 title
状态(state)来存储页面的标题。初始状态(state)是 freeCodeCamp
。
点击按钮之后,setTitle()
方法将state变量修改为FCC
。
useState()
是你在函数组件中管理状态的首选资源。
useReducer()
钩子是如何运作的
简单来说,useReducer()
是管理应用状态一个比较炫酷的方式。它的结构性更强,能够帮助你管理应用中复杂的状态。
让我们就一个例子来理解useReducer:
import "./styles.css";
import { useReducer } from "react";
const initialState = { title: "freeCodeCamp", count: 0 };
function reducer(state, action) {
switch (action.type) {
case "change-title":
return { ...state, title: "FCC" };
case "increment-counter":
return { ...state, count: state.count + 1 };
default:
throw new Error();
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<div className="App">
<h1>{state.title} CodeSandbox</h1>
<button onClick={() => dispatch({ type: "change-title" })}>
Change Title
</button>
<button onClick={() => dispatch({ type: "increment-counter" })}>
Increment Counter
</button>
</div>
<p style={{ textAlign: "center" }}>{state.count}</p>.
</>
);
}
useReducer()
接受两个参数,reducer
函数和一个initialState(初始状态)
值。
reducer函数是基于 switch-case
的实现,并返回最终状态值。 useReducer()
在内部使用这个值然后返回给组件。
由 useReducer()
函数返回的值是state
和dispatch
。state
是组件内部使用的state
值。在我们的例子中,指的是title
和count
,它们可以被dispatch()
方法操控,然后由useReducer()
方法返回。
在我们的例子中,我们编写了一个change-title
来修改title
,它可以被 dispatch({ type: "change-title" })
函数触发,然后调用改标题的函数,改变状态的title
属性。
应用中的count
部分同理。
如我前文所述,这是一个更为炫酷的管理状态的办法。
useEffect()
钩子是如何运作的
可以这样思考:如果你希望state变量改变的时候有副作用
,可以使用useEffect()
来触发。
例如,输入栏的input value(输入值)
发生改变之后,就要调用API。你可以在useEffect()
中编写 API handle(处理API)
的逻辑。
import React, {useState, useEffect} from 'react';
export const App = () => {
const [value, setValue] = useState('');
useEffect(() => {
console.log('value changed: ', value);
}, [value])
return <div>
<input type="text" name="username" value={value} onChange={(e) => setValue(e.target.value)} />
</div>
}
在上述代码块中,将状态值value
绑定在输入框
,一旦用户在输入框输入任意内容,这个值就会发生改变。
一旦这个值更新并渲染之后,useEffect()
代码块就会被调用,console
声明被触发,然后输出最新的状态值。
useEffect()
的常见用例是调用API
。假设你需要通过输入框中的内容来调用API,useEffect函数将会是最好的办法。
这个函数的另一部分是依赖数组
,也就是useEffect()
的第二个参数。在我们的例子中,这个参数是[value]
。
也就是说每当 value
发生改变,useEffect就会被触发。如果你不在依赖数组
内传入任何值(即依赖数组为空数组),函数只会被调用一次。
useRef()
钩子是如何运作的
useRef使得你可以改变DOM(但不单单只使用useRef)。
据文档描述:
useRef返回一个可以修改的ref对象,其中 .current属性用于传参(initialValue)。 返回对象会在组件的整个生命周期保留。
简言之,如果想要在组件的整个生命周期保留某个值,就可以使用useRef。useRef的基本实现是基于DOM元素的,请看示例:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current`指向mounted文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
在这里,我们向input
传入了ref
属性,这将会和我们创建的inputEl
引用绑定。
现在input
元素可以按需改变。我们可以修改style
属性,让它更好看,修改value
属性查看预备值是什么等。
在上述例子中,当我们点击按钮,input
就被锁定,我们可以马上输入任何内容。我们可以通过 inputEl.current.focus()
来实现——focus()
方法是基于current
对象的。
什么是自定义钩子
为键盘事件创建一个自定义钩子是最常见的问题之一。
我们见过各式各样的钩子,面试官可能需要你创建自己的钩子。这可能对一些人来说是一种挑战,但是通过一些练习,这就会变得容易许多。
让我们先理解一下钩子
是什么:
自定义钩子的基本用例是提炼出函数逻辑到自己的组件。
假设你需要在每个组件内部都监听回车键
,与其一遍又一遍地重复编写监听
的逻辑,不如将这个逻辑提取出来,组成一个自己的组件,在需要的时候调用。(就像我们使用useState()
或 useEffect()
一样)。
若一个函数想要被称为钩子
,需要满足以下条件:
- 命名必须以
use
关键词开头。 - 我们可以决定传入什么参数、以及如果需要的话,返回什么值。
// 自定义钩子: useAvailable
function useAvailable(resource) {
const [isAvailable, setIsAvailable] = useState(null);
// ...
return isAvailable;
}
// 用例:
const isAvailable = useAvailable(cpu);
在上述代码中,无论我们在自定义钩子内部调用useState
和 useEffects
多少次,它们都完全独立于使用自定义钩子的函数。
让我们创建一个存储本地存储值(local storage values)
的自定义钩子。
如何创建一个自定义钩子 – useLocalStorage示例
useLocalStorage自定义钩子是将数据保存在本地存储的一种方法。在本地存储中采用key
和value
对来获取和存储值,这样不论用户何时返回到web应用,都会得到同样的结果。
在下面的实现中,一旦select
标签值发生改变,就会被存储到本地存储中。
useLocalStorage.js
// 使用本地存储自定义钩子
import { useState } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === 'undefined') {
return initialValue;
}
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore =
value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue] as const;
}
export default useLocalStorage;
App.js
import * as React from 'react';
import './style.css';
import useLocalStorage from './useLocalStorate';
export default function App() {
const [storedValue, setStoredValue] = useLocalStorage(
'select-value',
'light'
);
return (
<div>
<select
className="select"
value={storedValue}
onChange={(e) => setStoredValue(e.target.value)}
>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
<p className="desc">
Value from local storage: <span>{storedValue}</span>
</p>
</div>
);
}
useLocalStorage
接受两个参数,本地存储key名称
用于存储,以及必要的默认值
。
钩子返回两个值:你使用的key的 local storage value(本地存储值)
以及通过setter方法
来改变key的值
。在例子中就是setStoredValue
方法。
在useLocalStorage.js
中,我们首先使用localStorage.getItem()
方法中的GET
来获取本地存储值,如果存在,我们就获取这个值,然后用JSON.parse()
转化这个值并返回,否则initialValue就被设置为默认值。
setLocalStorage()
函数需确认我们传入的是函数还是一个简单值,并且使用 localStorage.setItem()
函数来设置本地存储的值。
如何通过副业项目脱颖而出
我确实有通过副业项目脱颖而出的经历。
我认为创建10来个大同小异的副项目大可不必,需要做的是创建一到两个你运用了你所学的所有React/HTML/CSS/JavaScript知识好项目。
假设面试官一周安排了14场面试,并且需要查看14个候选人的简历,由于你创建了每1000次访问后就收取1美元的链接缩短网站
而不是Amazon或者Netflix克隆,你就会脱颖而出。
当然,通过克隆项目锻炼技能没有什么问题。但是你至少有一个独一无二的项目才能够突出自己。
此外,创建辅助项目将帮助你提高开发技能。从头开始创建项目时,不可能事先了解所有内容,因此在这个过程中你将收获各种不同的技能并且熟练掌握。