Node.js入门

Node.js 让 JavaScript 走出了浏览器,成为了一门真正的全栈语言。从服务端开发到命令行工具,Node.js 的应用场景极其广泛。

什么是 Node.js

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。

Node.js 的特点

特性 说明 优势
事件驱动 基于事件循环模型 高并发处理能力
非阻塞 I/O 异步操作不阻塞主线程 适合 I/O 密集型应用
单线程 主线程单线程执行 无锁竞争,简单可靠
跨平台 支持 Windows、Linux、macOS 一次编写,到处运行
NPM 生态 世界上最大的包管理器 海量开源资源

Node.js vs 浏览器

// 浏览器环境
console.log(window);      // 全局对象
console.log(document);    // DOM 操作
console.log(alert);       // 用户交互

// Node.js 环境
console.log(global);      // 全局对象
console.log(process);     // 进程信息
console.log(Buffer);      // 二进制数据处理

💡 东巴文提示:Node.js 和浏览器都是 JavaScript 的运行环境,但它们提供的 API 不同。浏览器专注于页面交互,Node.js 专注于系统操作。

安装与配置

安装 Node.js

# 使用 nvm 安装(推荐)
nvm install --lts        # 安装 LTS 版本
nvm use --lts            # 使用 LTS 版本
nvm ls                   # 查看已安装版本

# 验证安装
node -v                  # 查看 Node.js 版本
npm -v                   # 查看 npm 版本

版本选择

版本类型 说明 适用场景
LTS(长期支持版) 稳定,维护周期长 生产环境
Current(当前版) 最新特性,更新频繁 开发测试
奇数版本 实验性功能 尝鲜体验

REPL 交互环境

REPL(Read-Eval-Print-Loop)是 Node.js 的交互式解释器。

// 启动 REPL
// $ node

// 基本运算
> 1 + 1
2

// 变量定义
> const name = '东巴文'
undefined
> name
'东巴文'

// 多行输入
> function add(a, b) {
...   return a + b
... }
undefined
> add(1, 2)
3

// 特殊命令
> .help     // 显示帮助
> .exit     // 退出 REPL
> .clear    // 清除上下文

模块系统

Node.js 采用 CommonJS 模块规范。

导出模块

// 方式一:exports 对象
// math.js
exports.add = function(a, b) {
  return a + b
}

exports.subtract = function(a, b) {
  return a - b
}

exports.PI = 3.14159

// 方式二:module.exports
// calculator.js
class Calculator {
  add(a, b) { return a + b }
  subtract(a, b) { return a - b }
  multiply(a, b) { return a * b }
  divide(a, b) { return a / b }
}

module.exports = Calculator

// 也可以导出对象
module.exports = {
  name: '东巴文计算器',
  version: '1.0.0',
  Calculator
}

导入模块

// 导入核心模块
const fs = require('fs')
const path = require('path')
const http = require('http')

// 导入自定义模块
const math = require('./math')
const Calculator = require('./calculator')

// 使用导入的模块
console.log(math.add(1, 2))        // 3
console.log(math.PI)               // 3.14159

const calc = new Calculator()
console.log(calc.multiply(3, 4))   // 12

// 解构导入
const { add, subtract, PI } = require('./math')

模块查找机制

// 模块查找顺序
require('./utils')        // 1. 查找相对路径
require('lodash')         // 2. 查找 node_modules
require('fs')             // 3. 查找核心模块

// 查找 node_modules 的顺序
// 项目/node_modules/lodash
// 项目/node_modules/../node_modules/lodash
// ...直到根目录
// 全局 node_modules

核心模块

fs 文件系统

const fs = require('fs')
const path = require('path')

// 同步读取
const data = fs.readFileSync('./file.txt', 'utf-8')
console.log(data)

// 异步读取(回调)
fs.readFile('./file.txt', 'utf-8', (err, data) => {
  if (err) throw err
  console.log(data)
})

// Promise 版本
const fsPromises = require('fs').promises

async function readFile() {
  try {
    const data = await fsPromises.readFile('./file.txt', 'utf-8')
    console.log(data)
  } catch (err) {
    console.error('读取失败:', err)
  }
}

// 写入文件
fs.writeFileSync('./output.txt', '东巴文内容')

// 追加内容
fs.appendFileSync('./output.txt', '\n新增行')

// 检查文件是否存在
fs.existsSync('./file.txt')

// 获取文件信息
const stats = fs.statSync('./file.txt')
console.log(stats.isFile())     // 是否是文件
console.log(stats.isDirectory()) // 是否是目录
console.log(stats.size)         // 文件大小

// 创建目录
fs.mkdirSync('./new-dir', { recursive: true })

// 读取目录
const files = fs.readdirSync('./')
console.log(files)

// 删除文件
fs.unlinkSync('./file.txt')

// 删除目录
fs.rmdirSync('./empty-dir')

path 路径处理

const path = require('path')

// 路径拼接
const fullPath = path.join('/home', 'user', 'documents', 'file.txt')
console.log(fullPath)  // /home/user/documents/file.txt

