我最近找工作时问到的所有前端面试问题。
前端面试题
前端面试题
这篇自述文件整理了我最近在新冠疫情期间求职过程中遇到的所有问题。我还附上了一份准备过程中参考的资源清单。
这些问题分为以下几个部分。
- JS
- 编码
- 作业
- 各种各样的
该解决方案并非可用于生产环境的代码,仅代表了我的粗略想法。您可以尝试实现自己的方法。
编辑于 2020 年 8 月 20 日。点击查看更改
JS
1) 给定一个深度为 n 的多维数组,将其展开。展开后,将其作为array
实例的某个方法使用
解决方案
/**
* [1,2,[3,4]] -> [1,2,3,4]
*/
let arr = [1,2,[3,4, [5,6, [7, [8, 9, 10]]]]]
function flatten(arr) {
return arr.reduce(function(acc, next){
let isArray = Array.isArray(next)
return acc.concat(isArray ? flatten(next) : next)
}, [])
}
if (!Array.prototype.flatten) {
Array.prototype.flatten = function() {
return flatten(this)
}
}
console.log(arr.flatten());
2)从头开始创建承诺
解决方案
class CustomPromise {
state = "PENDING"
value = undefined
thenCallbacks = []
errorCallbacks = []
constructor(action) {
action(this.resolver.bind(this), this.reject.bind(this))
}
resolver(value) {
this.state = "RESOLVED"
this.value = value
this.thenCallbacks.forEach((callback) => {
callback(this.value)
})
}
reject(value) {
this.state = "REJECTED"
this.value = value
this.errorCallbacks.forEach((callback) => {
callback(this.value)
})
}
then(callback) {
this.thenCallbacks.push(callback)
return this
}
catch (callback) {
this.errorCallbacks.push(callback)
return this
}
}
let promise = new CustomPromise((resolver, reject) => {
setTimeout(() => {
const rand = Math.ceil(Math.random(1 * 1 + 6) * 6)
if (rand > 2) {
resolver("Success")
} else {
reject("Error")
}
}, 1000)
})
promise
.then(function(response){
console.log(response)
})
.catch(function(error){
console.log(error)
})
3) 按平均评分和名称筛选电影列表。按电影对象中的任意字段对筛选后的列表进行排序
解决方案
// O(M)
function getMovies() {
return []; // [{id, name, year}]
}
// O(R)
function getRatings() {
return []; // [{id, movie_id, rating}] 0 <= rating <= 10 // e.g 9.3
}
/**
* minAvgRating ->
* avgRating >= minAvgRating
*
* sort ->
* name -> ascending order movies by name
* -name -> descending
*
* avgRating
*
*
* search ->
* 'ave' -> 'Avengers'
* 'avengers' -> 'Avengers'
* 'AvengersInfinitywar' -> 'Avengers'
*/
const toLower = str => str.toLocaleLowerCase()
const getAvrgRating = (movie, movingWithRatings) => {
let count = 0;
return movingWithRatings.reduce((acc, value, index) => {
const movieMatch = movie.id === value.movie_id
if (movieMatch) {
acc+=value.rating
count++
}
if (index === movingWithRatings.length - 1) {
acc = acc/count
}
return acc
}, 0)
}
const isSubString = (str1, str2) => {
str1 = toLower(str1.split(" ").join(""))
str2 = toLower(str2.split(" ").join(""))
if (str1.length > str2.length) {
return str1.startWith(str2)
} else {
return str2.startWith(str1)
}
}
const moviesList = getMovies()
const movingWithRatings = getRatings();
function queryMovies({ search, sort, minAvgRating }) {
let filteredMovies = movingWithRatings.filter(movie => getAvrgRating(movie, movingWithRatings) >= minAvgRating);
filteredMovies = filteredMovies.map(movie => moviesList.filter(listItem => listItem.id === movie.movie_id).pop())
filteredMovies = filteredMovies.filter(movie => isSubString(toLower(movie.name), toLower(search)))
filteredMovies = filteredMovies.sort((a, b) => {
const isDescending = sort[0] === '-' ? true : false
let sortCopy = isDescending ? sort.slice(1) : sort
const value1 = a[sortCopy]
const value2 = b[sortCopy]
if (isDescending) {
return value1 > value2 ? -1 : 1
}else {
return value1 < value2 ? -1 : 1
}
})
filteredMovies = filteredMovies.map(movie => ({
...movie,
avgRating: movingWithRatings.filter(ratedMovie => ratedMovie.movie_id === movie.id)[0].rating
}))
return filteredMovies
}
4) 给定一个端点 URL 来获取所有posts
和comments
。执行以下操作。
- 将所有评论映射到其所属的帖子。映射后的结果数据应具有以下结构。
//service.js
const POSTS_URL = `https://jsonplaceholder.typicode.com/posts`;
const COMMENTS_URL = `https://jsonplaceholder.typicode.com/comments`;
export const fetchAllPosts = () => {
return fetch(POSTS_URL).then(res => res.json());
};
export const fetchAllComments = () => {
return fetch(COMMENTS_URL).then(res => res.json());
};
import { fetchAllPosts, fetchAllComments } from "./service";
const fetchData = async () => {
const [posts, comments] = await Promise.all([
fetchAllPosts(),
fetchAllComments()
]);
const grabAllCommentsForPost = postId =>
comments.filter(comment => comment.postId === postId);
const mappedPostWithComment = posts.reduce((acc, post) => {
const allComments = grabAllCommentsForPost(post.id);
acc[post.id] = allComments;
return acc;
}, {});
console.log("mappedPostWithComment ", mappedPostWithComment);
};
fetchData();
5) 在字符串实例上实现一个方法getHashCode
。该方法应该适用于所有字符串。
解决方案
let s1 = "sample"
if (!String.prototype.getHashCode) {
String.prototype.getHashCode = function(){
console.log('String instance ', this)
return this
}
}
6) 以下表达式的结果是什么
1+true
true+true
‘1’+true
‘2’ > ’3’
‘two’>’three’
解决方案
2
2
1true
false
true
7)实施bind
和reduce
。
解决方案
//bind
if (!Function.prototype.bind) {
Function.prototype.bind = function(...arg){
const func = this
const context = arg[0]
const params = arg.slice(1)
return function(...innerParam) {
func.apply(context, [...params, ...innerParam])
}
}
}
//reduce
Array.prototype.reduce = function(func, initState) {
const arr = this
const callback = func
let init = initState
arr.forEach(function(value, index){
init=callback(init, value)
})
return init
}
8)实现去抖动功能
解决方案
const debounce = function(func, interval) {
let timerId;
return function(e){
clearTimeout(timerId)
timerId = setTimeout(function(){
func.apply()
}, interval)
}
}
debounce(apiCall, 3000)
9)实现节流功能
解决方案
const throttle = (callback, interval) => {
let timerId;
let allowEvents = true;
return function() {
let context = this;
let args = arguments;
if (allowEvents) {
callback.apply(context, args)
allowEvents = false;
timerId = setTimeOut(function(){
allowEvents = true
}, interval)
}
}
}
10)设计 API 轮询机制。该 API 每隔固定时间间隔调用一次。该 API 是一个股票 API,用于获取股票的最新价格。获取结果后,渲染 UI。
这个问题需要的是解决方案的设计层面,而不是代码层面。这是一个开放式的问题。
解决方案
//With setInterval, throttling and flags
setInterval=>Endpoint=>Render
//with the inversion of control
//Endpoint=>Render=>setTimeout=>Endpoint=>Render=>SetTimeout...
11)将下面给出的基于类的继承代码转换为 ES5 代码。
class Parent(name){
constructor(name) {
this.name=name
}
getName(){return this.name}
}
class Children extends Parent {
constructor(props){
super(props)
}
}
解决方案
function Parent(name) {
this.name = name
}
Parent.prototype.getName = function() {
return this.name
}
function Children(name){
Parent.call(this, name)
}
Children.prototype = new Parent()
12) 以下代码的计算结果是什么?
//Q.1
var x = 1;
var y = x;
x = 0;
console.log(x, y);
//Q.2
var x = [1];
var y = x;
x = [];
console.log(x,y);
//Q.3
function Abc() { console.log(this); };
Abc()
new Abc();
//Q.4
var x = 1;
var obj = {
x: 2,
getX: function () {
return console.log(this.x);
}
};
obj.getX()
let a = obj.getX
console.log(a)
//Q.5
//How to get the a to log 2 in the above code
//Q.6
console.log("A");
setTimeout(() => console.log("B"), 0);
setTimeout(() => console.log("C"), 0);
console.log("D");
//Q.7
setTimeout(function() {
console.log("A");
}, 0);
Promise.resolve().then(function() {
console.log("B");
}).then(function() {
console.log("C");
});
console.log("D");
//Q.8
let obj1 = {
a:1,
b:2
}
function mutate(obj) {
obj = {a:4, c:6}
}
console.log(obj1)
mutate(obj1)
console.log(obj1)
解决方案
//A.1
0 1
//A.2
[] [1]
//A.3
window object is logged
//A.4
logs 2 and 1
//A.5
a.call(obj);
//A.6
A, D, B , C
//A.7
D, B, C, A
//A.8
{ a: 1, b: 2 }
{ a: 1, b: 2 }
13) 给定一个数字数组,实现以下
const list = [1,2,3,4,5,6,7,8]
const filteredArray = list.filter(between(3, 6)) // [4,5]
解决方案
function between(start, end) {
return function (value,index) {
return value>start && value<end
}
}
算法
1)考虑以下系列:
A := 1
B := A*2 + 2
C := B*2 + 3 and so on...
编写一个程序:
输出与给定字母对应的数字
给定一个像“GREP”这样的字母字符串,计算字符串中所有字母对应的数字之和(即 G + R + E + P),如上述系列所示,
给定一个大数字(适合标准的 32 位整数),找到与之对应的最短字母字符串。
最后一部分可以使用贪婪算法。根据需要计算字母对应的数字值,不要预先计算并将其存储在数据结构中。
解决方案
//A = 1
//B = A*2 +2
//C = B*2+ 3
//D = C*2+ 3
var genCharArray = function(charA, charZ) {
var a = [], i = charA.charCodeAt(0), j = charZ.charCodeAt(0);
for (; i <= j; ++i) {
a.push(String.fromCharCode(i));
}
return a;
}
var charMap = {};
var charArray = genCharArray('a', 'z');
charArray.forEach(function(char, index){
charMap[char] = Number(index + 1);
});
var charSequence = function(char){
if(typeof char==="string"){
char = charMap[char];
}
if(char==1){
return 1;
}else{
return char + 2 * charSequence(char-1);
}
}
var input = process.argv[2];
if(input.length===1){
console.log(charSequence(charMap[input]));
}else if(input.length>1){
var charTotalSequence = input.split("").reduce(function(acc, curr){
return acc + charSequence(charMap[curr]);
},0);
console.log(charTotalSequence);
}
2) 给定一个数组,找到一对,使得它们的和等于给定的数字
解决方案
let nums = [2, 7, 10, 1, 11, 15, 9]
let target = 11
let numsMap = new Map()
let pairs = nums.reduce((acc, num) => {
let numToFind = target - num
if (numsMap.get(numToFind)) {
return [...acc, [num, numToFind]]
} else {
numsMap.set(num, true)
return [...acc]
}
}, [])
console.log("Pairs ", pairs)
3) 在给定数组中查找局部最大值。局部最大值是指大于其左右相邻元素的元素。我提供了一个 O(n) 的解法,在进行优化之前,这个解法非常简单。
解决方案
let x = [1, 2, 3, 5, 4] //Outputs: 5
if x.length == 1 return x[0]
else
let i = 1
for(;i<x.length-1;i++){
if x[i-1]<x[i] and x[i] > x[i+1] return x[i]
}
if x.length - 1 == i return x[i]
4)将矩阵顺时针旋转90度。解应该在原处。
解决方案
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
//The solution is to first take the transpose of the matrix.
//After taking the transpose the resulting matrix is as follows.
[
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]
]
//After the transpose step, All we have to do is to reverse the array @ each entry.
//The resulting matrix after after reversal is as follows.
[
[7, 4, 1],
[8, 5, 2],
[9, 6, 3]
]
//The above matrix is rotated 90 degree
5) 最大子数组和模 m
6) 给定一个数组,找到数组中三个元素,使它们之和等于给定的目标
解决方案
let x = [1, 2, 3, 4, 5]
let target = 7
let found = []
const twoPointer = (l ,r, current) => {
while(l<r){
const totalSum = current + x[l] + x[r]
if (totalSum === target) {
found.push([current, x[l], x[r]])
return
} else if (totalSum > target) {
r--
} else {
l++
}
}
}
const threeSum = (x, target) => {
for (let i=0;i<x.length;i++) {
const current = x[i];
let leftPointer = i+1
let rightPointer = x.length - 1
if (current+x[leftPointer]+x[rightPointer] === target) {
found.push([current, x[leftPointer], x[rightPointer]])
} else {
twoPointer(leftPointer, rightPointer, current)
}
}
return found
}
7) 给定一个字符串和一个整数 k,找出所有不同字符恰好出现 k 次的子字符串的数量。
解决方案
const subStrHasSameCharCount = (str, startIndex, endIndex, totalHop) => {
let charMap = {}
for (let k=startIndex;k<endIndex;k++) {
let currentChar = str[k]
if (charMap[currentChar]) {
charMap[currentChar]++
} else {
charMap[currentChar] = 1
}
}
let totalCount = Object.values(charMap).length > 0
return totalCount ? Object.values(charMap).every(item => item == totalHop) : false
}
const characterWithCountK = (str, k) => {
if (k == 0) return ''
let count = 0
let initialHop = k
while (initialHop < str.length) {
for (let j=0;j<str.length;j++) {
let startIndex = j
let endIndex = j + initialHop
if(endIndex > str.length) continue
count = subStrHasSameCharCount(str, startIndex, endIndex, k)
? count + 1: count
}
initialHop+=k
}
count = subStrHasSameCharCount(str, 0, initialHop, k)
? count + 1: count
return count
}
let str = 'aabbcc'
let k = 2
console.log(characterWithCountK(str, k))
8) 给定两个输入字符串 s1 和 s2,包含从 az 开始的不同顺序的字符,检查重新排列 s1 中的字符串是否会产生与 s2 相等的字符串。
解决方案
let s1 = 'dadbcbc'
let s2 = 'ccbbdad'
let charMap = {}
const canBeRearranged = (s1, s2) => {
if(s1.length!==s2.length){
return false
}
for(let i=0;i<s1.length;i++){
const charFromString1 = s1[i]
const charFromString2 = s2[i]
if(charFromString1 in charMap){
charMap[charFromString1]++
} else {
charMap[charFromString1] = 1
}
if(charFromString2 in charMap){
charMap[charFromString2]--
} else {
charMap[charFromString2] = -1
}
}
for(let x in charMap){
if (charMap[x]!==0){
return false
}
}
return true
}
canBeRearranged(s1, s2)
9) 给定一个数组或变量输入大小,编写一个函数来打乱该数组。
解决方案
const swap = (index1, index2, arr) => {
let temp = arr[index1]
arr[index1] = arr[index2]
arr[index2] = temp
}
const shuffle = (arr) => {
let totalLength = arr.length
while(totalLength > 0) {
let random = Math.floor(Math.random() * totalLength)
totalLength--
swap(totalLength, random, arr)
}
return arr
}
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
arr = shuffle(arr)
10)计算无限深度的多维数组中所有元素的总和。
解决方案
let arr = [4, 5, 7, 8, [5, 7, 9, [3, 5, 7]]]
let sum = 0
const calculateSum = (arr) => {
arr.reduce(function(acc, currentVal) {
const isEntryArray = Array.isArray(currentVal)
if (isEntryArray) {
return acc.concat(calculateSum(currentVal))
} else {
sum+=currentVal
return acc.concat(currentVal)
}
}, [])
}
calculateSum(arr)
console.log(sum)
11) 展平不同债务的嵌套对象。
解决方案
const obj = {
level1: {
level2: {
level3: {
more: 'stuff',
other: 'otherz',
level4: {
the: 'end',
},
},
},
level2still: {
last: 'one',
},
am: 'bored',
},
more: 'what',
ipsum: {
lorem: 'latin',
},
};
var removeNesting = function(obj, parent){
for (let key in obj){
if (typeof obj[key] === "object") {
removeNesting(obj[key], parent+"."+key)
} else {
flattenedObj[parent+'.'+key] = obj[key]
}
}
}
let flattenedObj = {}
const sample = removeNesting(obj, "");
console.log(flattenedObj);
12) 给定一个 JSON 输入,其中每个条目代表一个目录,并且每个目录又可以有自己的嵌套条目。创建最终的目录结构。
13) 给定一个包含员工数据列表的对象数组,其中每个员工都有一个下属列表。使用此信息构建员工层级结构。
解决方案
const employeesData = [{
id: 2,
name: 'Abhishek (CTO)',
reportees: [6]
}, {
id: 3,
name: 'Abhiram (COO)',
reportees: []
}, {
id: 6,
name: 'Abhimanyu (Engineering Manager)',
reportees: [9]
}, {
id: 9,
name: 'Abhinav (Senior Engineer)',
reportees: []
}, {
id: 10,
name: 'Abhijeet (CEO)',
reportees: [2, 3],
}];
/*
A (CEO)
----B (CTO)
--------D (Engineering Manager)
------------E (Senior Software Engineer)
----C (COO)
*/
const findCeo = (currentEmp) => {
let parentEmployee = employeesData.filter(emp => emp.reportees.indexOf(currentEmp.id) > -1)
if (parentEmployee && parentEmployee.length > 0) {
return findCeo(parentEmployee[0])
} else {
return currentEmp
}
}
const logHierarchy = (currentEmp, indent) => {
console.log("-".repeat(indent) + currentEmp.name)
indent+=4;
for(let i=0;i <currentEmp.reportees.length;i++) {
let employee = employeesData.filter(emp => emp.id === currentEmp.reportees[i])
logHierarchy(employee[0], indent)
}
}
const traverse = (employee) => {
let ceo = findCeo(employee)
logHierarchy(ceo, 0)
}
traverse(employeesData[0])
14)以螺旋形式打印给定矩阵
const inputMatrix = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11,12,13,14,15],
[16,17,18,19,20],
]
const exprectOutput = [1,2,3,4,5,10,15,20,19,18,17,16,11,6,7,8,9,14,13,12]
解决方案
function spiralParser(inputMatrix){
const output = [];
let rows = inputMatrix.length;
let cols = rows > 0 ? inputMatrix[0].length : 0;
//singleEmptyRow => Edge case 1 //[]
if (rows === 0) {
return []
}
if (rows === 1) {
//singleElementRowNoCol => Edge case 2 //[[]]
if (cols === 0) {
return []
} else if (cols === 1){
//singleElementRow => Edge case 3 //[[1]]
output.push(inputMatrix[0][0])
return output
}
}
let top = 0;
let bottom = rows - 1;
let left = 0;
let right = cols - 1;
let direction = 0;
//0 => left->right
//1 => top->bottom
//2 => right->left
//3 => bottom->top
while(left <= right && top <= bottom) {
if(direction === 0) {
//left->right
for (let i=left; i<=right;i++) {
output.push(inputMatrix[top][i])
}
top++;
} else if (direction === 1) {
//top->bottom
for (let i=top; i<=bottom;i++) {
output.push(inputMatrix[i][right])
}
right--
} else if (direction === 2) {
//right->left
for (let i=right; i>=left;i--) {
output.push(inputMatrix[bottom][i])
}
bottom--
} else if (direction === 3) {
//bottom->top
for (let i=bottom; i>=top;i--) {
output.push(inputMatrix[i][left])
}
left++
}
direction = (direction + 1) % 4
}
return output;
}
console.log(spiralParser(inputMatrix2))
15) 在给定字符串中查找最大连续重复字符。
let str = 'bbbaaaaccadd'; //max repeating char is a with count 4
解决方案
//sudo code
maxNow = if input string length is 1 or greater than 1 ? 1 : 0
maxOverall = if input string length is 1 or greater than 1 ? 1 : 0
for char in inputString starting from index 1
if char equals prevChar
maxNow++
maxOverall = max(maxOverall, maxNow)
else if char not equals prevChar
maxNow = 1
16) 给定一个长度不等的输入数组,将数组末尾的所有 2 分开。
let inputArr = [2,9,1,5,2,3,1,2,7,4,3,8,29,2,4,6,54,32,2,100]
//ouput => [9,1,5,3,1,7,4,3,8,29,4,6,54,32,100,2,2,2,2,2]
解决方案
let slowRunner = 0
for (let fastRunner=0;fastRunner<arr.length;fastRunner++) {
if (arr[fastRunner]!==2 && arr[slow] == 2) {
[arr[fastRunner], arr[slow]] = [arr[slow], arr[fastRunner]]
slowRunner++
}
}
17)反转链表
//Input = 1 -> 2 -> 3 -> 4 -> 5 -> 6
//Output = 1 <- 2 <- 3 <- 4 <- 5 <- 6
解决方案
//sudo code
let current = head
let prev = null
let next = null
while(current) {
next = current.next
current.next = prev
prev = current
current = next
}
18)使用迭代进行前序树遍历(无递归)
解决方案
//sudo code
const preorder = (root) => {
let stack = []
stack.push(root)
while(there is element in stack) {
let current = stack.pop()
console.log(current.value)
if (current.right) {
stack.push(current.right)
}
if (current.left) {
stack.push(current.left)
}
}
}
作业
1)设计一个停车场系统,要求如下:
- 最多可容纳 N 辆车。处理停车场的可用性。
- 车辆进出记录。
- 自动售票系统将为进入/离开停车场的每辆车进行车辆登记,其中包含车辆详细信息,例如:登记号、颜色、分配的停车位。
我应该能够查询:
- 特定颜色的所有车辆的登记号码。
- 指定登记号的车辆的停车位。
- 指定颜色的车辆停车位。
- 停车场可用车位列表。
要求:
- 可以使用任何东西来构造代码:类/结构。
- 您的解决方案应该可扩展以适应未来的用例。
一些代码设计原则:
- 代码的模块化。
- 命名约定。
- SOLID 原则。
2) 创建一个 React 组件Ping
,用于对给定 URL 进行 API 调用。如果 API 调用返回状态码 200,则表示用户在线。如果 API 调用返回的状态码不是 200,则表示用户处于离线状态。
尝试更改
status
表单开发工具网络面板
3) 从输入创建动态表单生成器json
。该表单可以根据 进行分组id
。每个组可以有自己的嵌套组。
4) 用纯 JavaScript 创建一个支持行adding
和removing
列的极简 Excel 工作表。这道题的时间限制为 40 分钟。
5)您必须制作一个搜索输入框,它将搜索用户列表。
用户对象有以下字段
id: a unique id
name: user’s name
items: list of items ordered by user
address: address of the user
pincode: user address pin code
您必须对所有字段执行搜索。
搜索结果将显示为用户卡列表。
总结一下
,在搜索框中输入内容后,搜索结果列表就会打开。搜索可能只是字符串匹配搜索。
卡片列表可以通过键盘或鼠标导航,
如果同时使用鼠标和键盘进行导航,则一次只能突出显示一张卡片
(如果鼠标悬停在列表上,则键盘将优先,同样,如果不使用键盘导航,则鼠标将优先)。
这种行为类似于 YouTube 搜索的工作方式
当未找到搜索结果时,将显示一个空卡片,
卡片列表将可滚动。
突出显示的卡片(通过键盘/鼠标)将滚动到视图中
各种各样的
1) 你将如何构建一个前端应用程序点击
1) 实现延迟加载点击
2) 什么是服务器端渲染。
3) 如何将 React 应用程序部署到生产环境。
4) 什么是服务工作者/Web 工作者。
5) 如何优化 Web 应用程序并使其更高性能。
6) 解释不同类型的客户端缓存策略。
7) 什么是 CORS。
8) React 中的高阶组件是什么。
9) redux 中的 connect 函数是如何工作的。
10) React 中的纯组件是什么。