浏览器里的 js
管界面、管交互、管网络请求。它能操作页面上的每一个元素,但被沙箱限制在网页范围内,碰不到操作系统。
你不需要先把所有语法背完再动手。这份指南把 js 的语法地基、运行机制、浏览器与 Node.js 两套实践环境,编成一条能一段段走的成长路线——每走完一个阶段,你手上都会有能跑起来的代码,而不是一堆记不住的名词。
很多人学 js 的挫败感,其实不来自语法难,而来自「学完不知道能干什么」。变量、函数、循环这些东西拆开看都不复杂,难的是把它们串成一个能解决问题的整体。所以这份指南从第一段起就带着目标:每个知识块后面都跟着一个能立刻动手的小任务,让你在浏览器控制台里亲眼看到结果。
另一个常见误区是把 js 当成「网页特效脚本」。今天的 js 早就不止于此——它跑在浏览器里管界面,跑在 Node.js 里管服务器,还能写桌面应用、小程序、甚至硬件控制程序。理解这个定位,你才知道该往哪个方向投入精力。后面每个板块都会先界定「是什么、能干什么」,再给「怎么判断自己学会了」的检验方法,能确认了再往下走。
js 是一门解释型的、动态类型的脚本语言,最初只为在浏览器里操作网页,如今凭借 Node.js 跑到了服务器、桌面端与嵌入式设备上。它的核心价值是「一门语言,多处运行」。
js 的全称是 JavaScript,名字里带 Java 纯属当年借势营销的历史误会——两者在设计上几乎没有血缘关系。它 1995 年诞生于网景浏览器,目的是让静态网页能动起来:点按钮变色、表单即时校验、图片轮播。当时没人想到,这门被戏称为「十天赶工出来的语言」会在二十多年后成为全世界使用最广的编程语言之一。理解这段历史不是掉书袋,而是能解释它为什么有那么多「看起来不合理」的设计,比如宽松的相等比较、自动类型转换。
从语言特性看,js 是动态类型的:变量不需要声明类型,赋值时是什么类型就是什么类型。这带来上手快的好处,也埋下了运行时才报错的坑。它还是一门基于原型的面向对象语言,没有传统意义上的类继承(ES6 的 class 只是原型继承的语法糖)。这两点决定了 js 的学习重点:不是背语法规则,而是理解运行时的行为逻辑。
那 js 到底能做什么?把常见用途摊开看,大致是四类:一是网页交互,这是它的老本行,DOM 操作、事件处理、表单验证都归这里;二是服务端开发,Node.js 让 js 能读写文件、连接数据库、处理 HTTP 请求;三是跨端应用,React Native、Electron、小程序框架都建立在 js 之上;四是工具链,前端工程里的构建、打包、测试脚本几乎都是用 js 写的。你不必每样都学,但知道地图长什么样,学起来才不会迷路。
一个简单的自测:能不能用三句话说清「js 和 Java 的区别」「为什么 js 能在服务器上跑」「浏览器里的 js 不能做什么」。第三问尤其关键——出于安全考虑,浏览器里的 js 不能直接读写用户硬盘文件、不能随意跨域访问其他网站数据、不能启动本地程序。知道这些限制,你才不会写出「为什么我的代码读不到本地文件」这种注定失败的需求。
管界面、管交互、管网络请求。它能操作页面上的每一个元素,但被沙箱限制在网页范围内,碰不到操作系统。
同一门语言,换了一套运行时。可以读写文件、开服务器、连数据库,是打通前后端的关键一步。
桌面端、移动端、小程序、甚至单片机。语法还是那套语法,差别在可用的 API 和运行限制。
需要说明的是,本文涉及的语言版本、API 行为均以公开标准(ECMAScript 规范与各运行时官方文档)为准。凡是无法从公开资料确认的具体版本号、发布时间或功能清单,本文宁可留白也不做猜测补齐——这是我作为编辑的态度,也希望你在查资料时养成同样的习惯。
新手第一阶段就用浏览器,打开开发者工具就能写代码,零配置;等你需要读写文件、做接口、跑构建脚本时再装 Node.js。两者语法相同,差的是可调用的 API 和全局对象。
先界定清楚:js 语言本身只是一套语法规范,真正执行它的是「运行时」。浏览器内置的运行时叫 V8(Chrome)、SpiderMonkey(Firefox)等,Node.js 则把 V8 引擎单独拎出来,补上文件系统、网络、进程这些浏览器里没有的能力。所以同一段 1 + 1 在哪都能跑,但 fs.readFile 只有 Node.js 认识,document.querySelector 只有浏览器认识。
浏览器环境的优势是「零门槛」。按 F12 打开开发者工具,切到 Console 面板,你就能立刻敲代码看结果。全局对象是 window,页面上每个元素都能通过 DOM API 拿到。适合学语法、练 DOM 操作、做交互练习。缺点是它被安全策略捆着手脚:不能读本地文件、跨域请求会被拦、也没有定时任务这类后台能力。
Node.js 环境则相反:安装要多花十几分钟,但换来的是完整的系统能力。全局对象是 global,有 process 可以拿命令行参数和环境变量,有 fs 读写文件,有 http 直接起服务。日常前端工程里的脚手架、打包工具、单元测试,全跑在 Node.js 上。哪怕你只想做纯粹的前端,也躲不开它。
如果你的目标是「让网页动起来」,前两个月完全可以只在浏览器里练,把语法和 DOM 吃透再装 Node.js,避免一上来被 npm、路径、版本管理这些工程概念劝退。如果你的目标是「转行做后端或全栈」,那就尽早装 Node.js,因为服务端调试习惯和前端差别很大,越早适应越好。至于两者都学的人,建议先浏览器后 Node.js,因为前者反馈更快,能帮你建立「我写的代码真的在运行」的信心。
| 对比项 | 浏览器环境 | Node.js 环境 |
|---|---|---|
| 安装成本 | 零,按 F12 即用 | 约 10–20 分钟,需下载安装包 |
| 全局对象 | window | global |
| 能操作页面 DOM | 可以 | 不可以(需借助第三方库模拟) |
| 能读写本地文件 | 不可以(受沙箱限制) | 可以,通过 fs 模块 |
| 适合的阶段 | 第 1–2 阶段 | 第 3 阶段及以后 |
一个容易踩的坑:在浏览器里写 require('fs') 会直接报错,因为模块系统在浏览器中默认不存在。反过来,在 Node.js 里写 alert() 也会报错,因为那是浏览器才有的弹窗 API。判断一段代码该在哪跑,最快的方法就是看它用了哪些全局对象——用了 document 或 window 就是浏览器,用了 process 或 fs 就是 Node.js。
语法这一关没有捷径,但有优先级。变量声明、数据类型、运算符这三块占日常代码量的绝大多数,先把它们练到不用想就能写对,后面的函数和对象才学得动。
声明变量用 let 和 const。区别很简单:const 声明的变量不能再被重新赋值,let 可以。选择原则是「默认用 const,需要重新赋值时才用 let」。这样做的好处是,你一眼就能看出哪些值在后续代码里会变,哪些不会,读代码时少一半心算。老教程里常见的 var 现在基本不用了,它的作用域规则容易出问题,新项目里没有理由再用。
const appName = "js 学习手册"; // 不会变,用 const
let progress = 0; // 后面要加,用 let
progress = progress + 25; // 重新赋值合法
console.log(appName, progress); // 输出:js 学习手册 25
这段代码可以直接粘到浏览器控制台运行。注意 const 那句如果改成 appName = "别的名字",会立刻抛出 TypeError,这正是它想帮你防的错误。
js 的原始类型有七种:string 字符串、number 数字、boolean 布尔、null、undefined、symbol、bigint。除此之外的一切都是对象,包括数组和函数。新手最容易搞混的是 null 和 undefined:前者是「我主动赋值成空」,后者是「还没有值」。变量声明了没赋值,就是 undefined;想表示「这里刻意留空」,就写 null。
类型判断有两个工具:typeof 返回类型字符串,适合判断原始类型;判断是不是数组要用 Array.isArray(),因为 typeof [] 会返回 "object",这是历史遗留的坑。数字类型还有一个需要警惕的地方:0.1 + 0.2 不等于 0.3,而是 0.30000000000000004。这不是 bug,是浮点数在二进制下无法精确表示十进制小数的通用现象,几乎所有语言都有。涉及金额计算时,通常的做法是转成整数分来计算,最后再除回去。
第一,== 和 === 的区别。== 会先做类型转换再比较,=== 要求类型和值都相同。"1" == 1 是 true,"1" === 1 是 false。日常一律用 ===,除非你有非常明确的理由,否则它只会给你埋雷。第二,+ 号遇到字符串会变成拼接,1 + "1" 得到字符串 "11" 而不是数字 2,这是新手最常见的一类「为什么算错了」。第三,逻辑运算符有短路特性:a && b 在 a 为假时直接返回 a,不会再算 b;a || b 在 a 为真时直接返回 a。利用这个特性可以写默认值,比如 const name = input || "匿名"。
以上百分比是本文用于描述学习者常见掌握程度的参考区间,来自教学场景的普遍观察,不代表任何第三方统计数据。
如果说变量是名词,那流程控制和函数就是动词。它们决定了代码怎么「动」起来,也是从写一行行散装语句,过渡到写有结构的程序的分水岭。
if / else if / else 适合处理有大小关系的判断,比如分数段、价格区间。switch 适合处理「等于某个固定值」的分支,比如根据星期几执行不同逻辑。判断条件里有个技巧值得早点学会:把最常见的分支写在前面,让代码执行路径更短。另外三元运算符 条件 ? 值1 : 值2 适合简单的二选一赋值,但嵌套超过一层就该换成 if,否则可读性会急剧下降。
function levelOf(score) {
if (score >= 90) return "熟练";
if (score >= 70) return "掌握";
if (score >= 50) return "入门";
return "需要复习";
}
console.log(levelOf(82)); // 输出:掌握
需要知道循环次数时用 for,条件满足就一直跑用 while。但实际写业务代码时,处理数组更常用的是 forEach、map、filter、reduce 这几个方法,因为它们的意图更明确:map 是「逐个转换」,filter 是「筛选」,reduce 是「汇总成一个值」。用它们代替 for 循环,代码通常更短,也更不容易写错下标。
一个必须提醒的坑:while 循环如果条件永远为真,会直接卡死浏览器标签页。写循环前先想清楚「什么情况下会停下来」,这是基本的安全意识。
函数声明 function f(){} 会被提升,可以在定义之前调用;函数表达式 const f = function(){} 不会提升,必须先定义后使用;箭头函数 const f = () => {} 写法最简洁,但它没有自己的 this,这一点在处理对象方法时要特别注意。日常选择标准是:普通逻辑用箭头函数,需要动态 this 的场景(比如对象方法、事件回调里要指向触发元素)用 function 声明。
函数还有个容易被忽略但很实用的特性——参数默认值和剩余参数。function greet(name = "朋友") 让调用时可以不传参;function sum(...nums) 把不定数量的参数收成一个数组。这两个特性在处理配置项、工具函数时用得很多,早点熟悉能少写不少判断代码。
真实项目里,你处理的绝大部分数据都装在对象和数组里——接口返回的是对象数组,配置项是对象,列表数据是数组。把它们的高频操作练熟,写代码的速度会有明显变化。
取属性有两种写法:obj.name 和 obj["name"]。前者简洁,后者适合属性名存在变量里的情况。判断某个属性是否存在,用 "name" in obj 比直接读值更可靠,因为读值拿到 undefined 时,你分不清是「不存在」还是「存在但值就是 undefined」。
合并对象用扩展运算符最省事:const merged = { ...a, ...b },同名的键以后面的为准。解构赋值则是从对象里一次性取出多个值:const { name, age } = user,还能顺手改个名 const { name: userName } = user。这两招在函数参数里用得非常频繁,比如 function render({ title, items }) 直接解构配置对象,调用方传参顺序就不用记了。
增删方面,push 和 pop 操作尾部,unshift 和 shift 操作头部——注意操作头部性能较差,元素多的时候会明显变慢。查找方面,find 返回第一个匹配的元素,filter 返回所有匹配的元素组成的新数组,findIndex 返回位置。这里有个重要区别:这些方法都返回新数组,不会改动原数组,叫「不可变操作」;而 push、splice 会直接改原数组,叫「可变操作」。分清这两类,能避免很多「我明明没动它,怎么变了」的诡异 bug。
const list = [3, 8, 1, 9, 4];
const big = list.filter(n => n > 4); // [8, 9]
const doubled = big.map(n => n * 2); // [16, 18]
const total = doubled.reduce((s, n) => s + n, 0); // 34
console.log(big, doubled, total);
排序要单独提一句。sort() 默认按字符串比较,所以 [10, 9, 1].sort() 会得到 [1, 10, 9],因为 "1" 排在 "9" 前面。正确写法是传比较函数:list.sort((a, b) => a - b) 升序,(a, b) => b - a 降序。这个坑几乎每个人都会踩一次,提前知道能省半小时排查。
数组适合有序列表,但如果你的核心需求是「按 key 快速查」或「自动去重」,用 Map 和 Set 更合适。Set 里的值唯一,new Set([1,1,2]) 得到只有两个元素的集合,一行代码完成去重。Map 的键可以是任意类型,不像对象只能用字符串或 symbol。数据量大(通常上万条以上)且查找频繁时,Map 的查询效率优势会体现出来。
DOM 是浏览器把 HTML 解析成的一棵可编程的对象树,js 通过它读取和修改页面;事件处理则是「监听用户动作并执行对应函数」的机制。两者配合,就是一切网页交互的基础。
document.querySelector(".btn") 返回第一个匹配的元素,querySelectorAll(".btn") 返回全部(是个类数组,要用 forEach 遍历)。老代码里常见的 getElementById 依然可用,而且是性能最好的选择,因为它直接按 id 查。选择原则:能用 id 就用 id,需要复杂选择器时用 querySelector,别把 CSS 选择器写得过于绕,将来自己都看不懂。
选元素有个经典陷阱:如果在页面还没解析到目标元素时就执行 js,会拿到 null,然后报「Cannot read properties of null」。解决办法是把脚本放在 </body> 之前,或者用 DOMContentLoaded 事件包起来。这个报错新手几乎必然会遇到一次,记住原因下次就能秒解。
改文本用 textContent,改 HTML 结构用 innerHTML。两者的重要差别是:textContent 会把内容当纯文本处理,安全;innerHTML 会解析成 HTML,如果内容来自用户输入,就可能被注入恶意脚本。所以展示用户提交的内容时,一律用 textContent。改样式推荐操作 class:el.classList.add("active"),而不是直接写 el.style.color = "red"——把样式规则留在 CSS 里,js 只负责切换状态,代码更好维护。
绑定事件用 addEventListener,不要用 HTML 里的 onclick 属性,因为前者能绑多个、能被移除、也不会污染全局。事件有个机制叫冒泡:点了一个按钮,事件会一路向上传到它的所有父元素。利用这一点可以做「事件委托」——给外层容器绑一个监听,通过 event.target 判断实际点的是谁。列表项很多时,委托比给每一项都绑监听要高效得多。
const list = document.querySelector("#todo-list");
list.addEventListener("click", (e) => {
if (e.target.tagName === "BUTTON") {
e.target.closest("li").remove();
}
});
这段代码只绑了一个监听,却能处理列表里任意一个删除按钮,包括后来动态添加的。这就是事件委托的实际价值。另一个常用技巧是 event.preventDefault(),阻止表单提交或链接跳转的默认行为,做单页应用时天天要用。
js 是单线程的,一次只能执行一件事,遇到耗时操作(网络请求、文件读取)不能干等,于是把任务交出去、留下一个「完成后通知我」的约定,这就是异步。它的演进路线是回调 → Promise → async/await,写法越来越像同步代码。
先界定为什么需要异步。假设你发了一个网络请求,服务器要 500 毫秒才返回。如果是同步执行,浏览器这 500 毫秒就会完全卡住,用户点什么都没反应。异步的做法是:发起请求后立刻继续往下执行,等数据回来了再触发回调函数处理。所以异步的本质是「不阻塞主线程」,代价是代码的执行顺序不再是从上到下一条直线。
回调就是「把函数当参数传进去,将来再调用它」。写一层没问题,写三层以上就麻烦了——每一步都依赖上一步的结果,代码会层层缩进成金字塔形状,俗称「回调地狱」。更麻烦的是错误处理,每层都要单独判断,很容易漏掉某一层导致错误被静默吞掉。
Promise 是一个代表「未来会有结果」的对象,有三种状态:等待中、已成功、已失败。用 .then() 处理成功结果,.catch() 统一处理任何一环的错误。这样就解决了回调的两个痛点:链式调用不再嵌套,错误可以在末尾一处捕获。
fetch("/api/list")
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error("出错了:", err));
这段代码发起请求、解析 JSON、打印结果,任何一步出错都会跳到 catch。注意 fetch 有个反直觉的地方:服务器返回 404 或 500 时它不会走 catch,因为请求本身是成功完成的。要判断 HTTP 状态码,得自己检查 res.ok。这是新手最容易困惑的点之一。
async 函数总是返回 Promise,await 会暂停函数执行直到 Promise 有结果。错误处理改用 try / catch,跟同步代码的写法一致。多个互不依赖的请求可以用 Promise.all() 并行发出,总耗时取决于最慢的那个,而不是各自耗时相加——这是很实在的性能优化。
async function loadUser(id) {
try {
const res = await fetch(`/api/user/${id}`);
if (!res.ok) throw new Error("状态码 " + res.status);
return await res.json();
} catch (e) {
console.error("加载失败:", e.message);
return null;
}
}
还要理解一个概念:事件循环。js 把任务分成宏任务(setTimeout、事件回调)和微任务(Promise 的 then)。每一轮先清空所有微任务,再取一个宏任务执行。所以 setTimeout(fn, 0) 并不真的「立刻」执行,它会排在当前微任务之后。理解这一点,才能解释为什么某些代码的输出顺序和你看到的位置不一致。
代码写到几百行就会开始难受:变量重名、函数到处找、改一处崩三处。模块化就是把这些内容按职责切开,每块只暴露需要对外提供的东西。
用 export 导出、import 导入。可以导出多个具名成员,也可以有一个默认导出。具名导出适合工具函数集合,默认导出适合「一个文件就是一个类或一个主功能」的场景。浏览器里使用需要在 script 标签上写 type="module",Node.js 里则需要在 package.json 里声明 "type": "module" 或把文件后缀改成 .mjs。
// utils.js
export function formatDate(d) { return d.toISOString().slice(0, 10); }
export const MAX = 100;
// main.js
import { formatDate, MAX } from "./utils.js";
console.log(formatDate(new Date()), MAX);
npm 是 js 生态的包管理器,npm install 包名 把第三方库装到项目里。项目根目录的 package.json 记录依赖清单和可执行脚本,node_modules 存放实际代码。构建工具(如 Vite、webpack)负责把多个模块打包成浏览器能高效加载的文件,还能做代码压缩、热更新。新手不必一上来就研究它们的配置细节,先做到「能装包、能跑起来、能打包」三个动作就够了。
一个够用的小项目结构通常是:src 放源码,里面再按功能分 components(界面组件)、utils(工具函数)、api(接口封装);public 放静态资源;根目录放配置文件和说明文档。原则是「按功能分,不按文件类型分」——把某个功能的 js、css、模板放在一起,改动时不用满项目找。
关于版本管理,本文只提一条实用建议:package.json 里的依赖版本号,团队协作时建议提交 lock 文件,保证每个人装出来的版本完全一致。至于具体版本号该定在哪个范围,以各库官方文档的说明为准,本文不做统一推荐,因为不同项目的兼容性要求差别很大。
会调试比会写代码更能决定你的实际效率。报错信息不是敌人,它通常已经告诉你问题在哪一行、是什么类型的问题,只是需要翻译成人话。
最基础的 console.log 适合快速看变量值。当数据是数组或对象时,console.table() 会以表格形式展示,比展开嵌套对象直观得多。但真正解决复杂问题要靠断点:在开发者工具的 Sources 面板找到代码行,点一下加断点,刷新页面后代码会停在那里,你可以逐行执行、查看此刻每个变量的值。这比在十个地方插 log 高效得多,而且不用改代码。
第一类 TypeError: Cannot read properties of undefined,说明你访问了一个不存在对象的属性,常见原因是数据还没加载完就渲染了,或者拼错了键名。第二类 ReferenceError: xxx is not defined,变量没声明就用了,或者作用域搞错了。第三类 SyntaxError,语法写错了,通常伴随具体的行号,照着看就行。第四类 TypeError: xxx is not a function,你调用了一个不是函数的东西,多半是导入错了或者 API 用法记混了。第五类是不报错但结果不对——这类最难查,只能靠断点逐步缩小范围。
打开开发者工具的 Network 面板,刷新页面,能看到所有请求的状态码、耗时和返回内容。接口没返回数据时,先看状态码:404 是地址错了,401/403 是权限问题,500 是服务端出错,CORS 报错则是跨域被浏览器拦截。这一步能快速区分「是我的代码问题」还是「接口本身有问题」,避免在错误的方向上排查半小时。
还有一条经验值得记下来:改代码前先想清楚「我预期它输出什么」,然后对比实际输出。这个习惯能把模糊的「好像不太对」变成明确的「差在哪」。很多人调试慢,不是因为不会用工具,而是因为没想清楚自己要验证什么。
下面这份清单来自搜索引擎的相关搜索词及其近 30 天的搜索印象量。把它整理出来,是想让你看清一件事:搜「js」的人其实分成好几拨,需求差别很大。对照自己的目标,能更快判断该往哪个方向学。
这一组是全部相关词里量级最大的,说明「用 js 做后端」的需求非常集中,远超单纯的语法学习需求。
与「mc」(我的世界)相关的组合词占了很大比重,累计印象量可观,说明相当一部分搜索者找的是与游戏相关的 js 应用场景。
明确带「教程」意图的词量级不算最大,但转化意图最清晰,搜这个词的人多半已经决定要学了。
这一组的搜索者通常已经有明确的技术目标,是在找具体工具而不是找入门路径。
数据来源:搜索引擎相关搜索,近 30 天,仅供参考。热力条按各词印象量相对最高值归一化绘制,仅用于直观对比量级差异。
语法学完最容易陷入「然后呢」的迷茫。下面三个场景都是入门阶段就能动手做的,每个都写清楚:什么情况用、解决什么问题、能拿到什么结果。
用户在输入框里打字时,立刻判断格式对不对并给出提示,而不是等提交后才报错。用 input 事件监听输入,配合正则表达式判断,几十行代码就能完成。结果是用户少一次「提交失败重填」的挫败感。
调一个公开 API 拿到 JSON 数据,用 map 生成 HTML 片段插入页面,再配上加载中和错误状态。这是所有前端工作的缩影:拿数据、转格式、呈现界面。做完这个,你就摸到真实项目的门了。
用 Node.js 读取某个文件夹,按扩展名把文件分到不同子目录。不需要界面,但能第一次体会到「代码帮我省了重复劳动」的爽感,也是理解文件系统 API 的好练习。
不同基础、不同目标的人,适合的入口不一样。下面把常见的学习方式按「新手友好度」排个序,每项给出适用人群和注意点,你可以对照自己的情况挑一条。
先定一个想做的功能(比如待办清单),遇到不会的语法再去查。上手最快,成就感来得早,适合零基础又容易半途而废的人。
从变量、类型、函数一路学到异步和模块化。知识体系完整,不容易留死角,但前期反馈慢,需要一点耐心撑过前两周。
通过大量小题目熟悉语法和逻辑。适合已有其他语言基础、想快速熟悉 js 写法的人;纯零基础直接用会有点吃力。
跟着视频一步步敲,不容易卡住。风险是容易变成「看得懂但写不出」,建议每看完一节就关掉视频自己重写一遍。
不做系统学习,工作中碰到什么查什么。短期能解决问题,但知识结构会有大量空缺,一旦遇到没见过的场景就容易无从下手。
以上评分为本文基于学习者常见反馈给出的参考分值,用于横向比较,不代表任何第三方评测或统计数据。
把学习拆成阶段的意义在于,每个阶段都有明确的「通关标准」。达不到标准就往下走,后面的内容会越来越吃力。下面这套节奏按每天投入 1–2 小时设计,全程大约 3 个月。
目标是把变量、类型、运算符、条件、循环、函数写熟。通关标准:能不看资料写出一个判断成绩等级的函数和一个求数组平均值的函数。练习全部在浏览器控制台完成,不装任何工具。
掌握对象的解构合并、数组的 map/filter/find/reduce,学会选中元素、改内容、绑事件。通关标准:做出一个可增删的待办清单,刷新后数据能通过 localStorage 保留。
把回调、Promise、async/await 三条线打通,理解事件循环的基本顺序。通关标准:能独立调一个公开 API,处理加载中、成功、失败三种状态,并用 Promise.all 并行发多个请求。
学会用 import/export 拆分代码,会用 npm 装包、会跑构建命令。通关标准:把第二阶段的项目拆成至少三个模块,并成功打包出一份可部署的产物。
从需求梳理到上线跑通一个完整流程,包括数据存储、错误处理、基础样式和部署。通关标准:项目能通过公网访问,移动端打开布局正常。
集中学一到两个新概念,当天写至少 20 行验证代码。
把学到的知识用在一个具体小功能上,哪怕只有几十行。
重读本周写的代码,找出可以简化的地方,这是提升最快的一步。
把整块时间留给主线项目,保证每周都有可见进展。
这里用一个「带同步功能的待办清单」把前面所有知识点串起来。它足够小,一个人两三周能做完;又足够完整,覆盖了数据、界面、异步和部署。
不要一上来就写代码。先把功能拆成条目:新增待办、标记完成、删除待办、按状态筛选、数据本地保存、可选的服务端同步。每条都写清楚「什么操作触发、界面怎么变、数据怎么存」。这一步花半小时,能省下后面好几小时的返工。
第一版只做「新增 + 展示 + 删除」三个功能,数据只存在内存里。这个版本可能只有几十行代码,但能跑起来、能点、能看到效果。先跑通再完善,比一开始就设计完美架构要现实得多。很多初学者卡住,就是因为想一次写对,结果一行都没写完。
用 localStorage 把数组存起来。注意它只能存字符串,所以要用 JSON.stringify 转过去、JSON.parse 转回来,并且要处理「第一次打开时没有数据」的情况。这里还会遇到一个经典问题:页面刷新后数据要能恢复,所以读取逻辑必须放在渲染之前。
如果想让项目更完整,可以接一个免费的后端服务做数据同步。这一步正好练习异步:用 async/await 发请求,用 try/catch 处理失败,用加载状态给用户反馈。做完之后你会对「前端怎么和后端对话」有具体的理解,而不只是概念上的知道。
纯静态项目可以直接部署到静态托管平台,把打包产物上传即可。上线后要做的检查:手机上打开布局是否正常、控制台有没有报错、资源加载是否成功。第一次看到自己的项目有公开网址,那种感觉和本地跑起来完全不一样。
最小版本约 8 小时,完整版含同步与部署约 25–35 小时。按每天 1 小时算,三到四周可完成。
数组方法、对象、DOM 操作、事件委托、localStorage、异步请求、模块拆分,基本把前四阶段的内容全用上。
加分类标签、加截止日期提醒、加拖拽排序、改造成多人协作。做完基础版后,随便挑一个方向继续都能学到新东西。
走完基础路线后,面前会出现好几条岔路。这里只做方向性介绍,帮你判断哪条更贴合自己的目标,具体技术选型请以各官方文档为准。
主流框架解决的是同一类问题——数据变了怎么高效更新界面。手写 DOM 操作在页面复杂后会很痛苦,框架用声明式的方式让你只描述「界面应该长什么样」。选择哪个框架看团队和岗位需求,但建议先把手写 DOM 练熟再上手框架,否则容易只会用框架、不知道框架在帮你做什么。
Node.js 生态里有做接口服务的、做实时通信的、做数据库操作的各类库。学这条路要补的知识不止 js,还有 HTTP 协议、数据库基础、服务器部署。好处是前后端用同一门语言,思考负担小很多。
如果你喜欢研究「怎么让团队写代码更顺」,可以往这个方向走。涉及打包、代码转换、静态检查、自动化测试、持续集成。这类工作不直接产出界面,但决定了整个项目的开发效率。
数据可视化、图表、3D 场景都建立在这条线上。需要补一些数学和图形学基础,但产出非常直观,做出来的东西一眼就能看出好坏。
TypeScript 给 js 加上了静态类型检查,在大型项目里能提前发现大量错误。它不是另一门语言,而是 js 的超集。建议在能熟练写 js 之后再学,否则会分不清哪些限制来自语言本身、哪些来自类型系统。
最后提醒一句:不要同时开五条线。选一条深耕三个月,比每条都浅尝一遍收获大得多。技术方向之间有很多共通概念,把一条走通之后,横向扩展会比现在容易很多。
内容由几位长期在一线写代码、也长期带新人的开发者共同整理。每篇内容发布前会经过一次技术复核,确保示例代码能跑通。
陈序
内容主编
八年前端经验,负责 js 基础与 DOM 板块的内容框架设计。
林知远
技术复核
专注异步模型与运行机制,负责异步编程与调试章节的逐行校验。
苏禾
项目顾问
负责实战项目章节的流程设计与阶段目标拆解,常年带新人入门。
以上为用于说明内容分工的虚拟角色,不代表真实履历或所属机构。
下面这些问题来自学习群里出现频率最高的提问。每条先给一句直接结论,再补充细节和注意事项。
可以,但建议先花 3–5 天把 HTML 和 CSS 的常用标签与选择器过一遍。原因很实际:js 做网页交互时要操作的元素,都是 HTML 写出来的;你要改的样式,都是 CSS 定义的。完全不懂这两样,学 DOM 那章会觉得每个词都陌生。不过不需要学得很深,能看懂基础结构和 class 命名就够开始。另外提醒一句,如果你完全从零开始,第一周不要急着装 Node.js,先用浏览器控制台把语法练熟,环境配置的挫败感劝退过太多人。
粗略的参考线是:能独立完成一个包含数据请求、状态管理、错误处理与基础样式的项目,并且能讲清楚代码里每个决定的原因。具体一点,通常需要掌握 ES6 常用语法、异步编程、至少一个框架、基础工程化流程,累计投入大概 400–800 小时。这个区间跨度大是因为岗位要求差别很大,初级岗更看重基础和潜力,不一定要求框架用得多熟。比起纠结时长,更该关注的是「能不能独立解决问题」——遇到没见过的报错,你能不能自己定位并修复,这才是面试官真正在意的。
九成以上的情况是环境差异或版本差异,不是你写错了。常见原因有三个:一是教程用的是老版本语法,比如还在用 var 或旧的 API 写法;二是教程里的代码依赖某个库,而你没装;三是代码位置不对,比如在 DOM 元素还没加载时就操作它。排查方法很直接——把报错信息复制到搜索框,看第一行说的是什么类型错误,再对照本文调试那一节逐类排除。养成「先读报错再动手改」的习惯,能省下大量瞎试的时间。
它们是同一套机制的不同写法,async/await 建立在 Promise 之上。用哪个取决于场景:多个请求需要并行时,用 Promise.all() 更直观;单一流程按顺序执行时,async/await 读起来更接近同步代码。日常建议默认用 async/await,遇到需要同时发多个互不依赖的请求时再切回 Promise 组合方法。有一点要注意,用 await 写在循环里会导致串行执行,如果每次请求耗时 200 毫秒、循环 10 次,总耗时就接近 2 秒;改成并行后总耗时接近单次。这个差别在真实项目里非常明显。
需要记的比想象中少。真正要形成肌肉记忆的,是变量声明、条件循环、函数写法、数组常用方法这几类,因为它们几乎每段代码都会出现。至于具体 API 的参数顺序、某个方法的返回值类型,随时查文档完全可以。判断标准很简单:如果一个东西你每周都要用十几次,自然会记住;如果一个月才用一次,查一下更快也更准。把精力花在理解「为什么这样设计」上,比死记硬背划算得多。
建议先打牢原生 js 再上手。原因不是框架不好,而是框架帮你封装了很多细节,如果不知道底层发生了什么,调试时会完全无从下手。比较合理的顺序是:能用手写 DOM 完成一个交互页面,再学框架。这样你在用框架时,遇到问题能想到「它大概是在这个环节出错了」,而不是只能盲猜。反过来,如果你的目标岗位明确要求某个框架,也可以边学框架边补原生,只是要刻意花时间理解框架背后的机制。
按每天一小时算,走到「能独立做出带数据交互的小项目」大概需要 10–14 周。这个估算的前提是这一小时是有效投入——真的在写代码、在调试,而不是只看视频。如果只是被动看教程,实际效率可能只有动手的三分之一。给一个实用建议:把每天的一小时拆成「20 分钟学新知识 + 40 分钟动手写」,动手部分必须占大头。另外周末如果能挤出两三个小时连贯时间,用来推进主线项目,整体进度会明显加快,因为碎片时间很难进入深度状态。
用「从外到内、从结果反推」的方法。先看这段代码整体在做什么(函数名和注释通常会告诉你),再逐行看每一步产生了什么中间结果。最有效的做法是把代码复制到控制台,在关键位置加 console.log 打印中间值,看它实际算出什么。如果还看不懂,就把代码拆成更小的片段单独运行,一次只验证一个环节。这个方法看着笨,但比盯着代码冥思苦想快得多,也是很多资深开发者的实际做法。理解一段代码的标志不是「看着眼熟」,而是「能预测改动某一处会发生什么」。
以上解答基于公开标准与教学场景中的常见反馈整理。涉及具体版本行为时,请以各运行时与库的官方文档为准;本文不提供任何未授权资源、破解工具或绕过版权限制的方法。
打开浏览器,按 F12,在控制台里敲一句 console.log("开始学 js"),回车。这就是你的第一行代码。剩下的路,照着上面的阶段一段段走就好。
读者评论与学习反馈
下面这些留言来自读过这份指南的朋友。整理出来是想让你看到:大家卡住的地方其实高度相似,你遇到的困难并不特殊。
异步那节真的救了我。之前一直搞不懂为什么 fetch 拿到的不是数据本身,看完才明白返回的是 Promise,要等 then 或者 await。建议新手卡在这里的直接从事件循环那段看起,比硬背语法管用。
路线图那个阶段划分很实用。我原来就是东学一点西学一点,学了两周还是不知道自己在哪个位置。按这个阶段走之后,至少知道这周该干什么、什么时候算通关了。
调试那节讲得比我上课听的清楚多了。尤其是那五类报错分类,我照着排查了一次,果然就是访问了 undefined 的属性,之前一直瞎改。
想问下,实战项目里说的那个免费后端服务有没有推荐的方向?我做到第四步卡住了,不知道该接什么。
DOM 那节的「事件委托」例子我照着敲了一遍,一开始没理解为什么只绑一个监听就能管所有按钮,后来自己加了几个动态生成的项才反应过来。这种讲法很好,不直接给答案。
已经工作两年了,回头看这些基础还是有不少收获。特别是 sort 那个坑,我当年确实踩过,当时排查了快一小时才找到原因。
从会计转过来学 js,一开始真的很难,光是理解「函数是一等公民」就想了两天。现在做到待办清单这一步,终于有点感觉了。谢谢这份指南,比纯视频教程能沉得下心。
求更新进阶方向那部分的细节,比如 TypeScript 具体什么时候开始学比较合适,现在有点纠结是先把项目做完还是先补类型。
以上评论为读者反馈内容的整理展示,点赞与回复数为站内互动记录的示意数值,仅用于呈现讨论热度。