尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
JavaScript全栈开发必学:字符串、数组、异步与Excel处理的实用技巧
全栈开发学到JavaScript这一层很多人会突然觉得“飘”了前面HTML和CSS写出来的东西是静态的页面上的按钮点了没反应逻辑只能停留在猜。而JS一进来表单校验、接口请求、页面渲染更新全部活了连报错都开始变得“人性化”——控制台里一堆看不懂的红字其实就是它在你熟悉它之前跟你打的招呼。这篇文章是“全栈开发学习-JS”系列的第5篇适合已经看完基础语法、却还没能把这些语法真正串起来做事的读者。我会从实际开发里最常用的几个JS能力切入字符串是否包含的判断、忽略大小写的坑、数组排序/合并/去重、循环里最容易被误解的forEach、函数与执行上下文、事件循环再到一个真实项目里用得上的xlsx工具库。内容不追求覆盖整个语言而是把“你大概率会用到、且容易踩坑”的部分讲透。1. 字符串处理判断包含、忽略大小写这两件事1.1 判断字符串是否包含的四种方式“判断一个字符串里有没有另一个字符串”这是搜索框、筛选逻辑、接口数据匹配里最最常见的需求。JS里能做这件事的API有好几个但它们的性格完全不同。最常用的是includes()。它返回布尔值语义直白代码读起来像在讲人话const title 全栈开发学习路线; console.log(title.includes(开发)); // true console.log(title.includes(后端)); // false这个方法是ES6引入的兼容性在现代浏览器和Node环境里完全不用担心。它还能接收第二个参数作为起始搜索位置比如title.includes(开发, 3)表示从索引3开始往后找。老一点的项目里更常见的是indexOf()。它和includes的功能类似但返回值是位置索引如果找不到就返回-1。很多人写判断时容易踩一个潜意识坑if (title.indexOf(开发)) { // 这里的写法是错的 }为什么错因为indexOf找不到时返回-1而-1在布尔判断里是true。也就是说这段代码在“不包含”的时候反而会进入 if 分支。正确写法是显式判断if (title.indexOf(开发) ! -1) { // 找到了才进来 }如果你用过search()它也是返回索引但接受正则表达式而match()则是返回匹配结果的数组或null。三者各有用处判断是否包含的场景下includes最省心需要配合正则做模糊匹配时再考虑search和match。1.2 忽略大小写统一转换还是正则忽略大小写的本质是“把两边都变成同一种形态再比较”。新手最容易想到的是toLowerCase()大法const keyword javascript; const target JavaScript; console.log(target.toLowerCase() keyword.toLowerCase()); // true这种做法简单直接用toLowerCase而不是toUpperCase的原因在于对某些欧洲字符来说小写转换的意外更少比如土耳其字母 İ 的坑日常业务里很少遇到但知道这个点会让你显得很资深。另一个常见场景是“判断字符串是否包含同时忽略大小写”。Regular expression 在这时候更优雅const content Learn JavaScript Step By Step; const reg new RegExp(javascript, i); console.log(reg.test(content)); // truei就是 ignore case 修饰符。这里要提醒一句如果正则表达式的内容是用户输入的变量一定要先做转义比如new RegExp(escapeRegExp(keyword), i)否则用户输入一个点号.会匹配任意字符造成逻辑错误。很多跟搜索相关的小Bug都是这么来的。提示判断字符串包含优先考虑includes需要忽略大小写时能统一转小写就统一转小写别一边转小写一边转大写逻辑容易乱。正则只在你确实需要匹配模式时才引入。2. 数组操作排序、合并、去重还有那个打断不了的forEach2.1 排序的几种方法与比较器原理数组的sort()防不胜防。它默认把元素当成字符串按字典序排序所以[10, 9, 100]排序之后可能是[10, 100, 9]。原因很简单数字被转成了字符串10、100、9按字符顺序排就成了这样。正确的做法是传入比较器函数。比较器的规则是返回负数a排前面返回正数b排前面返回0两者保持顺序。const nums [10, 9, 100, 3, 25]; nums.sort((a, b) a - b); // 升序 [3, 9, 10, 25, 100] nums.sort((a, b) b - a); // 降序 [100, 25, 10, 9, 3]对象数组排序也遵循同一套规则。比如按用户年龄升序const users [ { name: 小明, age: 23 }, { name: 小红, age: 19 }, { name: 小刚, age: 31 }, ]; users.sort((a, b) a.age - b.age);字符串数组的中文排序有个隐藏坑。直接用sort()如果元素都是中文字符它按 Unicode 编码排序结果常常不符合拼音直觉。想让中文按拼音排需要localeCompareconst names [张三, 李四, 王五, 赵六]; names.sort((a, b) a.localeCompare(b, zh-Hans-CN));另一个细节sort是原地排序会修改原数组。不想改动原数组就先slice()复制一份再排const sorted [...arr].sort((a, b) a - b);2.2 数组合并与去重写法很多关键是选对数组合并通过concat或展开运算符都能做到const a [1, 2, 3]; const b [4, 5, 6]; const merged a.concat(b); const merged2 [...a, ...b];两者的区别很微妙concat是数组自带方法[...a, ...b]更通用适合在对象字面量或参数传递里灵活拼接。如果数组里嵌套了一层展开运算符只会展开一层这也算一个常识点。去重最简单且性能上限高的方式是Set这是ES6提供的新集合类型天然保证元素唯一const arr [1, 2, 2, 3, 4, 4, 5]; const unique [...new Set(arr)]; // [1, 2, 3, 4, 5]这套方案可以处理数字、字符串、布尔值这类原始类型。但如果数组里是对象Set就失效了因为每个对象的引用都不相同。对象去重需要按某个唯一键来过滤常见写法是用Map或reduceconst items [ { id: 1, name: A }, { id: 1, name: B }, { id: 2, name: C }, ]; const map new Map(); items.forEach((item) map.set(item.id, item)); const deduped [...map.values()];这个写法利用Map的键唯一性后出现的同 id 对象会覆盖先出现的最终拿到的是每个 id 对应的最后一个对象。如果你想保留第一个判断map.has(item.id)再写map.set即可。2.3 为什么forEach“中断”不了应该用什么替代很多人在forEach里想提前结束循环写了return或break结果一脸懵。先说结论forEach对每个元素都会调用一次回调函数return只是退出当前这一次回调相当于跳过当前元素不是结束整个循环而break在forEach里直接语法报错。[1, 2, 3, 4].forEach((n) { if (n 2) return; // 只是跳过2后面3、4还会执行 });既然要中断就该选一个支持中断的循环方式。for...of配合break最直观for (const n of [1, 2, 3, 4]) { if (n 2) break; console.log(n); // 只输出1 }另一个思路是用some()。它的语义是“是否存在一个元素满足条件”回调返回true时循环会提前终止[1, 2, 3, 4].some((n) { console.log(n); // 输出1、2 return n 2; });同理every()在回调返回false时终止。理解了这个特性之后很多“查找并提前退出”的需求都可以优雅地交给some既简洁又不会产生副作用。3. 函数与执行上下文从作用域到事件循环3.1 声明方式与箭头函数的区别JS里写函数至少有三种常见姿势函数声明、函数表达式、箭头函数。它们在前置调用、this指向、arguments是否存在上各有不同。function foo() {} // 函数声明会被提升 const bar function() {}; // 函数表达式不会被提升 const baz () {}; // 箭头函数函数声明因为有“函数提升”你可以在声明之前调用它这在模块化组织代码时很方便。函数表达式和箭头函数则必须定义之后再使用否则会报错。箭头函数和传统函数最大的区别是this。传统函数的this取决于“怎么调用”箭头函数的this取决于“在哪定义”它是词法绑定的也就是说它沿用了外层作用域的this。举一个实际例子按钮点击事件里需要延迟修改某个状态。const obj { count: 0, handleClick() { setTimeout(function () { this.count; // 这里的 this 指向 window / undefined }, 100); }, };如果用箭头函数const obj { count: 0, handleClick() { setTimeout(() { this.count; // 这里的 this 指向 obj }, 100); }, };这块是新手写交互逻辑时最常见的困惑之一。我的建议是回调场景优先用箭头函数需要动态改变this的场景再用普通函数。3.2 执行上下文与闭包的直观理解“执行上下文”听起来很抽象其实它就是代码运行时的环境信息集合包含变量对象、作用域链、this指向。每进入一个函数就会创建一个新的执行上下文JS引擎把这些上下文压入调用栈函数返回时再弹出。闭包则是“函数 它诞生的作用域”。比如function createCounter() { let count 0; return function () { count; return count; }; } const counter createCounter(); console.log(counter()); // 1 console.log(counter()); // 2counter引用了createCounter内部的count即便createCounter已经返回count依然存活。这就是闭包保存变量的机制。它在数据私有化、防抖节流、函数工厂场景里都很常用但同时也会拦截内存让变量不能及时回收。实际开发中不要在闭包里挂太大对象、不要无脑给事件回调绑闭包页面卡顿往往和这有关。3.3 事件循环与异步为什么setTimeout不一定按顺序JS是单线程语言却能做到异步处理核心就是事件循环。简单说同步代码先执行遇到定时器、网络请求这类异步操作时它们会在完成后被放到任务队列里等当前调用栈清空后再被执行。console.log(A); setTimeout(() console.log(B), 0); console.log(C);输出顺序是 A、C、B。因为setTimeout的回调被放进了宏任务队列即使延迟是0也要等同步代码跑完。这个现象第一次遇到时很容易困惑多看几次就习惯了。如果牵扯到 Promise那还要区分微任务和宏任务。微任务优先于宏任务执行console.log(A); Promise.resolve().then(() console.log(B)); setTimeout(() console.log(C), 0); console.log(D); // 输出 A D B C原因很简单微任务队列在当前调用栈结束后立即被清空宏任务队列要等下一次事件循环。理解这点调试一些复杂的加载顺序问题会快很多。4. 运算符、类型判断与容易被忽略的语法细节4.1??与||默认值到底该用哪个||在JS里是逻辑或它不只是返回布尔值而是返回第一个“真值”。所以常见写法const port config.port || 3000意味着如果config.port是0、空字符串、false、NaN这些假值都会被替换成默认值。这里有个隐藏Bug如果端口本来就应该配置为0或者某个字段合法值就是空字符串||会把合法值误判为缺省。ES2020 引入的??空值合并运算符就是来解决这个问题的它只在左边是null或undefined时才会取右边const port1 config.port ?? 3000; // 0 会保留null/undefined 才用 3000 const port2 config.port || 3000; // 0 会被替换成 3000我建议所有“默认值”场景优先用??只有你确实想把所有假值兜底时才用||。4.2 条件与循环语句的通用写法以及switch的意外条件语句if/else、switch、三元运算符各自有适用场景。if/else最灵活适合复杂条件组合三元运算符适合二选一的简单赋值switch适合单变量多值判断。switch有一个容易忽略的细节它内部是比较用的是严格相等。这意味着switch (1)永远不会匹配1。另外别漏写break否则会穿越到下一个case这种“fall-through”行为有时候会被故意用来做多条件合并但大多数时候是Bug。循环方面for适合需要计数索引的场景while适合循环次数不明确、依赖条件的场景for...of适合遍历数组和可迭代对象。for...in则更多用于对象键名遍历但会把继承来的可枚举属性也遍历出来配合hasOwnProperty判断更稳妥。4.3 类型判断与URL有效性验证类型判断的经典三四招typeof判断基本类型比如typeof 1 number但typeof null返回object这是历史Bug。Array.isArray()判断数组因为typeof []也是object。Object.prototype.toString.call()是更通用的方案拿到的结果是[object Array]、[object Object]这种格式。instanceof判断一个对象是否属于某个构造函数的实例跨iframe、跨Window时会失效。关于“验证URL有效性”网上一搜一大把正则但很多都不完整。最省心的方式是利用URL构造函数function isValidUrl(str) { try { new URL(str); return true; } catch { return false; } } console.log(isValidUrl(https://example.com)); // true console.log(isValidUrl(example.com)); // false因为没有协议头要注意的是new URL(example.com)会抛错要求必须是绝对地址。如果业务上希望允许不带协议的输入就得先给字符串补上https://。同时URL构造函数只做语法级验证不保证这个域名真的存在、服务器真的能访问。4.4escape/unescape与encodeURIComponent被遗忘的历史API在很老的JS代码里能看到escape和unescape它们会对字符串做转义用%加上十六进制表示特殊字符。但这两个函数属于“半退休”状态官方也不推荐再使用因为它们对非ASCII字符的处理方式和现在推荐的标准不一致。现在的做法是用三兄弟encodeURI保留URI整体结构适合编码整个URL不会把/:?这些分隔符转掉。encodeURIComponent编码URL中的参数值会把/:?全部转义适合拼在查询字符串里。对应的解码方法是decodeURI和decodeURIComponent。举个例子往URL里拼中文参数时如果用了encodeURI中文会被转但不会容易把参数截断用encodeURIComponent则安全const keyword JS 全栈开发 Vue; const url https://api.example.com/search?kw encodeURIComponent(keyword); // https://api.example.com/search?kwJS%20%E5%85%A8%E6%A0%88%E5%BC%80%E5%8F%91%20%26%20Vue提示接手老项目时看到escape/unescape不要照抄应尽快替换为encodeURIComponent/decodeURIComponent否则处理中文、Emoji 时会出乱码。5. 实际工程里的JS工具库用xlsx读Excel5.1 为什么要引入xlsx而不是手写解析全栈开发经常会遇到“用户上传Excel后端解析数据入库”的需求。有些新手想自己手写解析.xlsx别想了xlsx文件本质上是一个 ZIP 包里面装着多段 XML格式结构相当复杂手写解析不仅工作量大还很容易踩中文编码和单元格样式的坑。成熟的解决方案是SheetJS社区版通常以xlsx作为包名引入。它支持读取.xlsx、.xls、.csv等格式并且同时能在浏览器端和Node端使用。前端的场景里用户通过input typefile选择文件后我们直接读文件内容并解析出来后端的场景则通常是接收上传的文件流之后再做处理。5.2 快速读取Excel的两步走第一步拿到文件内容后调用XLSX.read()解析成工作簿对象。在这个过程里文件内容可以是 ArrayBuffer、File 对象或 Base64 字符串只要传入符合要求的形式库内部会帮我们把格式差异处理掉。第二步通过工作簿对象拿到想读的 sheet再调用XLSX.utils.sheet_to_json()转成JSON数组后续的数据校验、过滤、入库就很轻松了。下面给一段浏览器端的最小实现代码import * as XLSX from xlsx; document.getElementById(fileInput).addEventListener(change, (event) { const file event.target.files[0]; if (!file) return; const reader new FileReader(); reader.onload (e) { const data new Uint8Array(e.target.result); const workbook XLSX.read(data, { type: array }); const firstSheetName workbook.SheetNames[0]; const worksheet workbook.Sheets[firstSheetName]; const rows XLSX.utils.sheet_to_json(worksheet, { defval: }); console.log(rows); }; reader.readAsArrayBuffer(file); });这里有两个细节值得展开。type: array告诉库传入的内容是二进制数组sheet_to_json的第二个参数里defval: 表示空单元格用什么值填充不加这个参数时空单元格会被跳过行里会出现缺字段的情况非常容易在入库时导致undefined混进数据。rows出来之后每一行是一个对象键是表头文字值是单元格内容。比如Excel第一行是“姓名、年龄、城市”那rows[0]就是{ 姓名: 张三, 年龄: 25, 城市: 北京 }。这个结构对后续的字段映射、校验非常友好直接Array.map一波就能转换成接口需要的参数。5.3 新手容易踩的几个坑读取Excel看着简单但实际用得多了会发现不少坑。第一个坑是日期单元格。用XLSX.read读取日期时得到的结果经常是数字看起来像是乱码。这是Excel内部存储日期的机制解决方案是给sheet_to_json传raw: false让库帮你按单元格格式转换格式化的显示值或自己手动对数字做对应转换简单说就是借助处理后再使用。第二个坑是合并单元格。合并后的单元格只有左上角有值其他区域是空的。如果业务要求每个单元格都要有值就得自己写逻辑去“填充”合并区域或者使用!merges信息来识别合并区间。这个属于进阶需求但提前知道能省不少排查时间。第三个坑是文件格式。xlsx库能读.csv但.csv的字符编码多样尤其是中文环境下的GBK编码用type: string读取时容易解码乱码。稳妥做法是拿到二进制后先用TextDecoder(gbk)转成字符串或者建议用户统一使用.xlsx格式。第四个坑是.xlsx包里可能存在宏、外部链接、图表等附加内容XLSX.read默认会忽略这些部分对普通数据读取没有影响但如果你做的是“读取完再原样保存”的功能这些内容可能会丢失。所以“读-改-存”的完整链路一定要做兼容测试别等到上线才发现用户提交的Excel被“洗”得干干净净。6. 常见问题与排查技巧速查表下面这张表是我在实际学习和带新人过程中整理出来的JS高频问题集。问题本身的报错样式五花八门但根因往往集中在表格右侧的这几类。现象根本原因推荐排查方式includes报错不是函数浏览器环境老不支持ES6 API用indexOf ! -1兼容或引入 polyfill数字排序结果诡异sort()默认按字符串排序传入比较器(a, b) a - bforEach里return没效果回调返回不影响循环改用for...ofbreak或some()NaN、undefined传到接口数组元素缺省或类型不匹配先Array.filter(Boolean)再Array.map转换函数里的this不是预期的对象普通函数的this取决于调用方式改成箭头函数或bind绑定setTimeout输出顺序不符合直觉异步任务要等同步代码执行完理解事件循环设计时避免依赖执行顺序new Date(2024-02-30)结果不对JS会做日期自动纠偏自己校验年月日合法性读取Excel日期变成数字Excel内部日期是序列号sheet_to_json传{ raw: false }这里额外补充一个调试习惯遇到JS的诡异行为先在控制台里console.log打印变量的实际值而不是盯着代码“人肉执行”。比如判断字符串是否包含先打印target.includes(keyword)、target.toLowerCase()你会发现90%的玄学问题都源于类型或大小写不一致。这个小习惯比背任何API都实用。7. 我的实操体会学JS这关怎么才算过我自己带过不少准备入行全栈的新人发现一个共性很多人语法书看完了一到写代码就卡在“不知道该调哪个API”。这不是理解能力的问题而是JS这门语言里历史遗留和版本迭代叠加了太多API学的时候容易抓不住主线。我的建议很简单先练熟字符串、数组、对象、函数这四类基础能力的常用API再遇到复杂问题往“搜索断点调试”方向走不要硬背每个API的所有参数。另一个经验是学JS最容易“眼高手低”的地方是异步。同步代码怎么绕都能跑通异步一来加载顺序、渲染时机、数据竞争全得靠实践去体会。我当时是找一个真实的小项目比如做一个搜索筛选列表、一个导入Excel解析预览的页面把字符串提取、数组过滤、异步请求、事件绑定全部用上跑通一遍就通了。最后再分享一个小技巧遇到不确定某个API是怎么实现的直接在console里敲一遍再把它的返回值打印出来看。JS这门语言最“友好”的地方就在于它给你机会去实验。只要愿意反复试、反复看控制台报错你就已经把全栈开发路上最需要的那种“排错直觉”给练出来了。
RELATED