// 获取文件名
console.log(path.basename('/home/user/file.txt'))     // file.txt
console.log(path.basename('/home/user/file.txt', '.txt')) // file

// 获取目录名
console.log(path.dirname('/home/user/file.txt'))      // /home/user

// 获取扩展名
console.log(path.extname('file.txt'))                 // .txt
console.log(path.extname('file.tar.gz'))              // .gz

// 解析路径
const parsed = path.parse('/home/user/file.txt')
console.log(parsed)
// {
//   root: '/',
//   dir: '/home/user',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file'
// }

// 绝对路径
console.log(path.resolve('./file.txt'))
console.log(path.resolve('/home', 'user', 'file.txt'))

// 规范化路径
console.log(path.normalize('/home/../user/./file.txt')) // /user/file.txt

// 相对路径
console.log(path.relative('/home/user', '/home/admin')) // ../admin

// 当前工作目录
console.log(process.cwd())

// __dirname 和 __filename
console.log(__dirname)   // 当前文件所在目录
console.log(__filename)  // 当前文件完整路径

http 服务器

const http = require('http')

// 创建服务器
const server = http.createServer((req, res) => {
  // 请求信息
  console.log(req.method)    // 请求方法
  console.log(req.url)       // 请求路径
  console.log(req.headers)   // 请求头

  // 设置响应头
  res.writeHead(200, {
    'Content-Type': 'text/html; charset=utf-8'
  })

  // 发送响应
  res.end('<h1>东巴文欢迎你</h1>')
})

// 监听端口
server.listen(3000, () => {
  console.log('服务器运行在 http://localhost:3000')
})

// 处理不同的路由
const server2 = http.createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`)
  
  res.writeHead(200, { 'Content-Type': 'application/json' })
  
  if (url.pathname === '/') {
    res.end(JSON.stringify({ message: '首页' }))
  } else if (url.pathname === '/api/users') {
    res.end(JSON.stringify({ users: ['东巴文', '用户A', '用户B'] }))
  } else if (url.pathname === '/api/query') {
    const name = url.searchParams.get('name')
    res.end(JSON.stringify({ name }))
  } else {
    res.writeHead(404)
    res.end(JSON.stringify({ error: '未找到' }))
  }
})

server2.listen(3001)

events 事件模块

const EventEmitter = require('events')

// 创建事件发射器
class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

// 监听事件
myEmitter.on('event', (data) => {
  console.log('事件触发:', data)
})

// 只监听一次
myEmitter.once('once', () => {
  console.log('只执行一次')
})

// 触发事件
myEmitter.emit('event', '东巴文数据')
myEmitter.emit('once')
myEmitter.emit('once')  // 不会再次触发

// 移除监听
const callback = () => console.log('回调')
myEmitter.on('test', callback)
myEmitter.off('test', callback)

// 获取监听器数量
console.log(myEmitter.listenerCount('event'))

process 全局对象

// 进程信息
console.log(process.pid)           // 进程 ID
console.log(process.ppid)          // 父进程 ID
console.log(process.platform)      // 操作系统平台
console.log(process.arch)          // CPU 架构
console.log(process.version)       // Node.js 版本
console.log(process.versions)      // 各组件版本

// 环境变量
console.log(process.env)           // 所有环境变量
console.log(process.env.NODE_ENV)  // NODE_ENV
console.log(process.env.PATH)      // PATH

// 设置环境变量
process.env.MY_VAR = '东巴文'

// 命令行参数
console.log(process.argv)
// ['node路径', '脚本路径', '参数1', '参数2']

// 解析参数
const args = process.argv.slice(2)
console.log(args)

// 当前工作目录
console.log(process.cwd())

// 退出进程
process.exit(0)   // 正常退出
process.exit(1)   // 异常退出

// 退出钩子
process.on('exit', (code) => {
  console.log('进程退出,代码:', code)
})

// 未捕获异常
process.on('uncaughtException', (err) => {
  console.error('未捕获异常:', err)
  process.exit(1)
})

// 未处理的 Promise 拒绝
process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的拒绝:', reason)
})

// 内存使用
console.log(process.memoryUsage())
// {
//   rss: 常驻内存,
//   heapTotal: 堆总量,
//   heapUsed: 堆使用量,
//   external: 外部内存
// }

// 下一个事件循环
process.nextTick(() => {
  console.log('下一个 tick')
})

Buffer 缓冲区

// 创建 Buffer
const buf1 = Buffer.alloc(10)           // 分配 10 字节,填充 0
const buf2 = Buffer.allocUnsafe(10)     // 分配 10 字节,不初始化
const buf3 = Buffer.from('东巴文')       // 从字符串创建
const buf4 = Buffer.from([1, 2, 3])     // 从数组创建

// 写入数据
buf1.write('Hello')
console.log(buf1.toString())  // Hello

// 读取数据
console.log(buf3.toString())  // 东巴文
console.log(buf3.toString('utf-8'))

// Buffer 操作
const buf5 = Buffer.from('Hello ')
const buf6 = Buffer.from('World')
const buf7 = Buffer.concat([buf5, buf6])
console.log(buf7.toString())  // Hello World

// Buffer 比较
console.log(buf5.equals(buf6))        // false
console.log(buf5.compare(buf6))       // -1(buf5 < buf6)

// Buffer 切片
const buf8 = Buffer.from('东巴文教程')
console.log(buf8.slice(0, 9).toString())  // 东巴文

// Buffer 长度
console.log(buf3.length)  // 字节长度
console.log(Buffer.byteLength('东巴文'))  // 9

// Buffer 与 JSON
const json = JSON.stringify(buf3)
console.log(json)  // {"type":"Buffer","data":[...]}
console.log(Buffer.from(JSON.parse(json)))

Stream 流

const fs = require('fs')
const { Readable, Writable, Transform, pipeline } = require('stream')

// 可读流
const readStream = fs.createReadStream('./large-file.txt', {
  encoding: 'utf-8',
  highWaterMark: 64 * 1024  // 64KB 缓冲区
})

readStream.on('data', (chunk) => {
  console.log('读取到数据:', chunk.length)
})

readStream.on('end', () => {
  console.log('读取完成')
})

readStream.on('error', (err) => {
  console.error('读取错误:', err)
})

// 可写流
const writeStream = fs.createWriteStream('./output.txt')

writeStream.write('第一行\n')
writeStream.write('第二行\n')
writeStream.end()  // 结束写入

writeStream.on('finish', () => {
  console.log('写入完成')
})

// 管道操作
const readStream2 = fs.createReadStream('./input.txt')
const writeStream2 = fs.createWriteStream('./output.txt')

readStream2.pipe(writeStream2)

// 转换流
const upperCase = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase())
    callback()
  }
})

// 链式管道
fs.createReadStream('./input.txt')
  .pipe(upperCase)
  .pipe(fs.createWriteStream('./output-upper.txt'))

// pipeline(推荐)
pipeline(
  fs.createReadStream('./input.txt'),
  upperCase,
  fs.createWriteStream('./output.txt'),
  (err) => {
    if (err) {
      console.error('管道错误:', err)
    } else {
      console.log('管道完成')
    }
  }
)

// 自定义可读流
class MyReadable extends Readable {
  constructor(options) {
    super(options)
    this.data = ['东', '巴', '文']
    this.index = 0
  }

  _read(size) {
    if (this.index < this.data.length) {
      this.push(this.data[this.index])
      this.index++
    } else {
      this.push(null)  // 结束
    }
  }
}

const myReadable = new MyReadable()
myReadable.on('data', (chunk) => {
  console.log(chunk.toString())
})

实战:简单的 CLI 工具

#!/usr/bin/env node
// cli.js

const fs = require('fs')
const path = require('path')

// 解析命令行参数
const args = process.argv.slice(2)
const command = args[0]
const params = args.slice(1)

// 帮助信息
function showHelp() {
  console.log(`
