首页
壁纸
统计
友链
Search
1
Nestjs概述-中文
10 阅读
2
ExpressAPI
10 阅读
3
NestJS新手入门核心模块对比表笔记
9 阅读
4
JavaScript企业数据处理实用指南
9 阅读
5
Vue2详细笔记
8 阅读
Nodejs
Vue
Java
Msql
登录
Search
Wasnl
累计撰写
12
篇文章
累计收到
1
条评论
首页
栏目
Nodejs
Vue
Java
Msql
页面
壁纸
统计
友链
搜索到
8
篇与
的结果
2026-03-26
JavaScript 核心知识笔记
# JavaScript 核心知识笔记一、异步编程核心1. Promise 基础Promise 是 JavaScript 异步编程的基础,代表一个异步操作的最终完成(或失败)及其结果值。new Promise():创建一个异步任务。执行器函数接收 resolve 和 reject 两个参数。then() / catch():用于处理Promise成功或失败的结果。then 接收成功回调,catch 接收失败回调。Promise.resolve():将一个值包装成一个已成功的Promise对象。Promise.reject():将一个值包装成一个已失败的Promise对象。Promise.all():接收一个Promise数组,等待所有Promise都成功,返回一个包含所有结果的数组。只要有一个失败,整体就失败。Promise.race():接收一个Promise数组,等待第一个完成的Promise(无论成功或失败),返回其结果。2. async/await 语法糖async/await 是基于Promise的语法糖,让异步代码写起来像同步代码一样清晰。async函数:声明一个异步函数,它会隐式地返回一个Promise对象。await关键字:在 async 函数内部使用,用于等待一个Promise完成。await 会暂停函数的执行,直到Promise状态变为 fulfilled(成功)或 rejected(失败)。成功时直接返回结果值,失败时需要用 try...catch 捕获。// 示例:使用 async/await 替代 then/catch async function fetchUserData() { try { const user = await fetch('/api/user').then(res => res.json()); const posts = await fetch(`/api/posts/${user.id}`).then(res => res.json()); console.log('用户和帖子数据:', { user, posts }); } catch (error) { console.error('请求失败:', error); } }二、数组核心处理函数数组方法是JavaScript中最常用的工具集,掌握它们是高效处理数据的关键。2.1 every() - 全真才为真作用:测试一个数组内的所有元素是否都能通过指定函数的测试。它返回一个布尔值。场景:表单校验:检查所有输入项是否都通过了校验。权限校验:判断用户是否拥有全部所需的权限。批量数据的合规性检查:如所有商品数量是否都大于0。// 表单校验示例 const formFields = [ { name: 'username', valid: true, value: '张三' }, { name: 'email', valid: true, value: 'zhangsan@example.com' }, { name: 'phone', valid: false, value: '123' } ]; const isFormValid = formFields.every(field => field.valid); // false2.2 some() - 一真即为真作用:测试数组中是否至少有一个元素通过了指定函数的测试。它返回一个布尔值。场景:判断列表中是否存在未完成/异常的状态项。快速判断搜索是否命中结果。权限判断:只要用户拥有一个相关权限就能访问。// 搜索命中示例 const users = [{ name: '张三' }, { name: '李四' }, { name: '王五' }]; const keywords = '张三'; const hasMatch = users.some(user => user.name.includes(keywords)); // true2.3 filter() - 筛选过滤作用:创建一个新数组,其包含通过所提供函数实现的测试的所有元素。不会改变原数组。场景:数据筛选:根据条件过滤列表数据(如筛选出价格大于100的商品)。清理无效数据:过滤掉数组中的 null、undefined、空字符串等。数据分类:根据条件将原数组分成不同的子集。// 过滤空值示例 const strings = ["苹果", "", "香蕉", " ", "橙子", null]; const validStrings = strings.filter(s => s && s.trim()); // ["苹果", "香蕉", "橙子"]2.4 map() - 一对一映射作用:创建一个新数组,其结果是该数组中的每个元素调用一次提供的函数后的返回值。常用于数据转换。场景:接口数据适配:把后端返回的字段名(如 user_name)转成前端需要的(如 name)。渲染列表:把数据转成UI组件所需的格式。批量修改数组中的每一项。// 接口数据适配示例 const apiData = [{ user_name: '张三', user_age: 25 }]; const frontData = apiData.map(item => ({ name: item.user_name, age: item.user_age })); // 结果: [{ name: '张三', age: 25 }]2.5 reduce() - 万能汇总工具作用:对数组中的每个元素执行一个reducer函数(升序执行),将其结果汇总为单个返回值。reduce 是函数式编程中最强大的工具之一。参数:回调函数:(accumulator, currentValue) => newAccumulator初始值:initialValue,为累加器提供初始值。关键点:必须 return 新的累加器,否则下次迭代时累加器为 undefined。acc 可以是数字、对象、数组等任何类型。场景:聚合统计:求和、求平均值、统计出现次数。数据分组:把列表按某个字段分成不同的组。复杂的数据变换:数组转对象、多层数据处理。// 按类别分组并统计总价示例 const orderItems = [ { category: '食品', price: 20 }, { category: '服装', price: 100 }, { category: '食品', price: 30 } ]; const totalByCategory = orderItems.reduce((acc, item) => { acc[item.category] = (acc[item.category] || 0) + item.price; return acc; // 必须 return acc }, {}); // 初始值是一个空对象 {} // 结果: { 食品: 50, 服装: 100 }2.6 find() & findIndex() - 精准查找find():返回数组中第一个满足提供的测试函数的元素。找不到返回 undefined。findIndex():返回数组中第一个满足提供的测试函数的元素的索引。找不到返回 -1。与 indexOf 的区别:indexOf 只能用于简单值(如数字、字符串)的严格相等比较,而 find / findIndex 支持复杂的自定义判断条件。场景:根据ID查找特定的用户/数据项,或找到元素位置以进行后续的删除/更新操作。// 根据 ID 查找用户 const users = [{ id: 1, name: '张三' }, { id: 2, name: '李四' }]; const user = users.find(user => user.id === 2); // { id: 2, name: '李四' } const index = users.findIndex(user => user.id === 2); // 12.7 flat() & flatMap() - 数组扁平化flat(depth):按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组。depth 默认是1,可以传 Infinity 展开所有层级。flatMap():先执行 map,再对结果执行 flat(1),一步到位。场景:处理多层嵌套的数据结构。在 map 操作后直接扁平化,如将句子数组拆分成单词数组。// flatMap 示例:将句子拆分成单词 const lines = ["Hello world", "JavaScript is good"]; const words = lines.flatMap(line => line.split(' ')); // 结果: ["Hello", "world", "JavaScript", "is", "good"]2.8 includes() - 判断是否包含作用:判断一个数组是否包含一个指定的值,根据情况返回 true 或 false。用于简单值的判断。场景:检查用户角色、状态值等是否在允许的列表中。const roles = ['admin', 'editor']; const isAdmin = roles.includes('admin'); // true2.9 sort() - 排序(会修改原数组!)作用:对数组的元素进行排序,并返回原数组。注意:sort 方法会改变原数组。默认排序顺序是将元素转换为字符串,然后比较它们的UTF-16代码单元值序列。安全用法:使用 [...arr].sort() 先拷贝,再排序,避免修改原数组。数字排序:必须传入比较函数 (a, b) => a - b 来升序排序,(a, b) => b - a 来降序排序。const nums = [3, 1, 2]; const sortedNums = [...nums].sort((a, b) => a - b); // 新数组 [1, 2, 3] console.log(nums); // 原数组 [3, 1, 2],未改变2.10 reverse() - 反转数组(会修改原数组!)作用:反转数组中的元素,并返回原数组。同样会改变原数组。如需不可变操作,先拷贝。const arr = [1, 2, 3]; const reversedArr = [...arr].reverse(); // 新数组 [3, 2, 1]2.11 slice() - 截取数组作用:返回一个新的数组对象,这一对象是一个由 start 和 end 决定的原数组的浅拷贝(包括 start,不包括 end)。不会修改原数组。场景:提取数组的子集,或浅拷贝整个数组 slice(0) 或 slice()。const arr = [1, 2, 3, 4]; const newArr = arr.slice(1, 3); // [2, 3]2.12 splice() - 删除/插入/替换(会修改原数组!)作用:通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。会修改原数组。应尽量避免在复杂逻辑中直接使用,或用不可变方式替代。参数:(start, deleteCount, item1, item2, ...)const arr = [1, 2, 3]; const removed = arr.splice(1, 1); // 从索引1开始删1个,removed = [2], arr = [1, 3] arr.splice(1, 0, 99); // 在索引1处插入99,arr = [1, 99, 3]三、对象核心处理函数3.1 Object.keys() / values() / entries() - 对象遍历三剑客Object.keys(obj):返回一个由对象的键名组成的数组。Object.values(obj):返回一个由对象的值组成的数组。Object.entries(obj):返回一个由对象的键值对组成的二维数组,每个内部项是 [key, value]。场景:遍历对象的属性,替代过去的 for...in。把对象转成数组,方便使用 map、filter 等数组方法处理。渲染键值对形式的详情表格。// 对象过滤示例:只保留指定属性 const obj = { name: '张三', age: 25, gender: '男' }; const picked = Object.fromEntries( Object.entries(obj).filter(([key]) => ['name', 'age'].includes(key)) ); // 结果: { name: '张三', age: 25 }3.2 Object.fromEntries() - 键值对转回对象作用:把键值对列表(如 Map 或 entries 返回的二维数组)转换成一个对象。是 Object.entries() 的逆操作。场景:解析URL查询参数。过滤/转换对象的属性,先转成 entries 处理,再转回来。把 Map 转成普通对象。3.3 Object.assign() - 对象合并/浅拷贝作用:将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。场景:对象合并。在现代JavaScript中,更推荐使用展开运算符 ... 来实现,因为它更简洁、更直观。const obj1 = { a: 1 }; const obj2 = { b: 2 }; const newObj = Object.assign({}, obj1, obj2); // 等价于 const newObj = { ...obj1, ...obj2 }; // newObj: { a: 1, b: 2 }四、实用操作符与技巧4.1 可选链 ?. 与空值合并 ??可选链 ?.:当访问嵌套对象的属性时,如果中间某个属性不存在(是 null 或 undefined),不会报错,直接返回 undefined。语法:obj?.prop、obj?.[expr]、func?.()。空值合并 ??:只有当左边的值是 null 或 undefined 时,才返回右边的默认值。与 || 的区别在于,?? 不会把 0、''、false 这些假值当成无效值。场景:安全地访问深层嵌套属性,给表单字段设置默认值。const user = { profile: { name: '张三' } }; console.log(user?.profile?.age); // undefined,不会报错 console.log(user?.address?.city); // undefined const value = 0; const defaultValue = value || 'default'; // 'default',因为 0 是假值 const safeDefault = value ?? 'default'; // 0,因为 0 不是 null/undefined4.2 剩余运算符与展开运算符 (...)剩余运算符 (Rest):用在函数参数或解构赋值中,将剩余的参数或属性收集到一个数组或对象中。展开运算符 (Spread):用在数组或对象字面量中,将一个可迭代对象(如数组)或对象“展开”到新的数组或对象中。// 解构:收集剩余属性 const currentNode = { children: [], id: 1, name: 'root', type: 'folder' }; const { children, ...editMenuData } = currentNode; // editMenuData 为 { id: 1, name: 'root', type: 'folder' } // 对象合并 const obj1 = { a: 1 }; const obj2 = { b: 2, ...obj1 }; // { b: 2, a: 1 } // 数组去重 const arr = [1, 2, 2, 3]; const uniqueArr = [...new Set(arr)]; // [1, 2, 3]4.3 深拷贝与浅拷贝浅拷贝:只拷贝对象的第一层属性,如果属性值是引用类型,则拷贝的是其引用。方法:展开运算符 ...、Object.assign()。深拷贝:递归地拷贝对象的所有层级,生成一个完全独立的副本。structuredClone():现代浏览器和Node.js推荐的原生API,支持大多数类型(如 Date、Map、Set)。JSON.parse(JSON.stringify(obj)):简单粗暴,但只适用于纯数据对象(不含函数、undefined、循环引用等)。Lodash _.cloneDeep:工业级最稳方案。五、函数式编程高级实战5.1 函数组合 pipe / compose - 数据流水线函数组合是将多个简单函数组合成一个复杂函数,数据从左到右(pipe)或从右到左(compose)依次流过每个函数。这是函数式编程的核心思想,能极大提升代码的可读性和可维护性。// 从左到右执行(pipe,更符合人类阅读习惯,企业更常用) const pipe = (...fns) => (initValue) => fns.reduce((acc, fn) => fn(acc), initValue); // 从右到左执行(compose,经典函数式写法) const compose = (...fns) => (initValue) => fns.reduceRight((acc, fn) => fn(acc), initValue); // 实战场景:后端接口数据全链路处理 const apiData = [ { goods_id: 1, goods_name: '手机', price: 5000, status: 1 }, { goods_id: 2, goods_name: '充电器', price: 50, status: 1 }, { goods_id: 3, goods_name: '耳机', price: 0, status: 0 } // 无效数据 ]; // 步骤1:过滤有效数据(价格>0 且 状态=1) const filterValid = (list) => list.filter(item => item.price > 0 && item.status === 1); // 步骤2:字段映射(后端字段转前端字段) const mapField = (list) => list.map(({ goods_id: id, goods_name: name, price }) => ({ id, name, price })); // 步骤3:按价格排序 const sortByPrice = (list) => [...list].sort((a, b) => a.price - b.price); // 步骤4:分组统计 const groupByPriceRange = (list) => list.reduce((acc, item) => { const range = item.price < 100 ? '低价' : item.price < 500 ? '中价' : '高价'; acc[range] = [...(acc[range] || []), item]; return acc; }, {}); // 把所有步骤串成流水线,代码线性、可读性拉满 const processGoodsData = pipe(filterValid, mapField, sortByPrice, groupByPriceRange); // 直接使用 const result = processGoodsData(apiData); console.log(result); // 输出: { 高价: [ { id: 1, name: '手机', price: 5000 } ], 低价: [ { id: 2, name: '充电器', price: 50 } ] }5.2 柯里化 (Currying)柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数的技术。它允许你部分应用函数,预置一些参数。const curry = (fn) => (...args) => { if (args.length >= fn.length) { return fn(...args); } else { return curry(fn.bind(null, ...args)); } }; // 示例:一个计算价格的函数 const calcPrice = (price, tax, discount) => price * (1 + tax) * discount; const curriedCalc = curry(calcPrice); const priceWithTax = curriedCalc(100); // 预置价格100,返回一个新函数 const finalPrice = priceWithTax(0.1)(0.9); // 再传入税率0.1和折扣0.9,结果:99 console.log(finalPrice);六、异步编程高级实战6.1 带限流的异步并发控制 (Async Pool)解决批量请求并发过高导致服务端压力大或浏览器限制的问题。核心思想是始终维持最多 limit 个异步任务同时执行。/** * 异步任务并发控制 * @param {Array<Function>} tasks 返回 Promise 的任务函数数组 * @param {number} limit 最大并发数 * @returns {Promise<Array>} 所有任务的结果数组(按原顺序) */ const asyncPool = async (tasks, limit = 3) => { const results = []; const running = []; // 存放正在执行的 Promise for (const task of tasks) { // 包装任务,确保它是一个 Promise const promise = Promise.resolve().then(() => task()); results.push(promise); // 任务执行完后,从 running 数组中移除它 const finishPromise = promise.then(() => { const index = running.indexOf(finishPromise); if (index > -1) running.splice(index, 1); }); running.push(finishPromise); // 当并发数达到限制时,等待任意一个任务完成 if (running.length >= limit) { await Promise.race(running); } } // 等待所有任务完成 return Promise.all(results); }; // 使用示例 const createTask = (id, delay) => () => new Promise(resolve => { console.log(`任务 ${id} 开始`); setTimeout(() => { console.log(`任务 ${id} 结束`); resolve(id); }, delay); }); const tasks = [ createTask(1, 1000), createTask(2, 500), createTask(3, 800), createTask(4, 300), createTask(5, 200) ]; asyncPool(tasks, 2).then(results => console.log('所有任务结果:', results));七、数据处理与性能优化7.1 不可变数据更新 (Immutable Update)在React/Vue等框架开发中,直接修改状态可能导致视图不更新或难以追踪Bug。必须遵循“不修改原数据”的原则。改深层对象:用 ... 逐层展开复制。改数组元素:用 map,在需要修改的元素上返回新对象。删数组元素:用 filter,保留不需要删除的元素。加数组元素:用 [...arr, newItem]。// 原始状态 const state = { user: { name: '张三', profile: { age: 25, city: '北京' } }, posts: [{ id: 1, title: 'Post 1' }, { id: 2, title: 'Post 2' }] }; // 1. 修改深层属性 (user.profile.city 改为 '上海') const newState1 = { ...state, user: { ...state.user, profile: { ...state.user.profile, city: '上海' } } }; // 2. 修改数组元素 (将 id 为 2 的帖子标题改为 'New Title') const newState2 = { ...state, posts: state.posts.map(post => post.id === 2 ? { ...post, title: 'New Title' } : post ) }; // 3. 删除数组元素 (删除 id 为 1 的帖子) const newState3 = { ...state, posts: state.posts.filter(post => post.id !== 1) }; // 4. 添加数组元素 (在末尾添加新帖子) const newPost = { id: 3, title: 'Post 3' }; const newState4 = { ...state, posts: [...state.posts, newPost] };7.2 函数记忆化 (Memoization)缓存函数执行结果,对于相同参数,直接返回缓存值,避免重复计算。const memoize = (fn) => { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }; }; // 一个昂贵的计算函数 const expensiveFibonacci = (n) => { if (n <= 1) return n; return expensiveFibonacci(n - 1) + expensiveFibonacci(n - 2); }; const memoizedFibonacci = memoize(expensiveFibonacci); console.time('第一次'); memoizedFibonacci(40); // 耗时较长 console.timeEnd('第一次'); console.time('第二次'); memoizedFibonacci(40); // 瞬间返回,从缓存读取 console.timeEnd('第二次');7.3 全局错误捕获在生产环境中,需要捕获未处理的Promise异常和同步错误,防止应用崩溃,并向用户提供友好的提示或记录错误日志。// 捕获未处理的 Promise rejection window.addEventListener('unhandledrejection', (event) => { console.error('未捕获的 Promise 错误:', event.reason); // 在这里可以调用后端接口上报错误,或向用户展示错误提示 alert('系统出错了,请刷新页面重试。'); }); // 捕获未处理的同步错误 window.addEventListener('error', (event) => { console.error('未捕获的同步错误:', event.error); // 上报错误 }); // React 中的错误边界 (Error Boundary) 概念类似7.4 防抖 (Debounce) 与节流 (Throttle)防抖 (Debounce):在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。适用于输入框搜索。节流 (Throttle):规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。适用于滚动事件、resize事件。// 防抖函数 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // 节流函数 function throttle(func, limit) { let inThrottle; return function executedFunction(...args) { if (!inThrottle) { func(...args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }
2026年03月26日
4 阅读
0 评论
0 点赞
2026-03-24
JavaScript企业数据处理实用指南
在日常的企业级前端开发中,我们绝大多数时间都在和数据打交道:处理接口返回的列表、转换表单数据、优化高频事件、构建树形菜单... 熟练掌握这些数据处理技巧,能让你的代码更简洁、高效、易维护。本文将系统梳理企业开发中最常用的内置函数与实用算法,针对每一个知识点,都会从帮助理解、实战例子、如何记忆、什么时候用四个维度帮你彻底掌握。一、数组核心处理函数数组是前端数据处理最常用的数据结构,ES5 + 提供的一系列函数式方法,彻底改变了我们过去写for循环的模式。1.1 every:全真才为真帮助理解检查数组中所有元素是否都满足指定条件。只要发现有一个元素不满足,就会立即停止遍历,直接返回false;只有全部满足,才返回true。实战例子// 表单校验:检查所有输入项是否都通过了校验const formFields = [ { name: 'username', valid: true, value: '张三' }, { name: 'email', valid: true, value: 'zhangsan@example.com' }, { name: 'phone', valid: false, value: '123' }];// 只有所有字段都valid为true,表单才可以提交const isFormValid = formFields.every(field => field.valid);console.log(isFormValid); // false,因为phone字段校验失败// 权限判断:用户是否拥有访问某个功能的所有权限const requiredPerms = ['user:read', 'user:edit'];const userPerms = ['user:read', 'user:edit', 'admin'];const hasAccess = requiredPerms.every(perm => userPerms.includes(perm));console.log(hasAccess); // true如何记忆every的英文意思就是 “每一个”,你可以这么记:每一个都要满足条件,才算通过。就像期末考试,必须所有科目都及格,才算全部通过。什么时候用表单整体校验,判断所有字段是否合法权限校验,判断用户是否拥有全部所需权限批量数据的合规性检查1.2 some:一真即为真帮助理解检查数组中是否存在至少一个元素满足指定条件。只要找到第一个满足条件的元素,就会立即停止遍历,直接返回true;如果全部都不满足,才返回false。实战例子// 任务列表:判断是否有未完成的任务const tasks = [ { id: 1, name: '写文档', done: true }, { id: 2, name: '改Bug', done: false }, { id: 3, name: '开例会', done: true }];const hasPendingTask = tasks.some(task => !task.done);console.log(hasPendingTask); // true,说明还有未完成的任务// 搜索功能:判断列表中是否存在包含关键词的条目const keywords = '张三';const hasMatch = users.some(user => user.name.includes(keywords));如何记忆some的英文意思是 “一些、若干”,你可以这么记:只要有一些(哪怕一个)满足条件,就算通过。就像期末考试,只要有一科及格,就不算全挂科。什么时候用判断列表中是否存在未完成 / 异常的状态项快速判断搜索是否命中结果权限判断:只要有一个相关权限就能访问1.3 filter:筛选过滤,生成新数组帮助理解遍历数组,把所有满足条件的元素筛选出来,返回一个全新的数组。原数组不会被修改。实战例子// 过滤掉无效的空字符串const strings = ["苹果", "", "香蕉", " ", "橙子", null];const validStrings = strings.filter(s => s && s.trim());// 结果:["苹果", "香蕉", "橙子"]// React中瀑布流布局:筛选出偶数索引的图片放到左列const leftColumnImages = images .filter((_, index) => index % 2 === 0) .map(img => );如何记忆filter的意思就是 “过滤”,顾名思义,就是把符合条件的留下来,不符合的过滤掉。什么时候用数据筛选,比如根据条件过滤列表数据清理无效数据,过滤掉空值、无效项数据分类,把数组分成不同的子集1.4 map:一对一映射,生成新数组帮助理解对数组的每一个元素执行同一个转换函数,返回一个长度和原数组完全一致的全新数组。原数组不会被修改。实战例子// 把字符串数组转成数字数组const strNums = ['1', '2', '3', '4'];const numbers = strNums.map(Number);// 结果:[1, 2, 3, 4]// React中渲染列表:把用户数据转成JSX {users.map(user => ( {user.name} - {user.age} ))} // 接口数据适配:把后端返回的字段名转成前端需要的const apiData = [{ user_name: '张三', user_age: 25 }];const frontData = apiData.map(item => ({ name: item.user_name, age: item.user_age}));如何记忆map的意思是 “映射”,就是一一对应,原数组有多少个,新数组就有多少个,每个元素都做一次转换。什么时候用数据格式转换,比如字段名映射、类型转换渲染列表,把数据转成 UI 组件批量修改数组中的每一项数据1.5 reduce:累计器,万能的汇总工具帮助理解这是数组方法中最强大的一个,它可以把数组中的所有元素,通过一个累加函数,最终汇总成一个结果。这个结果可以是数字、对象、数组,任何类型都可以。它的两个核心参数:回调函数:(累加器acc, 当前元素curr) => 新的累加器累加器的初始值实战例子// 1. 基础求和const numbers = [1, 2, 3, 4];const sum = numbers.reduce((acc, curr) => acc + curr, 0);console.log(sum); // 10// 2. 按分类分组统计const orderItems = [ { category: '食品', price: 20 }, { category: '服装', price: 100 }, { category: '食品', price: 30 }];// 按分类统计总销售额const totalByCategory = orderItems.reduce((acc, item) => { acc[item.category] = (acc[item.category] || 0) + item.price; return acc;}, {});// 结果:{ 食品: 50, 服装: 100 }// 3. 数组扁平化const nestedArr = [[1,2], [3,4], [5,6]];const flatArr = nestedArr.reduce((acc, curr) => acc.concat(curr), []);// 结果:[1,2,3,4,5,6]如何记忆reduce的意思是 “归约、缩减”,就是把一个数组,缩减成一个最终的结果。你可以这么记:只要你需要把多个元素汇总成一个结果,就用 reduce。什么时候用聚合统计:求和、求平均值、统计次数数据分组:把列表按某个字段分成不同的组复杂的数据变换:比如数组转对象、多层数据处理函数式管道:把多个处理步骤串起来1.6 find & findIndex:精准查找帮助理解find:找到数组中第一个满足条件的元素,找不到返回undefinedfindIndex:找到数组中第一个满足条件的元素的索引,找不到返回-1和indexOf的区别是,它们支持自定义复杂的判断条件,而不是只能判断严格相等。实战例子const users = [{ id: 1, name: '张三' },{ id: 2, name: '李四' },{ id: 3, name: '王五' }];// 查找id为2的用户const targetUser = users.find(user => user.id === 2);console.log(targetUser); // { id: 2, name: '李四' }// 查找名字是王五的用户的索引const targetIndex = users.findIndex(user => user.name === '王五');console.log(targetIndex); // 2如何记忆顾名思义,find就是找元素,findIndex就是找元素的索引,找到第一个就停。什么时候用根据 ID 查找特定的用户 / 数据项查找符合特定条件的第一个元素找到元素的位置,用于后续的删除 / 更新操作1.7 flat & flatMap:数组扁平化帮助理解flat:把嵌套的数组展开,参数是展开的深度,传Infinity可以展开所有层级flatMap:相当于先执行map,再对结果执行flat(1),一步到位实战例子// 展开多层嵌套数组const nestedArr = [1, [2, [3, [4]]], 5];const flatArr = nestedArr.flat(Infinity);console.log(flatArr); // [1, 2, 3, 4, 5]// flatMap:处理每行的单词const lines = ["Hello world", "JavaScript is good"];const words = lines.flatMap(line => line.split(' '));// 结果:["Hello", "world", "JavaScript", "is", "good"]如何记忆flat就是 “拍平”,把嵌套的数组拍平成一维的。flatMap就是 map + 拍平,一步搞定。什么时候用处理后端返回的嵌套数组结构处理文本拆分,比如把多行文本拆成单词列表处理树形结构的扁平化数组方法核心对比表方法返回值是否短路典型用途forEachundefined❌执行副作用(打印、修改外部变量)everyboolean✅检查所有元素是否都满足条件someboolean✅检查是否存在至少一个满足条件的元素filter新数组❌筛选出符合条件的元素map新数组❌一对一转换每个元素reduce累积值❌汇总、分组、聚合统计find元素 /undefined✅查找第一个符合条件的元素findIndex索引 /-1✅查找第一个符合条件的元素的位置flat新数组❌展开嵌套数组二、对象核心处理函数除了数组,对象也是我们最常用的数据结构,ES6 + 提供了一系列非常实用的对象处理方法。2.1 Object.keys/values/entries:对象遍历三剑客帮助理解这三个方法把对象转换成数组,方便我们用上面的数组方法来处理:Object.keys(obj):返回对象的所有键名组成的数组Object.values(obj):返回对象的所有值组成的数组Object.entries(obj):返回对象的所有键值对组成的二维数组,每个项是[key, value]实战例子const user = {name: '张三',age: 25,role: 'admin'};// 遍历键名console.log(Object.keys(user)); // ["name", "age", "role"]// 遍历值console.log(Object.values(user)); // ["张三", 25, "admin"]// 遍历键值对,用来渲染表格const tableRows = Object.entries(user).map(([key, value]) => ( <td>{key}</td> <td>{value}</td>));如何记忆顾名思义,keys 拿键,values 拿值,entries 拿键值对。什么时候用遍历对象的属性,替代过去的for...in把对象转成数组,方便用数组的 filter、map 等方法处理渲染键值对形式的详情表格2.2 Object.fromEntries:键值对转回对象帮助理解它是Object.entries的逆操作,把一个键值对的二维数组,转回一个对象。实战例子// 1. 把URL参数转成对象const search = '?name=张三&age=25';const params = new URLSearchParams(search);const query = Object.fromEntries(params);console.log(query); // { name: '张三', age: '25' }// 2. 过滤对象的属性const obj = { a: 1, b: 2, c: 3, d: 4 };// 过滤掉值小于3的属性const filteredObj = Object.fromEntries( Object.entries(obj).filter(([key, value]) => value >= 3));console.log(filteredObj); // { c: 3, d: 4 }如何记忆fromEntries就是 “从 entries 来”,把 entries 的结果转回去。什么时候用解析 URL 查询参数过滤 / 转换对象的属性,先转成 entries 处理,再转回来把 Map 转成普通对象2.3 可选链?.与空值合并??帮助理解可选链?.:访问嵌套对象的属性时,如果中间某个属性不存在,不会报错,直接返回undefined空值合并??:只有当左边的值是null或undefined时,才返回右边的默认值,不会把0、''、false这些假值当成无效值实战例子// 可选链:避免层层判断const user = { profile: { address: { city: '北京' } } };// 传统写法const street = user.address && user.address.street;// 可选链写法const street2 = user.profile?.address?.street; // undefined,不会报错// 空值合并:精准的默认值const age = 0;// 错误写法:||会把0当成假值const wrongAge = age || 18; // 18,不对,0是合法的年龄// 正确写法:??只处理null/undefinedconst rightAge = age ?? 18; // 0,正确如何记忆?.就是 “如果前面的不存在,就别往下找了,直接返回 undefined”??就是 “只有当左边是空的(null/undefined),才用右边的默认值”什么时候用访问嵌套对象的深层属性,防止报错给表单字段设置默认值,避免覆盖合法的假值(0、false)安全的调用可能不存在的函数:api?.getData?.()三、企业级常用数据处理算法除了内置函数,企业开发中还有一些高频使用的自定义算法,用来解决特定的业务问题。3.1 防抖(Debounce):只认最后一次触发帮助理解防抖的核心逻辑是:触发事件后,等待 n 秒,如果 n 秒内没有再次触发,才执行函数;如果触发了,就重新计时。就像电梯:电梯门要关了,这时候有人进来,电梯就会重新倒计时关门,直到没人进来了,才真正关门。实战例子// 防抖函数实现function debounce(fn, delay = 300) { let timer = null; return function(...args) {// 每次触发,清除之前的定时器 clearTimeout(timer); // 重新设置定时器 timer = setTimeout(() => { fn.apply(this, args); }, delay);};}// 实战:搜索框输入联想function search(keyword) { // 调用接口搜索 console.log(搜索:${keyword});}// 包装成防抖函数,用户停止输入500ms后才执行搜索const debouncedSearch = debounce(search, 500);// 绑定输入事件input.addEventListener('input', (e) => { debouncedSearch(e.target.value);});如何记忆防抖,就是 “防止抖动”,用户连续的操作就像抖动,我们只认最后一次稳定的操作。什么时候用搜索框输入联想,避免每次输入都发请求按钮防重复点击,防止用户连续点击提交多次窗口resize事件,避免窗口大小改变时频繁触发重排滚动结束后触发的操作3.2 节流(Throttle):固定频率执行帮助理解节流的核心逻辑是:规定时间内,无论触发多少次,函数只执行一次。就像水龙头:不管你开多大,水都只能每隔固定时间滴一滴,不会因为你开的大就滴的更快。实战例子// 节流函数实现function throttle(fn, limit = 300) { let inThrottle = false; return function(...args) {// 如果不在冷却期,才执行 if (!inThrottle) { fn.apply(this, args); inThrottle = true; // 冷却期结束后重置 setTimeout(() => { inThrottle = false; }, limit); }};}// 实战:页面滚动懒加载function handleScroll() { // 检查滚动位置,加载新的内容 console.log('处理滚动...');}// 包装成节流函数,每200ms最多执行一次const throttledScroll = throttle(handleScroll, 200);window.addEventListener('scroll', throttledScroll);如何记忆节流,就是 “节制流量”,把高频的触发,稀释成固定频率的执行,保证不会太频繁。什么时候用页面滚动事件,比如懒加载、滚动位置监听鼠标移动事件游戏中的射击冷却高频的 DOM 事件处理3.3 扁平数组转树形结构帮助理解后端经常会返回一个扁平的列表,每个元素有id和parentId,前端需要把它转换成带children的树形结构,用来渲染菜单、部门树、分类树等组件。最优的实现是用 Map 做映射,时间复杂度 O (n),比递归的 O (n²) 高效很多。实战例子/**扁平数组转树形结构@param {Array} items 扁平数组@param {Object} config 配置,兼容不同的字段名 */function arrayToTree(items, config = {}) { const { id = 'id', pid = 'parentId', children = 'children' } = config; // 用Map存所有节点,方便快速查找 const nodeMap = new Map(); const result = [];// 先给每个节点加上children属性,并存到Map里 for (const item of items) {nodeMap.set(item[id], { ...item, [children]: [] });}// 遍历每个节点,找到它的父节点,把自己塞到父节点的children里 for (const item of items) {const node = nodeMap.get(item[id]); if (item[pid] === null || item[pid] === 0) { // 根节点,直接放到结果里 result.push(node); } else { // 找到父节点 const parent = nodeMap.get(item[pid]); if (parent) { parent[children].push(node); } }}return result;}// 实战:后端返回的部门列表const flatDepartments = [ { id: 1, name: '总公司', parentId: null }, { id: 2, name: '研发部', parentId: 1 }, { id: 3, name: '产品部', parentId: 1 }, { id: 4, name: '前端组', parentId: 2 }, { id: 5, name: '后端组', parentId: 2 },];const tree = arrayToTree(flatDepartments);console.log(tree);/* 结果:[ {id: 1, name: '总公司', parentId: null, children: [ { id: 2, name: '研发部', parentId: 1, children: [...] }, { id: 3, name: '产品部', parentId: 1, children: [] } ]}]*/如何记忆两步走:先把所有节点存到 Map 里,建立 ID 到节点的映射每个节点找自己的爸爸,把自己塞到爸爸的孩子里最后把没有爸爸的根节点拿出来什么时候用渲染 ElementUI/AntD 的树形组件构建系统菜单、权限菜单处理商品分类、部门组织架构3.4 深拷贝:完全复制对象帮助理解引用类型的赋值只是复制了引用,修改新对象会影响原对象。深拷贝会创建一个完全独立的新对象,无论嵌套多深,两个对象都完全隔离。我们需要处理特殊类型(Date、RegExp)和循环引用,避免 JSON 方法的缺陷。实战例子/**完整的深拷贝实现,支持循环引用、Date、RegExp等 */function deepClone(obj, map = new WeakMap()) { // 基本类型,直接返回 if (obj === null || typeof obj !== 'object') {return obj;}// 处理循环引用:如果已经拷贝过这个对象,直接返回缓存的副本 if (map.has(obj)) {return map.get(obj);}// 处理特殊对象类型 if (obj instanceof Date) {return new Date(obj.getTime());} if (obj instanceof RegExp) {return new RegExp(obj.source, obj.flags);}// 创建新的对象/数组 const cloneObj = Array.isArray(obj) ? [] : {}; // 缓存已经拷贝的对象 map.set(obj, cloneObj);// 递归拷贝每个属性 for (const key in obj) {if (obj.hasOwnProperty(key)) { cloneObj[key] = deepClone(obj[key], map); }}return cloneObj;}// 实战:编辑表单时,不修改原数据const originalUser = await api.getUserInfo();// 深拷贝一份,用来修改表单,不会影响原数据const formData = deepClone(originalUser);// 修改表单数据formData.name = '新名字';// originalUser不会被修改如何记忆递归遍历每个属性:基本类型直接返回特殊类型(Date、正则)单独处理用 WeakMap 存已经拷贝过的对象,防止循环引用对象 / 数组就递归继续挖什么时候用表单编辑,需要修改数据但不想影响原接口数据Redux/Vuex 中的不可变状态更新需要完全隔离两个对象的场景3.5 表格数据处理流水线企业后台系统中最常见的场景:对表格数据做过滤→排序→分页的流水线处理,用函数式的链式调用,一行搞定。实战例子function processTableData(data, options) { const { filterKey, sortKey, sortAsc, page, pageSize } = options; return data// 1. 过滤:模糊搜索名字 .filter(item => item.name.includes(filterKey)) // 2. 排序:根据指定字段排序 .sort((a, b) => { const diff = a[sortKey] - b[sortKey]; return sortAsc ? diff : -diff; }) // 3. 分页:截取当前页的数据 .slice((page - 1) * pageSize, page * pageSize);}// 使用const tableData = processTableData(rawData, { filterKey: '张', sortKey: 'age', sortAsc: false, page: 1, pageSize: 10});什么时候用后台管理系统的表格数据处理前端本地的列表筛选排序分页总结企业开发中的数据处理,核心就是用好原生的函数式方法,配合常用的通用算法,避免写大量的循环和判断。记住一个简单的选择口诀:想判断?every(全对)/some(有对的)想筛选?filter想转换?map想汇总?reduce想查找?find/findIndex高频事件?防抖(最后一次)/ 节流(固定频率)树形数据?扁平转树改对象怕影响原数据?深拷贝掌握这些,你就能应对 90% 以上的企业级数据处理场景了。
2026年03月24日
9 阅读
0 评论
0 点赞
2026-02-24
NestJS新手入门核心模块对比表笔记
NestJS新手入门核心模块对比表笔记说明:表格严格遵循NestJS官方文档定义,重点标注各模块「声明函数/接口」「核心用法」,适配快速记忆,所有接口、装饰器均与官方保持一致,如内容有问题可以联系作者修改,谢谢。模块名称官方核心定位声明函数/核心接口(必填)接口参数/核心方法官方核心装饰器执行时机官方核心作用Controllers (控制器)请求处理入口,负责接收客户端请求并返回响应无强制接口,通过「类+装饰器」声明请求装饰器参数(路由路径、请求方法);处理方法接收请求参数@Controller()、@Get()、@Post()、@Param()、@Body()、@Query()请求进入后,中间件之后路由映射、请求接收、响应返回Providers (提供者)可注入的依赖单元,封装业务逻辑(核心为Service)无接口,通过@Injectable()标记类即可声明自定义业务方法(如create、findAll),支持构造函数注入@Injectable()(必填,标记为可注入)被依赖组件(如Controller)调用时封装业务逻辑、解耦、支持依赖注入Modules (模块)应用组织单元,管理模块内组件及依赖关系无接口,通过@Module()装饰器声明类@Module()配置对象(providers、controllers、imports、exports)@Module()(必填)、@Global()(可选,全局模块)应用启动时加载划分功能边界、管理依赖导入导出、组件共享Middleware (中间件)请求处理流程中的中间层,可拦截请求/响应类式:implements NestMiddleware;函数式:直接声明函数use(req: Request, res: Response, next: NextFunction)(类式/函数式通用)@Injectable()(仅类式中间件必填)请求进入最前端,控制器之前日志记录、请求验证、跨域处理等通用逻辑Exception Filters (异常过滤器)统一处理应用中未捕获的异常,格式化异常响应implements ExceptionFiltercatch(exception: any, host: ArgumentsHost)(必填方法)@Injectable()(必填)、@Catch()(必填,指定捕获的异常类型)发生未捕获异常时统一异常响应格式、自定义异常处理逻辑Pipes (管道)处理控制器方法参数,实现参数验证、转换implements PipeTransform<T, R>(T:输入类型,R:输出类型)transform(value: T, metadata: ArgumentMetadata)(必填方法)@Injectable()(必填)控制器方法调用前,参数解析后参数验证、参数类型转换、数据格式化Guards (守卫)请求授权控制,决定请求是否能进入控制器方法implements CanActivatecanActivate(context: ExecutionContext)(必填方法,返回布尔值)@Injectable()(必填)中间件之后,管道之前权限校验、角色控制、请求合法性判断Interceptors (拦截器)面向切面编程,拦截控制器方法执行前后逻辑implements NestInterceptor<T, R>(T:输入,R:输出)intercept(context: ExecutionContext, next: CallHandler)(必填方法)@Injectable()(必填)管道之后,控制器方法执行前后日志记录、响应映射、异常映射、请求/响应拦截Custom decorators (自定义装饰器)封装装饰器逻辑,扩展Nest装饰器功能参数装饰器:createParamDecorator;装饰器组合:applyDecoratorscreateParamDecorator((data, ctx) => { ... });applyDecorators(多个装饰器)无必填装饰器,可组合官方装饰器使用被装饰的类/方法/参数初始化时简化代码、复用装饰器逻辑、扩展官方功能
2026年02月24日
9 阅读
0 评论
0 点赞
2026-02-24
NestJS 自定义 Provider
一、依赖注入(DI)基础依赖注入是控制反转(IoC) 技术,将依赖的实例化交给 Nest 运行时容器,而非手动代码创建。IOC 容器就是 Nest 负责 “接管依赖实例化” 的核心,依赖注入(DI)是 IOC 思想在 Nest 里的具体实现方式。工作三步流程标记可注入@Injectable() 装饰器 → 声明类可被 Nest IoC 容器管理。声明依赖控制器构造函数 constructor(private service: XxxService) → 声明依赖。注册提供者模块 @Module 的 providers 数组 → 绑定 Token 与实现类。容器行为实例化时自动解析依赖,递归处理依赖链(自底向上)。默认单例:创建 → 缓存 → 复用。二、标准 Provider(简写)providers: [CatsService]等价完整写法:providers: [ { provide: CatsService, useClass: CatsService, } ]provide:Token(注入令牌)useClass:实际实例化的类三、自定义 Provider 五种用法1. 值提供者:useValue注入常量、外部库、模拟对象。适合测试替换真实服务。{ provide: CatsService, useValue: mockCatsService }2. 非类 Token(字符串 / Symbol / 枚举)用字符串做令牌,配合 @Inject() 使用。{ provide: 'CONNECTION', useValue: connection }注入:constructor(@Inject('CONNECTION') connection: Connection) {}3. 类提供者:useClass动态切换实现类(如开发/生产环境)。{ provide: ConfigService, useClass: process.env.NODE_ENV === 'development' ? DevelopmentConfigService : ProductionConfigService }4. 工厂提供者:useFactory(最灵活)动态创建实例,可注入其他依赖。inject 数组 → 按顺序传入工厂函数参数。{ provide: 'CONNECTION', useFactory: (optionsProvider: MyOptionsProvider) => { return new DatabaseConnection(optionsProvider.get()); }, inject: [MyOptionsProvider] }支持可选依赖:inject: [{ token: 'OptionalProvider', optional: true }]5. 别名提供者:useExisting给已有 Provider 起别名,指向同一个单例。{ provide: 'AliasedLogger', useExisting: LoggerService }四、导出自定义 ProviderProvider 默认模块内可见,跨模块需导出:// 按 Token 导出 exports: ['CONNECTION'] // 或完整对象导出 exports: [connectionFactory]五、核心总结@Injectable() → 可被容器管理provide → 唯一 Token五大实现:useValue:常量/对象useClass:动态类useFactory:动态工厂(支持依赖)useExisting:别名类简写:providers: [Service]跨模块使用必须 exports
2026年02月24日
7 阅读
0 评论
1 点赞
2026-02-24
NestJS 内置 Pipes笔记
NestJS 内置 Pipes笔记一、概述Nest 内置 Pipe 均从 @nestjs/common 导出,用于参数验证、类型转换、默认值等场景,可直接在控制器路由、DTO、全局配置中使用。二、内置 Pipe 清单与作用1. 验证类1.1 ValidationPipe作用:最常用的全局验证管道,配合 class-validator + class-transformer 对 DTO 进行自动校验。场景:校验请求体、查询参数、路径参数的合法性。常用配置:whitelist: true:过滤掉非 DTO 定义的字段forbidNonWhitelisted: true:禁止传入多余字段transform: true:自动将原始值转为 DTO 对应类型2. 基础类型转换2.1 ParseIntPipe作用:将参数转为整数,转换失败抛出 400 Bad Request。场景:id、page、limit 等数字路径参数。2.2 ParseFloatPipe作用:转为浮点数。场景:价格、经纬度、小数类参数。2.3 ParseBoolPipe作用:转为布尔值,支持 true/false、1/0、'true'/'false'。场景:开关、状态查询参数。3. 格式/结构校验3.1 ParseUUIDPipe作用:校验参数是否为合法 UUID(v4 等),不合法则报错。场景:用户 ID、订单号等 UUID 格式参数。3.2 ParseEnumPipe作用:校验参数是否属于指定枚举。场景:状态、类型、固定选项参数。3.3 ParseArrayPipe作用:将字符串转为数组,可指定分隔符、校验每项类型。场景:批量 ID、多选参数 ?ids=1,2,3。3.4 ParseDatePipe作用:将字符串/时间戳转为 Date 对象,非法日期报错。场景:开始时间、结束时间等日期参数。4. 文件与默认值4.1 DefaultValuePipe作用:为参数提供默认值,不与其他 Pipe 冲突。场景:分页默认 page=1、limit=10。4.2 ParseFilePipe作用:上传文件校验,可校验文件类型、大小、数量等。场景:单文件/多文件上传校验。三、典型使用方式(极简示例)1. 路由参数直接使用@Get(':id') getOne(@Param('id', ParseIntPipe) id: number) {}2. 带默认值@Get() getList( @Query('page', ParseIntPipe, new DefaultValuePipe(1)) page: number, ) {}3. 枚举校验@Get('type/:type') getByType( @Param('type', new ParseEnumPipe(StatusEnum)) type: StatusEnum, ) {}4. 全局启用 ValidationPipe(推荐)// main.ts app.useGlobalPipes( new ValidationPipe({ transform: true, whitelist: true, }), );四、使用总结校验 + 转换:统一由 Pipe 完成,控制器代码更干净。失败自动抛错:返回标准 400 错误,无需手动判断。可组合:多个 Pipe 可按顺序链式使用。
2026年02月24日
3 阅读
0 评论
0 点赞
1
2