字符串和数组处理(动手能力+逻辑处理)
题目:实现一个函数 formatQueryParams,将对象转换为 URL 参数字符串,并处理特殊要求和数组格式: const params = { name: "张三&李四", hobbies: ["篮球", "编程"], filters: { min: 20, max: 30 }, empty: null }
// 目标输出:“name=张三%26 李四&hobbies[]=篮球&hobbies[]=编程&filters[min]=20&fi1ters[max]=30^"
要求:
- 处理值中的特殊字符(如&,=) //encodeURIComponent,为了确保查询字符串符合 URL 编码规范,防止中文、空格、特殊符号(如 &、=、#、[、] 等)被浏览器或服务器误解析,避免请求出错或参数混乱。
- 数组转换为 key[]=value 格式 //
instanceof
- 对象转换为嵌套键名(如 filters[min])//
instanceof
- 忽略 null/undefined 值 // == null
- 不使用 URLSearchParams
formatQueryParams(params) {
// 用于存储最终拼接好的键值对字符串
const result = [];
/**
* 递归处理对象属性
* @param {object} obj - 当前正在处理的对象
* @param {string} prefix - 键名前缀(用于嵌套对象键名拼接)
*/
const processObject = (obj, prefix = "") => {
// 遍历对象的所有键值对
for (const [key, value] of Object.entries(obj)) {
// 跳过 null 或 undefined 的值,不进行处理
if (value == null) continue;
// 构建完整的键名,处理嵌套对象时会带前缀,如 user[name]
const fullKey = prefix ? `${prefix}[${key}]` : key;
// 如果当前值是数组
if (value instanceof Array) {
// 遍历数组中的每一项
for (const item of value) {
// 同样跳过 null 或 undefined
if (item != null) {
// 将数组项格式化为 key[]=value 形式,并进行 URL 编码
result.push(
`${encodeURIComponent(fullKey + "[]")}=${encodeURIComponent(item)}`
);
}
}
}
// 如果当前值是对象(且不是数组,因为上面已处理)
else if (value instanceof Object) {
// 递归调用自身处理嵌套对象,键名前缀更新为当前键名
processObject(value, fullKey);
}
// 如果是基本类型(字符串、数字、布尔值等)
else {
// 将键值对格式化为 key=value 形式,并进行 URL 编码
result.push(
`${encodeURIComponent(fullKey)}=${encodeURIComponent(value)}`
);
}
}
};
// 开始处理传入的参数对象
processObject(params);
// 使用 & 将所有格式化好的键值对连接成查询字符串并返回
return result.join("&");
}
JS 核心逻辑与性能优化(作用域+性能)
题目:分析以下代码问题,并重构优化:
function processData(items) { var results = []; for (var i = 0; i < items.length; i++) { setTimeout(function () { const processed = expensiveOperation(items[i]); results.push(processed); }, 0); } return results; }
- 指出作用域、异步逻辑、性能的三处问题
- 使用 ES6+语法重构函数
- 添加并行处理优化(使用 Promise/Pool 控制)
原始代码问题分析:
-
作用域问题(闭包陷阱)
- var i 是函数作用域,不是块级作用域。
- setTimeout 回调中访问的是 同一个 i 变量,循环结束后 i === items.length。
- 所有回调执行时都访问了相同的 i,导致 items[i] 访问越界,结果是 undefined。
示例:
const items = ["a", "b", "c"];
for (var i = 0; i < items.length; i++) {
setTimeout(() => {
console.log(i); // 都会打印3,而不是0,1,2
}, 0);
}
-
异步逻辑问题
- setTimeout(..., 0) 是异步的,但 processData 函数同步返回 results。
- 因为异步任务还未执行,返回的 results 还是空数组。
- 造成函数调用方无法正确拿到处理结果
-
性能问题
- 所有回调几乎同时放入宏任务队列,任务堆积。
- 不保证执行顺序,且不能限制并发数量(资源可能被耗尽)。
- results.push 的顺序和原数组顺序可能不一致,难保证数据顺序。
/**
* 并发受控的异步数据处理函数
* @param {Array} items - 要处理的元素数组
* @param {number} poolLimit - 最大并发数(默认同时最多处理的任务数量)
* @returns {Promise<Array>} - 返回一个 Promise,解析为所有处理后的结果数组,顺序与原数组一致
*/
async function processData(items, poolLimit = 5) {
// 存放最终处理后的结果,按原顺序填充
const results = [];
// 当前要处理的数组下标,多个并发任务共享该变量
let currentIndex = 0;
// 创建一个固定长度的任务池,每个异步任务都是一个 "工人"
const pool = new Array(poolLimit).fill(null).map(async () => {
// 每个"工人"不断从共享的 currentIndex 获取下一个任务
while (currentIndex < items.length) {
// 记录当前要处理的索引,并递增以便下一个工人获取下一个任务
const idx = currentIndex++;
// 执行耗时操作(可为同步或异步),使用 await 保证正确顺序
const processed = await expensiveOperation(items[idx]);
// 把结果放到对应索引,确保最终结果与原数组顺序一致
results[idx] = processed;
}
});
// 等待所有池中工人任务完成
await Promise.all(pool);
// 返回结果数组
return results;
}
Vue 问题处理能力(Vue 实践)
题目:在 Vue3 中遇到以下场景:
<script setup> import (ref, watch)from 'vue' const searchText = ref(’’) const results = ref([]) //现有实现(存在性能问题) watch(searchText,async(newVal)=>( results.value = await fetchResults(newVal) )) </script>
- 分析输入"vue"时可能发起的请求次数(3 次)及问题
- 使用防抖优化(要求保留最新结果)
- 添加竟态处理(取消过期请求)
- 修改为 Composition AP 最佳实践
答案:
-
- 输入"vue"时,会发起 3 次请求,分别是"v", "vu", "vue"。
- 存在的问题:
- 请求风暴:短时间内发起多次请求
- 结果错乱:后发请求可能先返回,导致显示旧结果
- 资源浪费:无效请求占用带宽和服务器资源
- 竞态问题:无法保证最终显示的是最后一次请求的结果
import { ref, watch } from "vue";
import { debounce } from "lodash-es";
export function useSearchResults(fetchFn, debounceMs = 300) {
const searchText = ref("");
const results = ref([]);
let requestId = 0;
const fetchAndSet = async (keyword) => {
const id = ++requestId;
const res = await fetchFn(keyword);
if (id === requestId) {
results.value = res;
}
};
const debouncedFetch = debounce(fetchAndSet, debounceMs);
watch(searchText, (val) => {
debouncedFetch(val);
});
return {
searchText,
results,
};
}
HTTP 请求知识(网络层)
题目:设计一个带完整错误处理的请求函数:
async function apiRequest(config) { //需实现以下能力: //1.自动处理不同环境域名(dev/test/prod) //2.请求超时(3s)//3.401自动刷新token重试 // 4.支持取消请求 //5.错误分级(网络错误/服务端错误/业务逻辑错误) }
- 补全函数实现
- 解释如何避免 CORS 问题
- 描述 CSRF 防御方案;
// 环境配置
const envConfig = {
dev: { baseURL: "http://dev.api.com" },
test: { baseURL: "http://test.api.com" },
prod: { baseURL: "https://api.com" },
};
// 获取当前环境
function getBaseUrl() {
const env = process.env.NODE_ENV || "dev";
return envConfig[env].baseURL;
}
// 请求函数
async function apiRequest(config) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
try {
const response = await fetch(`${getBaseUrl()}${config.url}`, {
method: config.method || "GET",
headers: {
"Content-Type": "application/json",
...config.headers,
},
body: config.data ? JSON.stringify(config.data) : null,
signal: controller.signal,
});
clearTimeout(timeoutId);
// 处理401未授权
if (response.status === 401) {
const newToken = await refreshToken();
// 更新header后重试
return apiRequest({
...config,
headers: { ...config.headers, Authorization: `Bearer ${newToken}` },
});
}
const data = await response.json();
// 处理业务错误
if (!response.ok) {
throw {
type: response.status < 500 ? "business" : "server",
message: data.message || "请求失败",
code: data.code || response.status,
};
}
return data;
} catch (error) {
clearTimeout(timeoutId);
// 错误分类处理
if (error.name === "AbortError") {
throw { type: "network", message: "请求超时" };
} else if (!navigator.onLine) {
throw { type: "network", message: "网络连接失败" };
} else {
throw error.type
? error
: {
type: "server",
message: error.message || "服务器错误",
};
}
}
}
// 避免CORS方案:
// 1. 服务器设置Access-Control-Allow-Origin
// 2. 开发环境使用代理服务器
// 3. 预检请求(OPTIONS)处理
// CSRF防御方案:
// 1. 使用SameSite Cookie属性
// 2. 添加CSRF Token到请求头
// 3. 验证Origin/Referer头
算法能力(中等问题)
题目:实现函数计算最长有效括号子串:
function longestValidParentheses(s) { //输入:")()())"输出:4(因为"()()") //输入:"(())(()"输出:4(因为"()()") } -使用动态规划或栈实现 - 解释时间复杂度 - 边界测试用例(空字符串 / 全无效 / 全有效);
function longestValidParentheses(s) {
let maxLen = 0;
// DP数组:dp[i]表示以i结尾的最长有效括号长度
const dp = new Array(s.length).fill(0);
for (let i = 1; i < s.length; i++) {
if (s[i] === ")") {
// 情况1:前一个字符是 '('
if (s[i - 1] === "(") {
dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
}
// 情况2:前一个字符是 ')' 且前面有匹配的 '('
else if (i - dp[i - 1] > 0 && s[i - dp[i - 1] - 1] === "(") {
const prevLen = i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0;
dp[i] = dp[i - 1] + prevLen + 2;
}
maxLen = Math.max(maxLen, dp[i]);
}
}
return maxLen;
}
// 边界测试用例:
console.log(longestValidParentheses("")); // 0 (空字符串)
console.log(longestValidParentheses(")))(((")); // 0 (全无效)
console.log(longestValidParentheses("()()")); // 4 (全有效)
console.log(longestValidParentheses(")()())")); // 4
console.log(longestValidParentheses("(())(()")); // 4
/**
* 时间复杂度:O(n) 单次遍历字符串
* 空间复杂度:O(n) 使用DP数组
*
* 算法思路:
* 1. 使用动态规划数组dp记录以每个位置结尾的有效括号长度
* 2. 遇到')'时检查两种情况:
* a. 前一个字符是'(':形成一对括号
* b. 前一个字符是')':检查是否有嵌套结构
* 3. 维护最大值maxLen
*/
处理数据
const options = [ { "label": "标签1", "value": 101, }, { "label": "标签2", "value": 102, "children": [] }, { "label": "标签3", "value": 103, "children": [ { "label": "标签31", "value": 103001, "children": [] } ] } ];
写一个 function getLabel(value, options) 函数,据传入的
value
在嵌套的options
结构中查找对应的label
,并返回一个数组(表示层级路径),如果找不到则返回null
。测试用例
console.log(getLabel(103001, options)); // ["标签3", "标签31"]
console.log(getLabel(104, options)); // null
题解
function getLabel(value, options) {
// 遍历 options 数组中的每一个 option 对象
for (const option of options) {
// 检查当前 option 的 value 是否等于传入的 value
if (option.value === value) {
// 如果匹配,返回包含当前 label 的数组
return [option.label]; // 直接匹配,返回 [label]
}
// 检查当前 option 是否有 children 且 children 数组不为空
if (option.children && option.children.length > 0) {
// 递归调用 getLabel,在 children 中继续查找 value
const childResult = getLabel(value, option.children); // 递归查找子级
// 如果在子级中找到匹配项
if (childResult) {
// 返回当前 label 和子级结果的拼接数组
return [option.label, ...childResult]; // 拼接当前 label 和子级结果
}
}
}
// 如果遍历完所有 option 都没找到匹配项,返回 null
return null; // 没找到,返回 null
}
事件循环
setTimeout(function() {
console.log(1);
}, 0);
new Promise(function(resolve, reject) {
console.log(2);
resolve();
}).then(function() {
console.log(3);
}).then(function() {
console.log(4);
});
console.log(6);
打印结果
26341