前端算法题和后端算法的区别:
前端更注重浏览器性能(如避免长耗时递归)。
数据量通常较小,优先考虑代码可读性。
常结合 DOM / 组件操作(如树形结构遍历对应 DOM 节点操作)
前端处理算法题
节流和防抖
- 节流(Throttle):
让一个函数在规定的时间间隔内,最多只执行一次。以第一次调用为准,忽略短时间内后续的调用,直到时间间隔结束。
特点:它的作用是让一个函数在规定的时间间隔内只执行一次,适用于需要持续响应但频率受限的场景。
- 防抖(Debounce):
当事件在一定时间间隔内多次触发时,以最后一次调用为准,只有最后一次触发后,经过设定的延迟时间,函数才会执行。
特点:确保函数在用户操作结束后一段时间执行,适用于需要等待用户输入稳定后再处理的场景。
函数防抖
防抖函数用于限制函数的执行频率;当事件在一定时间间隔内多次触发时,只有最后一次触发后,经过设定的延迟时间,才会执行回调函数。
常用于搜索框输入、窗口大小调整场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function debounce(fn, delay) { let timer = null; return function(...args) { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; }
|
测试函数:
1 2 3 4 5 6 7 8 9 10 11
| function search(query) { console.log(`搜索: ${query}`); }
const debouncedSearch = debounce(search, 300);
debouncedSearch("a"); debouncedSearch("ab"); debouncedSearch("abc");
|
代码两种写法的对比
看了多篇相关文章防抖函数this部分有两种写法,但是更推荐写法1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; }
function debounce(fn, delay) { let timer; return (...args) => { clearTimeout(timer); let content = this; timer = setTimeout(() => { fn.apply(content, args); }, delay); }; }
|
写法一:使用箭头函数的 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function throttle(fn, delay) { let lastTime = 0;
return function(...args) { const now = Date.now();
const diff = now - lastTime;
if (diff >= delay) { lastTime = now; fn.apply(this, args); } }; }
|
axios/fetch
post请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| fetch("https://api.example.com/data", { method: "POST", headers: { "Content-Type": "application/json" 'Authorization': 'Bearer your_token' }, body: JSON.stringify({ username: 'Alice', password: '123456' }) }) .then(response => { if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return response.json(); }) .then(data => console.log(data)) .catch(error => console.error(error));
axios.post('https://api.example.com/submit', { username: 'Alice', password: '123456' }, { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer your_token' } }) .then(response => console.log(response.data)) .catch(error => console.error(error));
|
- 多个 .then:用于处理异步操作的结果,每个
.then 处理前一个步骤的结果。
- response 和 data:来自 Fetch API 的响应对象和解析后的数据。
- return:返回一个值或 Promise,传递给下一个
.then 进行处理
axios创建单独的实例
1 2 3 4 5 6 7
| const instance1 = axios.create({ baseURL = "https//xxxxxx", timeout = 5000, headers={} })
|
拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import axios from 'axios';
const instance = axios.create({ baseURL: 'https://api.example.com', timeout: 5000 });
instance.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } );
instance.interceptors.response.use( response => { if (response.status === 200) { return response.data; } else { return Promise.reject(new Error(`HTTP 错误: ${response.status}`)); } }, error => { if (error.response) { console.error('服务器错误:', error.response.status, error.response.data); } else if (error.request) { console.error('请求无响应:', error.request); } else { console.error('请求异常:', error.message); } return Promise.reject(error); } );
export default instance;
|
promise,async/await
创建 Promise
1 2 3 4 5 6 7 8 9 10 11 12 13
| const promise = new Promise((resolve, reject) => { setTimeout(() => { Math.random() > 0.5 ? resolve('Success!') : reject(new Error('Failed!')); }, 1000); });
promise .then(result => console.log(result)) .catch(error => console.error(error)) .finally(() => console.log('Done'));
|
基础 fetch 请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| async function fetchData(){ try{ const response = await fetchData('url') const data = await response.json() console.log(data) }catch(error){ console.log(error) } }
async function fetchData(url, content){ try{ const response = await fetch(url, { method: 'POST', body: JSON.stringify({content}), }) }catch(error){ console.log(error) } }
|
基础axios请求
1 2 3 4 5 6 7 8 9 10 11 12
| import axios from 'axios'
async function fetchData(){ try{ const response = async axios.get('url') console.log(response.data) }catconsole.log(error)ch(error){
}
}
|
串行请求,并行请求,分批次处理请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| async function sequentialRequests() { const user1 = await fetchUser(1); const user2 = await fetchUser(2); const user3 = await fetchUser(3); return [user1, user2, user3]; }
async function parallelRequests() { const promises = [fetchUser(1), fetchUser(2), fetchUser(3)]; return await Promise.all(promises); }
async function getData(){ const [data1,data2] = await Promise.all([ axios.get('/api/data1'), axios.get('/api/data2') ]); return { user: user.data, orders: orders.data }; }
async function limitConcurrentRequests(ids, limit = 3) { const results = []; for (let i = 0; i < ids.length; i += limit) { const batch = ids.slice(i, i + limit); const batchResults = await Promise.all(batch.map(id => fetchUser(id))); results.push(...batchResults); } return results; }
|
数据结构算法题
两数之和 (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 16
| let 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++) { 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 21
| let nums = [3, 7, 12, 14] let target = 15 function twoSum2(nums, target) { const map = new Map(); for (let i = 0; i < nums.length; i++) { const currentNum = nums[i]; const complement = target - currentNum;
if (map.has(complement)) { return [map.get(complement), i]; }
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function isPalindrome(x) { if (x < 0) return false; let original = x; let reversed = 0; while (original !== 0) { const lastDigit = original % 10; reversed = reversed * 10 + lastDigit; original = Math.floor(original / 10); console.log(`当前反转数: ${reversed}, 剩余原数: ${original}`); } return x === reversed; }
console.log(isPalindrome(123));
|
- 最优解法:反转一半数字
回文数的后半部分反转后应与前半部分相同;用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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function isPalindrome(x) { if (x < 0 || (x % 10 === 0 && x !== 0)) { return false; }
let reverted = 0; while (x > reverted) { reverted = reverted * 10 + x % 10; x = Math.floor(x / 10); }
return x === reverted || x === Math.floor(reverted / 10); }
|
反转字符串(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 2 3 4 5 6 7 8 9 10 11 12
| function reverseString(s) { let left = 0; let right = s.length - 1;
while (left < right) { [s[left], s[right]] = [s[right], s[left]]; left++; right--; } }
|