面试题--算法题
前端算法题和后端算法的区别:
前端更注重浏览器性能(如避免长耗时递归)。
数据量通常较小,优先考虑代码可读性。
常结合 DOM / 组件操作(如树形结构遍历对应 DOM 节点操作)
前端处理算法题
节流和防抖
- 节流(Throttle):
让一个函数在规定的时间间隔内,最多只执行一次。以第一次调用为准,忽略短时间内后续的调用,直到时间间隔结束。
特点:它的作用是让一个函数在规定的时间间隔内只执行一次,适用于需要持续响应但频率受限的场景。 - 防抖(Debounce):
当事件在一定时间间隔内多次触发时,以最后一次调用为准,只有最后一次触发后,经过设定的延迟时间,函数才会执行。
特点:确保函数在用户操作结束后一段时间执行,适用于需要等待用户输入稳定后再处理的场景。
函数防抖
防抖函数用于限制函数的执行频率;当事件在一定时间间隔内多次触发时,只有最后一次触发后,经过设定的延迟时间,才会执行回调函数。
常用于搜索框输入、窗口大小调整场景
1 | function debounce(fn, delay) { |
测试函数:
1 | function search(query) { |
代码两种写法的对比
看了多篇相关文章防抖函数this部分有两种写法,但是更推荐写法1:
1 | // 写法一:直接使用箭头函数的 this |
写法一:使用箭头函数的 this(继承自外层作用域);支持动态绑定 this,适用场景如事件处理
写法二:显式保存 this 到 content 变量,不支持动态绑定,适用于静态方法
为什么写法一更推荐
- 箭头函数的 this 继承机制:
箭头函数没有自己的 this,它继承自外层函数(即 debounce 返回的箭头函数)
调用时动态绑定 this:当调用 onSearch.call(obj, “query”) 时,this 会指向 obj,从而在 fn.apply(this, args) 中正确传递上下文- 灵活性:支持动态绑定:
调用防抖函数时可以传入不同的 this,适用于事件处理、对象方法等场景- 写法二:固定 this:content = this 会在 debounce 定义时绑定 this,后续调用无法动态修改
防抖函数中,为什么选择使用箭头函数而不是普通函数?
箭头函数没有自己的 this,它会继承外层作用域的 this。在防抖函数中,我们希望调用者能够动态绑定 this(例如事件处理函数的 this 指向 DOM 元素),而箭头函数能保证 this 的一致性,避免在 setTimeout 中丢失上下文。
函数节流
是一种控制函数执行频率的技术。它的作用是让一个函数在规定的时间间隔内最多只执行一次。
在一定时间间隔内,如果一个事件被多次触发,只以第一次被触发为准,忽略短时间内后续的调用,直到时间间隔结束。c
1 | function throttle(fn, delay) { |
axios/fetch
post请求
1 | fetch("https://api.example.com/data", { |
- 多个 .then:用于处理异步操作的结果,每个
.then
处理前一个步骤的结果。- response 和 data:来自 Fetch API 的响应对象和解析后的数据。
- return:返回一个值或 Promise,传递给下一个
.then
进行处理
axios创建单独的实例
1 | //创建单独的实例发送网络请求 |
拦截器
1 | import axios from 'axios'; |
promise,async/await
创建 Promise
1 | const promise = new Promise((resolve, reject) => { |
基础 fetch 请求
1 | async function fetchData(){ |
基础axios请求
1 | import axios from 'axios' |
串行请求,并行请求,分批次处理请求
1 | // 串行请求:依次等待每个请求完成 |
数据结构算法题
两数之和 (LeetCode 1)(数组 + 哈希表)
前端应用场景:表单搜索联想、数据匹配校验等
给定一个整数数组 nums 和一个目标值 target,在数组中找出两个数,使其和为 target,返回它们的索引(假设只有唯一解,不能重复使用同一个元素)
输入:nums = [2, 7, 11, 15], target = 9
输出:[0, 1](因为 2 + 7 = 9)
- 暴力解法(量少行,不推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16let nums = [3, 7, 12, 14]
let target = 15
function twoSum1(nums, target) {
const n = nums.length;
for (let i = 0; i < n; i++) { // 外层循环:选第一个数
for (let j = i + 1; j < n; j++) { // 内层循环:选第二个数(j在i后面)
if (nums[i] + nums[j] === target) { // 检查和是否等于目标数
return [i, j]; // 找到就返回索引
}
}
}
return [];
}
console.log("原来数组:",nums,"目标值:",target,"twoSum1返回索引:",twoSum1(nums,target)) - 使用哈希表(推荐)
- 当遍历到一个数
nums[i]
时,需要知道是否存在另一个数complement = target - nums[i]
,如果存在,那这两个数就是答案。 - 用 Map 来记录每个数出现的位置,这样每次遇到
nums[i]
时,只需在 Map 中查complement
是否存在,存在就直接返回结果 - Map 存的是 “之前出现过的数及其索引”,每次新来一个数,先查补数是否在 Map 里,再把自己存进去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let nums = [3, 7, 12, 14]
let target = 15
function twoSum2(nums, target) {
const map = new Map(); // 创建一个Map,用来存“数值:索引”
for (let i = 0; i < nums.length; i++) { // 遍历数组,i是当前数的索引
const currentNum = nums[i]; // 当前数
const complement = target - currentNum; // 计算需要的补数
// 重点:先查补数是否在Map里
if (map.has(complement)) {
// 如果存在,说明前面已经有一个数等于complement,直接返回它的索引和当前索引
return [map.get(complement), i];
}
// 如果不存在,就把当前数和索引存入Map,供后面的数查询
map.set(currentNum, i);
}
return []; // 题目保证有解,这行可以省略
console.log("原来数组:",nums,"目标值:",target,"twoSum2返回索引:",twoSum2(nums,target))
}
回文数(LeetCode 9)
判断一个整数是否是回文数。不允许将整数转为字符串
输入:121 → 输出:true(正序和逆序都是 121)
输入:-121 → 输出:false(负数一定不是回文数)
输入:10 → 输出:false(逆序为 01,即 1,与原数不相等)
- 暴力解法(不推荐,但必须理解):
反转整个数:将原数 x 逐位反转,得到反转后的数 reversed。
比较:判断 x 是否等于 reversed取最后一位:original % 10
移除最后一位:Math.floor(original / 10)
构建反转数:reversed = reversed * 10 + 最后一位
为什么用 Math.floor()?
js中整数除法会返回浮点数(如 123 / 10 = 12.3),而我们需要的是整数部分12,Math.floor() 向下取整,确保结果为整数
1 | function isPalindrome(x) { |
- 最优解法:反转一半数字
回文数的后半部分反转后应与前半部分相同;用x判断是否反转到一半,当原数x小于等于反转后的数reverted时,说明已反转一半以 x = 1221 为例:
初始状态:x = 1221,reverted = 0- 逐位反转:
取最后一位 1 → reverted = 0 * 10 + 1 = 1,x = 122
取最后一位 2 → reverted = 1 * 10 + 2 = 12,x = 12 - 停止条件:
此时 x = 12 ≤ reverted = 12,说明已反转一半,停止循环 - 比较结果:
x = 12 与 reverted = 12 相等 → 返回 true
- 逐位反转:
如果是奇数长度的回文数(如12321)
反转过程:x = 12321 → reverted = 1 → 12 → 123
停止时:x = 12,reverted = 123
处理方式:去除中间位(reverted / 10),再比较
1 | function isPalindrome(x) { |
反转字符串(LeetCode 344)
编写一个函数,反转输入的字符串(原地修改,不使用额外数组)。
示例:输入 s = [“h”,”e”,”l”,”l”,”o”],输出 [“o”,”l”,”l”,”e”,”h”]
核心思路:双指针对撞法
定义两个指针:
left 指向数组开头(0),right 指向数组末尾(length-1)。
交换元素并移动指针:
交换 s[left] 和 s[right],然后 left++,right–。
直到 left >= right 时停止(此时所有元素已反转)
1 | function reverseString(s) { |