相关推荐

RK3568/RK3588 Android平台RKNN部署实战:避坑指南与性能调优

RK3568/RK3588 Android平台RKNN部署实战:避坑指南与性能调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/28 13:37:24
STM32电子宠物狗:实时交互系统设计实战

STM32电子宠物狗:实时交互系统设计实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/28 13:37:24
在Dify中实现Hindsight机制:为LLM应用打造事后回看与复盘能力

在Dify中实现Hindsight机制:为LLM应用打造事后回看与复盘能力

不用太紧张,这篇文章我按自己的实际经验来写,先把话说在前面:hindsight 这个名字听起来像某个模型或者论文的代号,但落到日常开发里,它其实是一类很实用的设计思路——让 AI 应用具备“事后回看”的能力。我这一两年在…

📅 2026/9/28 13:37:24
MORE NEWS

更多资讯

📰

Kubernetes 上构建 Agentic 工作负载的运行时调度层:ax 调度设计与实践

1. 从“ax”这个标题说起:一个被低估的运行时调度命题第一次看到“ax”这个标题,很多人会以为是某个命令行工具的缩写,或者某个内部代号。但把热搜词摊开来看——ax、agentic、orchestration、runtime、Kubernetes——这几个词凑在一起&#…

📰

基于Kubernetes的Agentic运行时编排:ax调度与池化实践

