ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JavaScript学习第五天避坑笔记:从报错到工程化的关键知识点

2026/9/9 19:28:43 拓冰建站 浏览量
JavaScript学习第五天避坑笔记:从报错到工程化的关键知识点 学JavaScript到第五天我已经不再满足于控制台里打印hello world了。这一天手机里的搜索记录几乎被同一个开头的词塞满javascript运行时报错、javascript函数、javascript:void(0)、vue javascript项目、为什么elmessage还是提示未定义……每一个热搜词背后都是一个今天踩过的坑。如果你也刚开始学JS或者刚把一个JS项目跑起来这篇就当我的day5学习笔记兼避坑日记看吧里面提到的每个词都是真实搜索过、确实绕了弯的路。1. 第五天的高频词几乎全是报错和看不懂的语法1.1 让人摸不着头脑的 javascript:void(0)第五天打开某个老项目的页面满屏a标签的href里都是javascript:void(0)。第一次见到这串东西我下意识觉得是什么高深魔法后来一查才发现逻辑非常简单。void是JavaScript里的一个运算符作用是计算后面的表达式然后永远返回undefined。void(0)就是计算数字0然后返回undefined。把它写在href里配合javascript:这个伪协议浏览器点击链接时会执行后面的代码而不是发生页面跳转。所以a hrefjavascript:void(0)相当于告诉浏览器点击这里执行一段什么都返回不了的JS别跳转。那为什么不直接用href#这是我今天踩的第一个小坑。#会让页面跳到顶部同时在URL后面拼一个锚点如果页面已经滚到很下面点击之后视野突然回到顶部体验非常糟糕。javascript:void(0)则完全不会动URL不会动滚动位置。很多老代码用它来挂接JS事件比如在onclick里写逻辑再配合一个“什么都不干的href”保证回车键、中键等特殊操作不会触发默认跳转。补充一个今天学到的点现在新项目里其实已经不太推荐直接写javascript:void(0)了更常见的做法是a hrefjavascript:;/a或者干脆用button元素。但在读老代码、改老页面的时候见到这一串要立刻反应过来它在干什么而不是一头雾水。1.2 运行时报错第五天的第一课是学会读报错今天搜索“javascript运行时报错”的次数大概比前面四天加起来都多。整理下来最常见的就三类报错类型错误信息长这样常见原因ReferenceErrorxxx is not defined变量没声明就使用或者拼错了变量名TypeErrorCannot read properties of undefined在一个undefined的值上读取属性或调用方法SyntaxErrorUnexpected token语法写错比如少了个括号、逗号放在不该放的位置发现一个规律JavaScript的报错信息其实非常诚实它会直接告诉你“哪个变量没定义”“哪一行读到了undefined”。我一开始看到一堆红色英文就慌后面学会了一个笨办法——先看报错信息里的文件名和行号再看错误类型最后往前找“出错的那一行到底引用了哪个变量”。百分之八十的问题这样三步就能定位。今天还发现一个容易误导新手的地方控制台报错的行号有时候是压缩后的JS文件行号不是源码行号。这种情况需要先在Sources里打开源文件或者配置sourcemap否则照着报错行号去源码里找怎么都找不到对应代码。1.3 地址栏里旋转视频的那行“小魔法”热搜里有一条很长很具体的内容javascript:v document.queryselector(video);v.style.rotate -90deg;v.s后面大概是被截断了。这条其实是很多人在浏览器里执行的“整活代码”把网页里的视频元素选中然后旋转90度。它用的API并不复杂——document.querySelector(video)用来选中第一个video标签element.style.rotate是CSS里比较新的旋转属性和transform: rotate()类似但写法更直接。这种地址栏执行JS的方式在浏览器里确实有效但有个前提浏览器会把地址栏输入的javascript:开头内容当作一段脚本执行页面当前的环境变量、DOM元素都能访问到。所以你可以在任意网页上试比如把页面标题改成别的字符串或者给所有图片加个边框。不过要泼一盆冷水这东西只能用来临时调试或者自己本地玩不要指望它成为一个“功能”。一旦刷新页面所有修改全部还原。真正要做的功能还是得写进代码里。另外现在的现代浏览器出于安全考虑对地址栏执行JS做了一些限制某些场景下会直接把输入内容当成搜索词处理所以这个玩法越来越像“时代的眼泪”了。2. 跨端调用与网络请求第五天开始面对真实世界2.1 C#执行JavaScript把JS引擎焊进后端今天搜索词里出现“c# 执行javascript代码”第一反应是C#是后端语言JavaScript是前端语言这俩为啥能扯到一起后来搞明白了实际开发里真的有这种需求。常见场景是后端C#服务需要执行一段由前端或业务方编写的JS规则脚本比如动态的折扣计算、风控规则、报表公式。规则经常变如果每次变更都发版后端成本太高。于是干脆把规则写成JS字符串存进数据库后端运行时调用JS引擎执行。在C#里执行JS主流方案有这么几种Jint纯.NET实现的JavaScript解释器不需要额外安装原生依赖适合在Windows/Linux服务器上使用。ClearScript微软开源的脚本引擎封装底层用V8或者JScript性能更好但需要在部署环境里带原生库。通过Selenium或Playwright启动真实浏览器来执行JS这种方式最重一般用于端到端自动化测试而不是常规业务。用Jint的代码大概长这样var engine new Jint.Engine(); engine.Execute(function add(a, b) { return a b; }); var result engine.Invoke(add, 3, 4); Console.WriteLine(result); // 7这里的关键思路是C#只负责把JS字符串丢给引擎然后拿结果两边通过“字符串和执行结果”通信。既然是字符串就需要注意转义、异常捕获和超时控制否则一段恶意或者死循环的JS脚本可能把整个服务拖垮。生产环境下一定要限制脚本执行时间和资源消耗这是很多人容易忽略的坑。2.2 OC和JavaScript互相调用iOS的H5桥接逻辑“oc和javascript互相调用”也是今天的高频词。在iOS开发里WKWebView加载H5页面后原生OC和网页JS经常要互相传数据比如H5里点击按钮要唤起原生分享面板原生操作完成后再把结果回传给H5。我梳理了一下正常流程是两条链路JS调OC在OC里通过WKUserContentController注册一个消息处理器H5那边用window.webkit.messageHandlers.xxx.postMessage(数据)发消息。OC在代理方法里接收消息内容。OC调JSOC直接调用webView.evaluateJavaScript(js函数名(参数))执行一段JS字符串。示例代码OC侧注册处理器WKWebViewConfiguration *config [[WKWebViewConfiguration alloc] init]; [config.userContentController addScriptMessageHandler:self name:share]; WKWebView *webView [[WKWebView alloc] initWithFrame:CGRectZero configuration:config];JS侧触发原生window.webkit.messageHandlers.share.postMessage({title: 分享标题, content: 分享内容});这里最大的坑是内存泄漏。addScriptMessageHandler会对self强引用如果self又持有webView就会形成一个循环引用导致控制器永远无法释放。常规做法是在页面销毁时主动调用removeScriptMessageHandlerForName或者在中间加一层代理对象来打破循环。理解桥接原理其实不难JS运行在浏览器内核里OC运行在原生进程中二者隔着进程边界。所有“互相调用”本质上都是把数据序列化成字符串或JSON对象通过系统提供的那条消息通道递过去。想通这一点无论是OC、Swift还是安卓的addJavascriptInterface就都不难理解了。2.3 fetch API的各种语法今天不用再写XHR了以前学AJAX绕不开XMLHttpRequest那套事件回调写多了确实头疼。今天查“javascript的fetchapi 各种语法”发现fetch才是这个时代应该用的东西。它是浏览器原生提供的网络请求接口返回的是Promise配合上async/await以后代码平铺直叙阅读体验好很多。最基础的GET请求const res await fetch(https://api.example.com/users); if (!res.ok) { throw new Error(HTTP res.status); } const data await res.json(); console.log(data);带参数的POST请求const res await fetch(https://api.example.com/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: day5, password: 123456 }) }); const result await res.json();这里有几个容易踩的细节fetch默认携带的credentials是same-origin跨域请求如果想要带上cookie需要显式设置credentials: include。响应体只能消费一次。res.json()之后不能再调res.text()如果既要JSON又要原始文本得先拿res.text()再自己JSON.parse。网络断开或超时fetch抛出的错误类型是TypeError而HTTP 404、500这类的非2xx状态码并不会走到catch里只有res.ok是false。所以代码里一定要先判断res.ok。今天还用AbortController试了一下请求超时控制核心是这么写的const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), 5000); try { const res await fetch(url, { signal: controller.signal }); // ... } catch (err) { if (err.name AbortError) { console.log(请求超时); } } finally { clearTimeout(timeoutId); }这套逻辑在真实项目里几乎必用不然一个慢接口能挂起页面半天。3. Vue项目里自动导入ElementPlus为什么ElMessage还是未定义3.1 自动导入是怎么“自动”的搜索词里有一组明显偏向Vue工程化的“vue javascript项目”“自动导入elementplus”“为什么elmessage还是提示未定义”。这组合在一起基本就是今天最有代表性的踩坑连环套。先说自动导入。ElementPlus是一个Vue 3组件库传统用法是在main.js里全局注册import ElementPlus from element-plus import element-plus/dist/index.css app.use(ElementPlus)这么做省事但会把整个组件库打进打包产物项目一大首屏体积直线上升。于是就有了按需自动导入的方案通过unplugin-auto-import和unplugin-vue-components这两个Vite插件在编译阶段扫描模板里用到的组件名和API名自动生成对应的import语句。vite.config.js里大致配置如下import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default { plugins: [ AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] }配置完之后模板里直接写el-button编译时插件会自动帮你引入ElButton组件和它的样式。表面上代码里看不到import但产物里已经按需包含了这就是它“自动”的工作原理。3.2 ElMessage未定义的真正原因按需导入只管模板很多人在模板里用el-button、el-input都没问题但一写ElMessage.success(保存成功)就报“ElMessage is not defined”。我今天也卡在这个问题上很久。原因在于unplugin-vue-components只处理模板中的组件标签它扫描的是template里的内容。而ElMessage、ElNotification、ElMessageBox这一类是“通过函数调用的组件”它们在模板里没有对应的标签组件插件根本不知道你用了它们自然就不会自动引入。解决方法是给AutoImport单独配置ElementPlus的API解析器让它认识这些函数式APIAutoImport({ imports: [vue], resolvers: [ElementPlusResolver()] })有了这个配置代码里直接写ElMessage.success插件编译时会自动在文件顶部插入import { ElMessage } from element-plus注意ElementPlusResolver()默认不会自动处理API的样式通常还需要配合unplugin-element-plus的样式导入或者在项目入口手动引入基础样式。这也是为什么有些同学配置了自动导入后ElMessage能弹出来但样式乱糟糟。另外还有一个隐蔽问题如果你在script setup里明明写了ElMessage的import同时又开着自动导入插件的API解析有些版本会生成重复声明造成编译告警。这种情况可以关掉自动导入对某个API的解析或者统一走自动导入不要混着写。工程化的坑大多是这种“约定冲突”。3.3 Vue2安全检测报“脆弱的JavaScript库”别只点掉警告热搜词里还有“vue2 安全检测提示 脆弱的javascript库”。这其实是npm或CI安全扫描工具给出的依赖漏洞提示常见于老项目因为Vue2生态停更后很多间接依赖的版本停留在很旧的阶段。安全扫描报某某库“脆弱”通常指的是这个库存在已知CVE漏洞比如原型污染、正则拒绝服务等。大多数情况下我们拿到的不是真正的攻击而是“存在理论风险”。处理这件事的通用排查链路运行npm audit查看漏洞详情看是直接依赖还是间接依赖。如果是间接依赖优先尝试npm audit fix自动修复到兼容版本。不能自动修复的看漏洞公告里修复版本手动修改package.json里的版本范围。版本范围跨度过大、可能破坏兼容性时使用overrides强制指定间接依赖版本或者用patch-package打补丁。无论如何修完要跑一遍回归测试尤其是老项目依赖大版本升级经常带来样式或接口行为的变化。这个坑给我的体会是安全扫描报警不等于立刻被攻破但也别直接忽略。最稳妥的做法是把依赖升级纳入日常迭代而不是等到扫描器亮红灯才处理。老项目升级依赖的时候一次只动一个包测一个包比一次性全升要安全得多。4. 箭头函数、模板变量和坐标数组那些看着眼熟却说不清的语法4.1 箭头函数到底解决了什么问题今天搜索“javascript 箭头函数”的不止我一个。箭头函数写法太简洁了const add (a, b) a b;但这时我产生了一个疑问它跟普通函数只是少写几个字母的区别吗显然不是。箭头函数最核心的差异在于this的指向。普通函数的this是在调用时决定的谁调用它this就指向谁。箭头函数没有自己的this它会捕获定义时所处作用域的this而且一旦捕获就无法改变。用一个生活化的类比普通函数像“看人下菜碟”今天领导叫你this就是领导明天同事叫你this就变成同事。箭头函数像“孩子随父姓”在哪个作用域里出生this就永远是谁。这也解释了为什么箭头函数不能作为构造函数不能用new来调用因为构造新对象需要自己的this而箭头函数根本没有。此外它也没有arguments对象如果需要类数组参数得用剩余参数...args。写代码时的一个实用经验在Vue 3的setup、React的函数组件里事件回调、定时器回调都尽量用箭头函数这样this能稳定指向组件实例或外层作用域。以前人们经常写var self this来绕过this指向问题有了箭头函数之后这个写法基本可以退休了。4.2 模板变量与模板字符串告别加号地狱还记得第一天学字符串拼接的时候写Hello, name !连接词一多字符串中间全是加号和引号眼都花了。今天搜“javascript 模板变量”终于找到了正主ES6的模板字符串。模板字符串用反引号包裹变量通过${}嵌入const name day5; const message Hello, ${name}!; console.log(message); // Hello, day5!它最大的优势不只是少加几个加号而是支持多行字符串。前端拼接HTML时以前要用div span text /span /div现在直接const html div classcard span${title}/span /div ;配合上简化版的${}表达式可以放心写复杂的条件判断、甚至是函数调用反正每个${}里都是一段合法的JS表达式。模板字符串在Vue、React里也特别常见Vue模板里的插值语法、React的JSX表达式都延续了这种“变量嵌进去”的直觉。4.3 用数组表达坐标数据结构的第一次实战热搜词里有“javascript 坐标表达数组”。光看描述有点像算法题的内容。点进去之后发现是在问“怎么用数组表示一个坐标点”。最常见的无非两种[x, y]一个二维数组表示点的横纵坐标。[{x: 3, y: 5}]一个对象数组每个对象里有x和y字段。选哪种不是随便定的。如果只是单纯处理一组同样结构的点比如画布上的多个顶点用[[1,2], [3,4], [5,6]]最紧凑配合数组解构遍历起来非常顺手const points [[1, 2], [3, 4], [5, 6]]; for (const [x, y] of points) { console.log(x${x}, y${y}); }但如果每个点还附带颜色、名称、大小这些属性那就该用对象数组const markers [ { x: 10, y: 20, label: 起点 }, { x: 50, y: 60, label: 终点 } ];这里真正想提醒的是数据结构是为访问方式服务的。你选择数组还是对象取决于后续是要“遍历计算”还是“按名字取字段”。第五天能把这个问题想明白后面学Map、Set、二叉树这些就会轻松很多。5. 学习资源的岔路口百炼成仙、悟透JavaScript与禁用的JS5.1 《JavaScript百炼成仙》和《悟透JavaScript》两本书的配合用法热搜词里同时出现“javascript百炼成仙”和“悟透javascript 美绘版 pdf”说明这个阶段的人已经开始主动找书读了。先说《JavaScript百炼成仙》。这书的形式很特别用玄幻小说的壳子讲JS语法主角修炼“JS内力”每一章练一个知识点。对于刚学到第五天的我来说这种形式最大的价值是降低枯燥感。JS语法其实不复杂但AP和练习之间隔着一层“抗拒感”一本能让人连续读下去的书对新手非常友好。《悟透JavaScript》则是另一路子。它不教你怎么写一个按钮的点击事件而是深入讲对象、原型、闭包、作用域链这些语言内核概念。它的标题起得很直白——把JS“悟透”不满足于会调API而要理解它为什么这么设计。我的建议是两本配合着来先用《百炼成仙》把语法和常用API顺一遍手不生了再回头看《悟透JavaScript》的闭包、原型链会突然有“原来之前写的代码是这个意思”的顿悟感。顺序反过来的话很容易被底层概念劝退。另外说一句网上搜“pdf”确实出了很多电子版但作者写书也要吃饭有条件尽量支持正版。学编程资料很多不差这一本书的钱。5.2 网页设计案例第五天适合动手做的小页面今天还看到“javascript网页设计案例”这个词。这个阶段看再多书都不如做一个能交到别人手里的页面成长快。我给自己定了一个第五天的练手目标做一个“今日待办清单”页面需求就三条输入框里输入文字点击添加按钮列表新增一条。每条待办前面有个复选框勾选后文字加删除线。点击删除按钮这一条被移除。这个案例麻雀虽小五脏俱全DOM选择、事件绑定、数组操作、渲染更新全都能练到。写完之后还可以扩展一下用localStorage把待办存下来刷新页面还在这就是一个完整的“数据持久化”体验。做案例时的一个小技巧把页面拆成三部分——HTML负责结构、CSS负责样式、JS负责行为。先把结构写死再给它加样式最后才写JS让页面“活”起来。很多人一上来就各种炫技写一堆库结果页面崩了都不知道是样式问题还是逻辑问题。第五天老老实实按这个顺序来出错概率会低很多。5.3 禁用JavaScript之后一个被忽视的健壮性问题热搜词里出现“禁用javascript”可能很多人就是顺手一搜但这其实是一个非常好的前端思考题。如果用户在浏览器里禁用了JS你的网页还能看吗JavaScript是让网页“动起来”的技术但它不应该是网页“能看”的前提。有些站点把内容全用JS渲染禁用JS之后只剩一个空白div这对部分用户和搜索引擎爬虫都很不友好。比较合理的做法是渐进增强先用HTML写好基础内容和静态结构再用JS增强交互和动态功能。在HTML里提供降级提示用noscript标签noscript div您的浏览器未启用JavaScript部分功能可能无法正常使用。/div /noscript这个标签在用户禁用JS时才会渲染启用JS时不会显示。对于SPA这类必须依赖JS的应用用noscript提示用户开启JS是成本和体验之间比较均衡的兜底方案。更深一层的原因是性能与可用性如果核心内容必须要等JS下载完、解析完、执行完才能出现那用户的网速一慢整个页面就是白屏。越来越多开发者开始研究SSR、静态站点生成目的之一就是让首屏内容不依赖JavaScript。第五天看到这个热搜词能想到这一层今天的学费就没白交。晚上翻今天保存下来的代码片段发现学习进度比我预想的要快。上午还在查void(0)下午已经在折腾Vite插件配置了。学习JS的过程就是这样每一个“为什么报错”背后都藏着一个真实的运行机制。明天准备把今天遇到的所有报错信息整理成一个速查表下次再遇到直接对号入座。