最近搜"JavaScript"相关关键词的人特别多,我扫了一圈热搜词——从 javascript:void(0)、箭头函数、fetch 各种语法,到 Vue 项目里自动导入 Element Plus 却报 ElMessage is not defined,再到 OC 和 JavaScript 互相调用、macOS 环境配置、运行时报错……说实话,这些词凑一块儿,正好勾勒出一名前端开发者从入门到接手真实项目的完整路径。
这篇文章我就按这条路径来写:先讲环境怎么搭、基础怎么补,再聊现代语法和 API 的实用细节,接着用 Vue + Element Plus 的真实报错场景做一次排查演示,最后说说跨端互调和学习资料踩坑。所有内容都来自我实际开发和带新人过程中的积累,不讲空话,直接给结论和可以照着抄的方案。
1. 先把环境跑起来:macOS 开发环境配置与运行时错误根源
1.1 别一上来就装全家桶,先想清楚你要跑什么
很多初学者拿到的第一份"JavaScript 环境配置教程"会让人装一堆东西:Node、npm、yarn、pnpm、Vite、Webpack……全装完再说。结果就是环境变量乱成一锅粥,跑什么都是满屏报错。我带的几个新人里,至少有三次"运行时报错"最后定位到是环境问题,而不是代码问题。
在 macOS 上,一套干净、够用的 JavaScript 开发环境其实只需要三样:
- Node.js:JavaScript 的运行时,跑脚本、装依赖都靠它。
- 包管理器:npm 会随 Node 一起装,够用;你要是愿意用 pnpm 或 yarn 也行,但别同时混用。
- 一个编辑器:VS Code 是绝大多数人的选择,装好 ESLint 和 Prettier 插件即可。
安装 Node 我推荐用 nvm(Node Version Manager)而不是直接去官网下 pkg 安装包。原因很简单:真实项目会锁定 Node 版本,你同时维护三五个项目时,不同项目可能要求 Node 14、16、18,用 nvm 可以随时切换,不用反复卸载安装。
bash复制# 安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 安装 Node 长期支持版
nvm install --lts
# 查看当前版本
node -v
装完之后,在终端输入 node -v 能输出版本号,npm -v 也正常,环境就通了。如果出现 command not found: node,大概率是 nvm 没有写入 shell 配置,检查 ~/.zshrc 里有没有 nvm 的初始化脚本,重新 source ~/.zshrc 就好。
1.2 macOS 上常见的运行时错误,冤枉代码的情况占一半
我见过太多人对着一段逻辑完全正确的代码抓耳挠腮,结果问题出在环境上。macOS 下这几个错误出现频率最高,我列个速查表:
| 错误信息 | 常见原因 | 解决方法 |
|---|---|---|
command not found: node |
nvm 未正确加载或 Node 未安装 | 检查 ~/.zshrc,重新加载 nvm |
EACCES: permission denied |
全局安装依赖时权限不足 | 用 nvm 管理 Node,不要用 sudo 装全局包 |
Digital envelope routines::unsupported |
Node 17+ 与旧版 Webpack 的 OpenSSL 冲突 | 升级构建工具,或设置 NODE_OPTIONS=--openssl-legacy-provider |
Module not found: Error: Can't resolve 'xxx' |
依赖没装全或路径写错 | 先 npm install,再检查 import 路径 |
Cannot find module 'xxx' |
node_modules 损坏或版本不一致 | 删除 node_modules 和 lock 文件后重装 |
最后一个错误建议优先试:rm -rf node_modules package-lock.json && npm install。这招能解决大概三分之一莫名其妙的本地运行时报错。
注意:macOS 上不要用
sudo npm install -g去装全局工具。权限错误十有八九是这么来的,而且 sudo 装出来的全局包在 nvm 切换 Node 版本后会直接失效,到时候你都不知道哪个环节出了问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 别让基础概念卡住你:函数、void(0) 与箭头函数
2.1 理解 JavaScript 函数,先抓住"一等公民"这个点
"JavaScript 函数"这个热搜词太宽泛了,但背后反映的问题很集中:函数声明、函数表达式、立即执行函数、回调函数、高阶函数……每种写法看着相似,行为却不同。新手最容易栽在"函数声明会被提升,函数表达式不会"这一点上。
javascript复制// 函数声明:可以在定义之前调用,因为声明会被提升
sayHello("张三"); // 正常输出
function sayHello(name) {
console.log("你好," + name);
}
// 函数表达式:赋值给变量,声明不会提升
sayBye("李四"); // TypeError: sayBye is not a function
var sayBye = function (name) {
console.log("再见," + name);
};
我当时带新人最喜欢用这个例子来解释"提升"(hoisting)的概念:JavaScript 引擎在正式执行前会先把所有函数声明和变量声明"预读"一遍,但赋值操作留在原处。所以 var sayBye 这个变量名被提升了,但它的值还是 undefined,一调用就报 not a function。
这也是为什么现代项目都推荐用 const 声明函数表达式,能在编译阶段就暴露这种问题。ESLint 里的 prefer-const 规则就是干这个的。
2.2 javascript:void(0) 是什么,为什么老在旧代码里看到
搜索 javascript:void(0) 的人,多半是碰到了老项目或者某些网页源码。这个写法其实是把 JavaScript 当作 href 属性的协议来用:
html复制<a href="javascript:void(0);" onclick="handleClick()">点击我</a>
void 是一个一元运算符,它对后面的表达式求值,然后永远返回 undefined。写成 void(0) 就是计算 0 并返回 undefined。为什么要这么干?因为如果 href 里直接写 javascript:returnValue,浏览器会用返回值替换当前页面内容。加 void(0) 是为了确保点击链接后页面不会跳转、不会刷新,只执行 onclick 里的逻辑。
现在回过头看,这属于历史遗留写法。现代开发里你完全可以用 button 元素替代 a,或者用 href="#!" 这种占位符,再在事件处理器里调用 preventDefault()。我遇到过的真实坑是:在单页应用里,某些老组件用了 href="javascript:void(0)",结果被内容安全策略(CSP)拦截,点击毫无反应。排查半天才发现是这行代码的问题。所以遇到老代码,能替换就替换掉,别留着过年。
2.3 箭头函数:省略 return 之外,更重要的是 this 的绑定逻辑
"JavaScript 箭头函数"是热搜词里的常客。箭头函数确实让代码更简洁,但很多人只学到了简化写法,没搞明白它和普通函数最本质的区别——this 的绑定方式。
普通函数的 this 在调用时决定,谁调用的就指向谁;箭头函数没有自己的 this,它捕获定义时所在作用域的 this。
javascript复制const obj = {
name: "计数器",
count: 0,
startWithRegular: function () {
setInterval(function () {
// 这里的 this 指向 window(非严格模式),不是 obj
this.count++;
}, 1000);
},
startWithArrow: function () {
setInterval(() => {
// 箭头函数捕获了外层 this,指向 obj
this.count++;
}, 1000);
},
};
这段代码可以说是理解 this 的最佳教材。早期开发者解决这个问题得用 var self = this 或者 .bind(this),箭头函数出现后用起来就自然多了。但我要提个反直觉的坑:在对象方法里用箭头函数反而会出错。比如:
javascript复制const obj = {
name: "test",
getName: () => {
// 这里 this 不指向 obj,而是指向外层作用域(全局)
return this.name;
},
};
所以我在代码评审时的原则是:箭头函数用于回调、函数式编程、需要保留外层 this 的场景;对象方法、需要动态 this 的场景、构造函数,老老实实用普通函数。
3. 浏览器里的 JavaScript 实战:Fetch API 用法大汇总
3.1 从 XMLHttpRequest 到 fetch,再到现代的封装习惯
"JavaScript 的 fetch API 各种语法"这个热搜词透露出一个矛盾:fetch 看着简单,用起来却到处是细节。它解决了 XMLHttpRequest 时代回调地狱的问题,基于 Promise 设计,但实际开发中如果只用基础写法,很快就会踩到各种坑。
最基本的 GET 请求:
javascript复制fetch("https://api.example.com/users")
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("请求失败:", error));
这个写法看起来没什么问题,但有一个经典坑:fetch 只有在网络错误或请求被取消时才会 reject,HTTP 状态码是 404、500 时它照样 resolve。所以上面的 .catch 捕获不到"接口返回错误状态码"的情况。
javascript复制fetch("https://api.example.com/users/not-exist")
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.catch((error) => console.error("请求失败:", error));
加一个 response.ok 的判断是必须的,否则你的页面在接口 500 时会拿到一堆没法解析的数据,然后在渲染层报一个根本定位不到源的错误。
3.2 带参数、带请求头、上传文件,各种写法的要点
GET 请求带查询参数,别自己拼字符串,用 URLSearchParams:
javascript复制const params = new URLSearchParams({
page: 1,
pageSize: 20,
keyword: "javascript",
});
fetch(`https://api.example.com/users?${params.toString()}`)
.then((response) => response.json())
.then((data) => console.log(data));
POST 请求提交 JSON 数据时,有两个必须注意的点。第一,要设置 Content-Type: application/json 请求头;第二,请求体要用 JSON.stringify 序列化。我见过不少新人直接传一个对象进去,结果后端收到的是 [object Object]:
javascript复制fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "张三",
email: "zhangsan@example.com",
}),
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => console.log("创建成功:", data))
.catch((error) => console.error("请求失败:", error));
如果是文件上传,不能手动设置 Content-Type,要让浏览器自动生成带 boundary 的 multipart/form-data:
javascript复制const fileInput = document.querySelector('input[type="file"]');
const formData = new FormData();
formData.append("file", fileInput.files[0]);
fetch("https://api.example.com/upload", {
method: "POST",
body: formData,
// 注意:不要手动设置 Content-Type
})
.then((response) => response.json())
.then((data) => console.log("上传成功:", data));
手动设置 Content-Type 之后,boundary 会丢失,后端解析 multipart 直接失败。这个坑我踩过一次,排查了将近两个小时,最后发现是请求头搞的鬼。
3.3 超时控制与取消请求:fetch 的短板
fetch 默认没有超时机制,请求挂在那里可能很久都不报错。要加超时可以用 AbortController:
javascript复制const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
fetch("https://api.example.com/slow-endpoint", {
signal: controller.signal,
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => {
if (error.name === "AbortError") {
console.error("请求超时,已取消");
} else {
console.error("请求失败:", error);
}
})
.finally(() => clearTimeout(timeoutId));
AbortController 是处理 fetch 超时和页面卸载时取消请求的标准做法。实际项目中我通常不会直接裸用 fetch,而是封装一层 request 函数,统一处理 baseURL、token、超时、错误码、错误提示。至于具体封装方案,每家公司风格不同,但核心思路都是把上面这些细节收拢到一个地方,不要在业务代码里到处重复。
提示:如果你在调接口时发现页面报错信息里只有"Unexpected end of JSON input"或者"Unexpected token < in JSON",先别查代码逻辑。大概率是后端返回的不是 JSON,而是 HTML 错误页或者一个空 body,
response.json()自然解析失败。把返回内容打出来看一眼,比盯着代码猜要快得多。
4. Vue 项目中的 JavaScript:自动导入 Element Plus 与 ElMessage 未定义之谜
4.1 从"全量引入"到"自动导入",Element Plus 的两种用法
热搜词里 "vue javascript 项目、自动导入 elementplus、为什么 elmessage 还是提示未定义" 这几个连在一起,我几乎能脑补出一个完整的技术咨询场景:新人用 Vite 创建了 Vue 3 项目,然后跟着 Element Plus 官方文档配置了自动导入,跑起来后发现组件能正常渲染,但调用 ElMessage 却报"未定义"。
先理清楚背景。Element Plus 的引入方式有两大类:
全量引入。在 main.js 里一次性引入整个组件库和样式。优点是省心,缺点是打包体积大:
javascript复制import { createApp } from "vue";
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import App from "./App.vue";
const app = createApp(App);
app.use(ElementPlus);
app.mount("#app");
按需自动导入。借助 unplugin-auto-import 和 unplugin-vue-components 两个 Vite 插件,按需加载组件和 API。这种方式下,模板里直接用 <el-button> 不需要手动 import,组件和样式都会被自动注入:
javascript复制// vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
// 自动导入 Vue 和 Vue Router 的 API
imports: ["vue", "vue-router"],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
});
配置了 AutoImport 之后,你可以在组件里直接使用 Vue 的 ref、computed,不用手动 import。这让代码清爽不少,但也埋下了"符号到底从哪来"的认知盲区。
4.2 为什么 ElMessage 还是提示未定义
问题就出在 ElMessage 这种函数式 API 上。Components 插件能处理的是模板里用到的组件,比如 <el-button>、<el-table>。你在模板里写了这些标签,插件识别到之后会自动帮你注入对应的组件和样式。
但 ElMessage、ElMessageBox、ElNotification 这几个不是通过模板标签使用的,它们是直接以函数调用的方式出现在 JavaScript 代码里的:
javascript复制import { ElMessage } from "element-plus";
// 注意:message 的样式需要单独引入,不是组件自动导入就能覆盖的
ElMessage.success("保存成功");
如果你在 AutoImport 插件里配置了 ElementPlusResolver(),它理论上会自动帮你导入 ElMessage,这就是官方文档说的"API 自动导入"。但实际中出现"未定义"报错,常见原因有三个:
第一个原因:AutoImport 插件配置了,但 ElementPlusResolver 没传给 AutoImport。很多人只在 Components 里配置了 ElementPlusResolver,AutoImport 里没有配。结果模板组件没问题,但 ElMessage 没有任何插件负责自动导入:
javascript复制// 正确配置:两个插件都要传 ElementPlusResolver
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
第二个原因:样式没引入。这个最隐蔽,因为报的不一定是"未定义"错误,而是消息提示弹出来了,但样式全乱,或者干脆不显示。Element Plus 的 message 样式是以独立 CSS 形式存在的,按需自动导入的 resolver 理论上会处理样式,但如果你的项目里手动引入了全量样式又用了按需组件,可能出现样式被覆盖或 tree-shaking 把样式抖掉的问题。解决办法是检查 main.js 里是否手动 import "element-plus/dist/index.css" 并和自动导入方式同时使用,这两者混用往往会出幺蛾子。
第三个原因:没有重新启动开发服务器。AutoImport 插件在启动时会生成一个 auto-imports.d.ts 文件,如果你在插件配置改动后没有重启 Vite 开发服务器,自动导入的声明文件还是旧的,IDE 和运行时都可能拿着旧版本在那儿工作。
4.3 运行时报错的排查思路,用这套流程能省一半时间
针对"ElMessage 提示未定义",下面是我推荐的排查顺序:
- 先看
vite.config.js里 AutoImport 和 Components 是否都配置了 ElementPlusResolver。 - 重启
npm run dev,让插件重新生成类型声明文件。 - 在报错文件里手动加一行
import { ElMessage } from "element-plus";,如果问题消失,说明自动导入没生效,回到第 1 步检查。 - 检查项目根目录是否生成了
auto-imports.d.ts,如果项目根目录下没有,确认插件是否被正确加载。 - 如果你是在 TypeScript 项目里用,还要确认
tsconfig.json的include里是否覆盖了auto-imports.d.ts,否则 TS 会报找不到名称。
这套排查逻辑适用于很多"配置了自动导入但报未定义"的场景,不只是 Element Plus。unplugin-vue-components 生态下还有 Vant、Ant Design Vue、Naive UI 的 resolver,问题表现和解法都一样,核心都是确认 AutoImport 和 Components 各自职责、确保 resolver 都配上、然后重启让声明文件重新生成。
5. 跨过语言边界:iOS 开发中 OC 与 JavaScript 的互相调用
5.1 为什么会有 OC 和 JS 互调的需求
"OC 和 JavaScript 互相调用"是热搜词里比较偏工程化的一个。场景很明确:App 内的部分页面用 Web 技术渲染(H5 页面、混合开发、React Native 容器),或者 App 里有大量运营活动页面用 HTML 快速迭代。运营要发个活动,要是走原生发版流程,审核周期长得没法看;用 H5 页面就能绕过发版,后台配置完前端直接上线,这是混合开发模式经久不衰的根本原因。
但 H5 页面不能只展示静态内容,它需要调用原生能力:获取用户的登录态、调起相机、上传图片、分享到微信、关闭当前页面……这时候就要求 JavaScript 能调用 OC;原生端也要能主动通知 H5 某些事件,比如用户登录状态变了、App 从后台回前台了,这就涉及 OC 调用 JavaScript。
5.2 核心实现方案和真实踩坑记录
在 iOS 的 WKWebView 体系下,OC 与 JS 互调的主流方案是使用 WKScriptMessageHandler(JS 调 OC)和 evaluateJavaScript(OC 调 JS)。
先看 JS 这端怎么调 OC。通过 window.webkit.messageHandlers.xxx.postMessage(data) 发送消息:
javascript复制// JavaScript 侧代码
function callNativeShare() {
const message = {
type: "share",
payload: {
title: document.title,
url: window.location.href,
},
};
window.webkit.messageHandlers.ShareHandler.postMessage(message);
}
原生端在 OC 里要注册对应的 handler。有一个非常容易踩的坑:postMessage 发送的数据如果你传的不是简单字符串,而是带函数的对象或者循环引用的对象,会被序列化失败。WKScriptMessageHandler 接收到的 body 实际上是 JavaScript 对象的序列化版本,不是原始 JS 对象。所以传到原生的对象里只能有可序列化的数据,函数、Map、Set 都会丢失或报错。
再看 OC 调 JS,用 evaluateJavaScript:
objective-c复制// OC 侧代码
WKWebView *webView = [[WKWebView alloc] init];
NSString *token = @"your-auth-token";
// 调用 JS 里的全局函数
NSString *jsCode = [NSString stringWithFormat:@"window.updateToken('%@')", token];
[webView evaluateJavaScript:jsCode completionHandler:^(id result, NSError *error) {
if (error) {
NSLog(@"JS 执行失败: %@", error);
} else {
NSLog(@"JS 返回值: %@", result);
}
}];
这段代码有几个实操要点。第一,JS 执行的时机很关键。evaluateJavaScript 必须在网页加载完成后再调用,否则 window.updateToken 不存在,执行直接报错。所以实践中通常要监听 didFinishNavigation 回调,在里面做 OC 调 JS 的动作。第二,传给 JS 的字符串内容要防注入,如果 token 里有单引号、反斜杠,拼出来的 JS 代码可能语法错误或者被篡改逻辑。正式方案是用 JSON.stringify 序列化数据,再通过 JSON.parse 在 JS 侧解析进入,但这个过程要注意 Cocoa 的字符串和 JS 字符串转义规则不一致的问题。第三,evaluateJavaScript 的返回值不一定能直接拿到 JS 函数的返回值,如果 JS 函数返回的是 Promise,原生端拿到的是 undefined,异步结果没法直接通过返回值回传,只能靠 JS 再把结果通过 postMessage 发给原生。
如果要处理比较复杂的互调逻辑,成熟的方案是封装统一的桥接层,两端都定义好协议格式。JS 侧无论调用什么原生能力,都走同一个方法,消息体里带 method 名和参数列表;原生端收到消息后路由到对应的处理函数。这样 H5 页面和原生端各自维护一套稳定的接口定义,业务方开发起来心智负担小很多。在项目起步阶段就约定好这套协议,能给你以后省下大量排查"为什么 H5 传的参数原生收不到"这种问题的精力。
6. 别盲目买书:JavaScript 学习资料的真实价值
6.1 "你不知道的 JavaScript" 和 "高级程序设计" 该怎么读
搜索 你不知道的javascript pdf 和 javascript高级程序设计 的人,多半是在求资源或者纠结先读哪本。先说结论:这两本书值得读,但不是同一个读法。
《JavaScript 高级程序设计》(红宝书)是系统性的教材,事无巨细地把语言规范讲了一遍。我的建议是把它当作工具书,前几章讲类型、作用域、闭包的必读,后面的 DOM、事件、网络请求部分可以配合实际项目按需查阅,不必追求一次性全部读完。
《你不知道的 JavaScript》系列("You Don't Know JS")跟红宝书不一样,它是挑语言中最容易被误解的机制深入剖析的。上卷讲作用域与闭包、this 与对象原型,这两块是无数前端面试翻车的重灾区。读完它你才能真正理解为什么 this 会那样绑定、为什么 var 和 let 行为不一样。中卷讲类型与语法、异步与性能,下卷讲 ES6 以后的新特性。
这两本我都不建议直接看 PDF 电子版。它们是需要反复翻、来回对照的深度书,纸质版或者正规电子书平台的批注功能会让你效率高很多。更重要的是,别把时间花在找资源上,打开官方的免费补充资料或者先看目录找自己最薄弱的一章下手,比存一堆 PDF 吃灰有用得多。
6.2 从书到代码:网页设计案例和"JavaScript 百炼成仙"适合什么阶段
搜 javascript网页设计案例 的读者应该已经过了语法学习阶段,想通过完整项目把知识串起来。这个思路是对的,但选案例要克制。做成"待办事项"或"计算器"这类小项目,只能练到 DOM 操作;想体系化提升,建议做数据驱动的中台项目,比如一个带登录鉴权、列表筛选、表单校验、图表展示的管理后台,技术栈覆盖 Vue 或 React、路由、状态管理、HTTP 请求封装、组件化拆分,这才是接近真实工作的形态。
至于《JavaScript 百炼成仙》,这是一本修仙小说风格的技术书,用故事把 JavaScript 知识点串起来。适合学完基础但觉得枯燥、需要换换口味维持兴趣的初学者。这类书的问题在于知识点密度低,故事线会稀释技术内容,你可以当休闲读物看,但别作为系统学习的主线。我认识的几个后端转前端的同事用它入门,评价是"能看下去,但看完还得回来看正经书"。
6.3 关于 Chrome 与 Apple 事件自动化的一些补充说明
热搜词里"chrome 允许 apple 事件中的 javascript"对应的其实是 macOS 上的自动化场景——通过 Apple Events(比如 AppleScript 或 Automator)控制 Chrome 执行 JavaScript。这在做浏览器自动化测试时会用到。实操中你在"系统设置-隐私与安全性-自动化"里能看到某个应用请求控制 Chrome 的权限,允许后就能在 AppleScript 里写 execute javascript 之类的语句让 Chrome 运行 JS 脚本来操作网页。
我对这块的建议是:如果你的目标是自动化测试网页,优先用 Playwright、Puppeteer 这类专业浏览器自动化工具,它们对 JavaScript 执行、元素等待、无头模式的支持远比 Apple Events 方案完善。只有当你需要把系统级操作(比如读取本地文件、控制其他应用)和浏览器操作串在一起时,Apple Events 才值得上手。这个方向小众,资料少,踩坑只能自己摸索,做好心理准备。
如果只是普通的前端开发调试,你大概率用不到这个功能。Chrome 自带的开发者工具(DevTools)里的 Console 面板已经能执行任意 JavaScript,配合 Sources 面板打断点调试,应对日常开发绰绰有余。
写在最后的几句实话
文章写到这里,从环境配置一路聊到了跨端开发,从基础语法聊到了框架生态。说实话,JavaScript 这个生态最大的特点就是东西太多、变化太快,今天学完的框架,可能过两年就迭代了两三个大版本。我自己的感受是:不要追着框架跑,要把语言本身吃透。this 绑定的规则、闭包的形成机制、事件循环的顺序、Promise 和异步的底层逻辑,这些是十年不变的,而框架只是一个上层建筑。
我经历过的最大的教训就是,早期过于依赖框架的"魔法",比如自动导入、自动 diff、自动更新,出了问题只会对着错误信息束手无策。后来花时间补了语言基础,慢慢能看懂框架源码在做什么,很多报错不再需要百度,看一眼堆栈就能猜到问题出在哪个环节。所以如果你想在这个行业走远一点,建议你现在就开始读那本你已经存了 PDF 但一直没翻开的大部头。
最后再分享一个实用的小习惯:不管你在哪个框架里,多留意构建工具在编译时给你的提示。Vite 控制台那些 [plugin auto-import] 日志、ESLint 的告警,都不是废话,它们会坦白告诉你框架替你做了什么、没做什么。很多"未定义"和"运行时报错",其实提示信息早就告诉你答案了,只是你还没学会看它而已。