东巴文 CLI 工具

用法:
  node cli.js <command> [options]

命令:
  init <name>    创建新项目
  list           列出当前目录文件
  read <file>    读取文件内容
  help           显示帮助信息
  `)
}

// 创建项目
function initProject(name) {
  const dir = path.join(process.cwd(), name)
  
  if (fs.existsSync(dir)) {
    console.log(`目录 ${name} 已存在`)
    return
  }
  
  fs.mkdirSync(dir)
  fs.mkdirSync(path.join(dir, 'src'))
  
  const packageJson = {
    name,
    version: '1.0.0',
    main: 'src/index.js'
  }
  
  fs.writeFileSync(
    path.join(dir, 'package.json'),
    JSON.stringify(packageJson, null, 2)
  )
  
  fs.writeFileSync(
    path.join(dir, 'src', 'index.js'),
    `console.log('东巴文 ${name}')`
  )
  
  console.log(`项目 ${name} 创建成功`)
}

// 列出文件
function listFiles() {
  const files = fs.readdirSync(process.cwd())
  files.forEach(file => {
    const stats = fs.statSync(file)
    const type = stats.isDirectory() ? '📁' : '📄'
    console.log(`${type} ${file}`)
  })
}

// 读取文件
function readFile(filename) {
  const filepath = path.join(process.cwd(), filename)
  
  if (!fs.existsSync(filepath)) {
    console.log(`文件 ${filename} 不存在`)
    return
  }
  
  const content = fs.readFileSync(filepath, 'utf-8')
  console.log(content)
}

// 命令分发
switch (command) {
  case 'init':
    initProject(params[0])
    break
  case 'list':
    listFiles()
    break
  case 'read':
    readFile(params[0])
    break
  case 'help':
  default:
    showHelp()
}

package.json 配置

{
  "name": "dongba-project",
  "version": "1.0.0",
  "description": "东巴文项目",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "test": "jest"
  },
  "keywords": ["javascript", "nodejs"],
  "author": "东巴文",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.0",
    "jest": "^29.0.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "type": "module"
}

下一步

Node.js 入门后,你可以继续学习:


东巴文(db-w.cn)—— 让 Node.js 开发更简单