JavaScript基础
前言
随着多年的发展,Node.js覆盖了从业务开发到工程基础设施的完整链路:Web 后端,WebSocket,CLI 工具等等, 甚至连当今流行 WEB3工程与AI 工程都与其有深深关联
基础概念
nodeJs 各技术点如下
- JavaScript (JS):编程语言
- ECMAScript (ES):JS 的标准规范(例如 ES5、ES2015、ES2020…)
- V8:JavaScript 引擎(负责执行 JS)
- Chrome:浏览器运行时(V8 + Web API,如 DOM)
- Node.js:服务端/命令行运行时(V8 + Node API,如
fs、process) - TypeScript (TS):JS 超集(微软参考ES规范并增加类型系统,最终编译成 JS)
- npm:Node.js 包管理器(装依赖、跑脚本、发包)
- nvm:Node 版本管理器(多版本切换)
结合依赖关系开发者可通过
- nvm选择nodejs的版本
- 通过npm管理依赖包
- 再结合代码要运行的环境,选择对应的编译工具
ES5、ES6、ES2020 到底是什么?
它们是 ECMAScript 标准的版本名,不是不同语言。
- ES5(2009):传统老项目常见基线。
- ES6 = ES2015:现代 JS 分水岭(let/const、class、模块、Promise)。
- 后续按年份命名:ES2016、ES2017、ES2020、ES2025…
注意:浏览器/Node 不是“按 ES 整包支持”,而是“按特性逐步支持”。
所以工程里会说“某特性兼容性如何”,而不是简单说“支持 ES2025 ”。
浏览器和 Node 是不是同一个环境?
不是。 但它们是“同级别”:都是 JS 运行时。
- 浏览器有:window、document、DOM、同源策略。
- Node 有:fs、path、process、文件系统/进程能力。
发展历史
| 技术 | 大致时间 | 为什么出现 | 主要解决问题 |
|---|---|---|---|
| JavaScript | 1995 | 网页需要交互,不想每次都依赖后端刷新 | 前端动态交互能力 |
| ECMAScript | 1997 | 各浏览器实现差异大,生态混乱 | 给 JS 一个统一标准 |
| V8 | 2008 | JS 性能瓶颈,难支撑复杂应用 | 高性能执行(JIT) |
| Chrome | 2008 | 浏览器性能/稳定性不够 | 更快更稳的浏览器运行时 |
| Node.js | 2009 | JS 只能在浏览器跑,前后端割裂 | JS 进入服务端/CLI,I/O 友好 |
| npm | 2009 后快速发展 | Node 缺统一依赖生态 | 包分发、版本管理、复用 |
| nvm(社区工具) | 2011 前后 | Node 版本冲突严重 | 一机多版本切换 |
| TypeScript | 2012 | 大型 JS 项目可维护性差 | 类型检查、重构安全、工程化 |
ECMAScript 语法的发展历史
可以把历史粗暴分成这几代:
-
ES3(1999)
老 JS 基线,核心就是 var、function、原型、对象/数组字面量、正则、try/catch。这就是很长一段时间里大家印象中的“老JavaScript”。 -
ES5(2009)
开始现代化,但语法变化不算爆炸。 开发者最常接触的是:- “use strict”
- Array.prototype.forEach/map/filter
- JSON
- Object.defineProperty
- getter/setter
-
ES2015 / ES6(2015)
真正的大分水岭,现代 JS 基本盘就是这一版建立的:- let / const
- 箭头函数 () => {}
- 模板字符串
... - 解构赋值
- 默认参数
- class
- for…of
- Promise
- import / export
- generator function*
-
ES2016
比较小,但有一个很常见:- 幂运算 **
-
ES2017
异步编程进入现代写法:- async / await
-
ES2018
继续补强:- 对象展开/剩余 …obj
- for await…of
-
ES2019
小改动为主:- catch 可以不写参数
-
ES2020
这一版开发者体感很强:- 可选链 ?.
- 空值合并 ??
- 动态导入 import()
- import.meta
- BigInt,比如 123n
-
ES2021
一些很实用的小语法:- 逻辑赋值 &&= ||= ??=
- 数字分隔符 1_000_000
-
ES2022
类相关能力补齐很多:- class field
- 私有字段 #x
- static {} 块
- top-level await
CommonJS 是为了解决早期 (ES3,ES5)JS 缺乏模块化标准而产生的社区方案;ES6 则把模块化正式纳入了语言标准。
TypeScript
typeScript的编译打包过程如下
src/*.ts
|
| 1. 开发期 / 检查期
v
tsconfig.json
- 规定 TS 怎么理解代码
- 规定模块规则(NodeNext)
- 规定类型检查严格度
- 规定路径别名
- 这里 noEmit: true,所以不负责输出文件
|
| 2. 构建期
v
tsdown.config.ts
- 决定哪些入口要被打包
- 决定输出到 dist/
- 决定产物格式、扩展名等
|
v
dist/*.js 或 dist/*.mjs
- tsc可以说是typescript的官方编译器负责“怎么检查和理解 TS,它通过tsconfig.json指编译路径
- tsdown称为构建器,负责“怎么把 TS 构建成可运行产物。它通过tsdown.config.ts打包产物规则
tsc 产出 ESM 语法,不等于它一定产出 .mjs 文件名
在 module: “node16” / “nodenext” 下,foo.ts 依然通常是 foo.js;只是这个 foo.js 里面到底是 ESM 还是 CJS,TypeScript 会结合最近的 package.json 里的 “type” 去决定。
tsc的工作分为类型检查和转译,其中转译部分被“转译器”给替代了。这方法称为“bundler 路线”
.ts -> esbuild/tsup/tsdown/rollup-> 直接 dist/*.js / *.mjs
- 构建流程以 esbuild / rollup / webpack / tsup / tsdown 这类 bundler 为核心,而不是以 tsc 为核心。
- tsc的语法检查做为弱依赖,即使不用 tsc,bundler 也照样会报很多语法级错误
虽然bundler 做为 负责“主构建产物” , 但它仍会以tsconfig.json 作为配置文件使用,tsconfig.json 则作为这两类工具共享的底层 TS 规则
npm pack 与tsdown的区别
- tsdown 的构建:把源码编成发布/运行用的产物
- npm pack:把当前包打成一个 .tgz 压缩包
tsx
tsx 是一个 直接运行 TypeScript 的 Node 工具。它名字就来自 TypeScript Execute。你可以把它理解成:
- node:运行 .js
- tsx:运行 .ts / .tsx
它主要做的事是:
- 让你不用先手动 tsc 编译
- 直接执行 TypeScript 文件
- 支持 ESM / CJS 混用
- 支持 watch 模式
- 支持 node --import tsx, 导入模块
它底层用 esbuild 来转换 TS/ESM,所以跑得快。tsx 不是完整类型检查器,官方文档明确说它不会自己做 type check
NPM基础
npm做为node项目的工程管理工具,是必须了解的.可以从package.json切入,以下是核心字段
- name:包唯一标识;这里是 openclaw,影响 npm install openclaw、node_modules/openclaw、打包名(如 openclaw-版本.tgz)。
- version:当前版本号;这里是 2026.2.25,用于发布、依赖解析、产物命名和升级判断。
- description:包简介;用于 npm/GitHub 展示,不影响运行逻辑。
- keywords:检索关键词;作用在npm search。
- homepage:项目主页链接;展示/跳转用途。
- bugs:问题反馈地址;npm 页面会用它指向 issue 入口。
- license:许可证声明;合规和开源分发所需。
- author:作者信息;当前为空,纯元数据。
- repository:源码仓库信息;用于溯源、npm 展示、工具联动。
- bin:CLI 命令映射;这里把 openclaw 命令绑定到 openclaw.mjs,安装后可直接执行 openclaw。
- files:发布白名单;决定 npm publish 时哪些文件/目录会被打进包(比如 dist/、extensions/、docs/ 等)。
- type:模块系统声明;这里是 “module”,表示 .js 按 ESM 规则解析(用 import/export)。没写按 CommonJS 解析(用 require/module.exports)
- main:默认入口; exports字段的默认值
- exports:对外导出映射;外部可 import 的入口和类型声明路径。如果没有exports 没值,走main字段
- scripts:项目命令入口;这里集中定义了构建、开发、测试、发布检查等命令.
- dependencies:生产依赖;运行时必须安装(。
- devDependencies:开发依赖;用于类型检查、测试、lint、构建工具(如 typescript、vitest、oxlint)。
- peerDependencies:宿主依赖约束;就是运行时依赖,即provided依赖
- optionalDependencies:可选依赖;安装失败不一定阻塞整体安装。
- engines:运行时版本要求;这里要求 Node >=22.12.0,低版本可能告警/不兼容。
- packageManager:推荐包管理器及版本;这里是 pnpm@10.23.0,用于团队环境一致性
- pnpm 字段是 pnpm 专用配置区,用于控制安装策略和依赖解析规则。它只对 pnpm 生效,npm 基本会忽略。
与maven的对比
| 功能维度 | npm(package.json) | Maven(pom.xml) | 主要差异 |
|---|---|---|---|
| 项目身份与版本 | name, version | groupId, artifactId, version | npm 常以 name 作为安装名;Maven 用 GAV 坐标唯一标识 |
| 项目展示与溯源 | description, keywords, homepage, bugs, author, repository, license | name, description, url, issueManagement, developers, scm, licenses | Maven 元数据更结构化;npm 更偏页面展示/搜索 |
| 依赖管理 | dependencies, devDependencies, peerDependencies, optionalDependencies | + scope(compile/test/provided/runtime)+ optional | npm 有 peerDependencies(宿主约束)这一强语义 |
| 构建与命令编排 | scripts | 生命周期 + 插件(mvn test/package/deploy) | npm 更自由脚本化;Maven 更标准流程化 |
| 发布内容控制 | files(发布白名单) | 插件 includes/excludes(jar/assembly/shade 等) | npm 直接白名单;Maven 多靠插件配置 |
| 包入口与导出 | main, exports | 无直接等价字段 | npm 原生定义包导出边界;Maven 不在 POM 定义模块导出 |
| CLI 暴露 | bin | 无直接等价(通常借助插件/打包脚本) | npm 对命令行入口是原生能力 |
| 运行环境约束 | engines | Enforcer/Toolchain/Compiler 插件配置 | npm 字段直观;Maven 常靠插件规则 |
| 包管理器一致性 | packageManager(如 pnpm@10.x) | Maven Wrapper(mvnw) | 都可锁工具版本,但机制不同 |
生命周期
npm 内置命令触发(生命周期)
- npm install / npm ci 常见顺序:
preinstall -> install -> postinstall -> preprepare -> prepare -> postprepare - npm pack:
prepack -> prepare -> postpack - npm publish:
prepublishOnly -> prepack -> prepare -> postpack -> publish -> postpublish - npm start:
prestart -> start -> poststart - npm test:
pretest -> test -> posttest
你可以在scripts中去定义额外的命令,例如
{
"scripts": {
"preinstall": "echo preinstall",
"install": "echo install",
"postinstall": "echo postinstall"
}
}
执行 npm install 时会按顺序触发这些脚本。注意:
- 你定义的 install 脚本是“附加钩子”,不是替代安装逻辑
- 如果你的 install 脚本报错退出(非 0),整个 npm install 会失败。
手动命令
除了内置生命周期, npm支持手动命令
- npm run <script>
- pnpm <script>
其也支持对应的勾子规则:pre<name> -> <name> -> post<name>
- 你执行 npm run build
- 若存在会按 prebuild -> build -> postbuild
CLI 命令
bin + shebang 一起工作,可以让运行js脚本和运行本地shell脚本一样的效果。例子如下
test
├── hello-shebang.js
└── package.json
## package.json
{
"name": "shebang-bin-demo",
"version": "1.0.0",
"private": true,
"type": "module",
"bin": {
"hello-shebang": "./hello-shebang.js"
}
}
## hello-shebang.js
#!/usr/bin/env node
console.log("Hello, world from shebang!");
#!/usr/bin/env node是shebang , 看到 shebang 后,用 node 去执行这个脚本- package.json中的"bin", 把 “./hello-shebang.js” 转化为hello-shebang 命令
- npm link 负责 把 hello-shebang 放到npm全局bin的目录中
- npm全局bin的目录 在安装npm的时候已经加入到path中
总之的分工是:
- shell:负责在 PATH 里找到 hello-shebang
- 内核:看到 shebang 后,用 node 去执行这个脚本
模块规范
ES Modules 在 ECMAScript 2015 里正式成为标准。但也没有放弃之前的commonJs.那如何区分两都,NODEJS约定
- .js:可能是 CommonJS,也可能是 ESM,要看项目配置
- .mjs:明确就是 ESM,
- .cjs:明确就是 CommonJS
即
- mjs文件,按ESM 处理,通常写 import / export
- cjs文件,按CommonJS处理,通常写 require / module.exports
- js文件,则对通过package.json中的type处理
package.json 里的 type,本质上是 Node.js 用来判断 .js 文件按什么模块格式加载 的字段
- “type”: “module”
这包下面的 .js 按 ESM 处理,通常写 import / export - “type”: “commonjs”
这包下面的 .js 按 CommonJS 处理,通常写 require / module.exports
TODO
更多推荐

所有评论(0)