1. 从“ax”这个标题说起:一个被低估的运行时编排切口第一次看到“ax”这个标题,很多人会以为是某个命令行工具的缩写,或者某个前端框架的别名。但把热搜词摊开来看——agentic、orchestration、runtime、Kubernetes、ax调度、agentic rag、c…

📰

Kubernetes 上 agentic 工作负载的运行时编排层设计与实践

1. 从“ax”这个标题说起:一个被低估的运行时编排切口“ax”这个标题乍看像某个命令行工具的缩写,但结合 agentic、orchestration、runtime、Kubernetes 这组关键词,它指向的其实是一个很具体的问题域:在 Kubernetes 之上&#xf…

📰

手机摄像头模组拆解:Lens、VCM、CMOS与DSP的协同原理

直接说结论:手机摄像头模组远没有大家想象中那么"封闭"。拆开一颗主摄,你看到的不是一块黑盒子,而是一条精密的光学电学算法流水线。这条流水线浓缩了四个核心角色——Lens(镜头)、VCM马达、CMOS图像传感器、…

📰

ax基础设施层:跨语言Agent调度的运行时契约与排错实践

1. “ax”不是缩写,而是一个正在成型的基础设施层代号最近在几个开源社区和内部技术分享会上,频繁看到“ax”这个词被单独拎出来讨论——不是作为某个单词的缩写(比如access、axis、acceleration),也不是项目代号里的随…

📰

Cursor Agent成本优化:五处脚手架改动拆解,token消耗降7%

先说结论:Cursor 的 Agent 模式把单次任务的 token 消耗降了大概 7%,靠的不是换更便宜的模型,而是把 Agent 运行时候的“脚手架”重新捋了一遍。这个数看起来不大,但对天天挂 Agent 跑重构、批量改代码的人来说,攒下来…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