nodejs基础
Node.js 安装与使用指南
一、 Node.js 安装
A. Ubuntu 20.04 安装 Node.js
1. 方法一:官网下载安装
此方法允许您安装指定版本的 Node.js。
-
下载安装包:
- 访问官方网站 https://nodejs.org/en/download 或中国镜像网站 https://npmmirror.com/mirrors/node/ 选择所需版本(例如 v18.17.0)。
- 使用
wget
下载(请根据实际版本调整命令):- 从官网下载:
wget https://nodejs.org/dist/v18.17.0/node-v18.17.0-linux-x64.tar.xz
- 或从中国镜像下载:
wget https://npmmirror.com/mirrors/node/v18.17.0/node-v18.17.0-linux-x64.tar.xz
- 从官网下载:
-
创建目标目录并解压:
# 创建安装目录 sudo mkdir -p /usr/local/nodejs # 解压文件到目标目录,并移除顶层文件夹 sudo tar -xf node-v18.17.0-linux-x64.tar.xz -C /usr/local/nodejs --strip-components=1
--strip-components=1
选项会去除解压出来的顶级文件夹,将文件直接解压到指定目录。
-
配置环境变量:
# 将Node.js路径添加到环境变量配置中 echo 'export NODE_HOME=/usr/local/nodejs' >> ~/.bashrc echo 'export PATH=$NODE_HOME/bin:$PATH' >> ~/.bashrc echo 'export NODE_PATH=$NODE_HOME/lib/node_modules' >> ~/.bashrc # 使环境变量立即生效 source ~/.bashrc
-
验证安装:
在终端中运行以下命令,查看 Node.js 和 npm 的版本号:node -v npm -v
2. 方法二:使用 apt
(通过 NodeSource PPA)
此方法通常安装较新版本,但可能不如手动下载灵活。注意:原文示例中的 setup_6.x
版本非常老旧,建议使用更新的版本,例如 setup_18.x
或 setup_20.x
。
-
安装
curl
(如果尚未安装):sudo apt update sudo apt install curl -y
-
获取 NodeSource 安装脚本:
(以 Node.js 18.x 为例,请根据需要替换版本号)cd ~ curl -sL https://deb.nodesource.com/setup_18.x -o nodesource_setup.sh
-
运行脚本:
sudo bash nodesource_setup.sh
-
安装 Node.js:
sudo apt-get install nodejs -y
-
安装构建工具 (推荐,用于编译原生模块):
sudo apt-get install build-essential -y
-
验证安装:
node -v npm -v
B. CentOS 安装 Node.js
1. 官网下载安装
此方法与 Ubuntu 手动下载类似。
-
下载安装包:
- 访问官网 https://nodejs.org/en/download/ 获取下载链接。
- 使用
wget
下载(以 v16.14.0 为例):wget https://nodejs.org/dist/v16.14.0/node-v16.14.0-linux-x64.tar.xz
-
解压:
tar -xvf node-v16.14.0-linux-x64.tar.xz
-
移动并重命名文件夹 (移动到
/usr/local
并命名为nodejs
):# 假设下载和解压在 /home/yys/test/ 目录下 sudo mv /home/yys/test/node-v16.14.0-linux-x64 /usr/local/nodejs # 或者,如果在当前目录解压后: # sudo mv node-v16.14.0-linux-x64 /usr/local/nodejs
-
配置环境变量 (全局生效):
- 编辑
/etc/profile
文件:sudo vi /etc/profile
- 在文件末尾添加:
export PATH=$PATH:/usr/local/nodejs/bin
- 保存退出后,使配置生效:
source /etc/profile
- 编辑
-
验证安装:
node -v npm -v
C. 通用配置与问题处理
1. 更换 NPM 国内源
为了提高 npm install
的下载速度,可以更换为国内镜像源。
- 临时使用:
npm --registry https://registry.npm.taobao.org install <package-name>
- 永久更换:
npm config set registry https://registry.npm.taobao.org
- 验证当前源:
npm config get registry
- 去掉 SSL 证书验证 (如果遇到证书问题,但不推荐在生产环境使用):
npm config set strict-ssl false
2. 常见问题处理
-
Ubuntu (WSL)
apt
报错:- 错误信息:
E: Could not read response to hello message from hook [ ! -f /usr/bin/snap ] || /usr/bin/snap advise-snap --from-apt 2>/dev/null || true: Success
- 原因:
snapd
包与 WSL 可能存在兼容性问题。 - 解决方法: 删除相关的
apt
配置文件。sudo rm -rf /etc/apt/apt.conf.d/20snapd.conf
apt
命令。
- 错误信息:
-
Windows 11 PowerShell 运行
npm
报错:- 错误信息:
npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
- 原因: PowerShell 的执行策略限制了脚本运行。
- 解决方法:
- 以 管理员身份 打开 PowerShell。
- 查看当前执行策略:
Get-ExecutionPolicy
- 更改执行策略(例如改为
RemoteSigned
):Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
- 根据提示输入
Y
确认。 - 重新运行
npm
命令。
- 错误信息:
二、 Node.js 基础使用
A. 运行第一个脚本 (helloworld
)
- 创建
00-helloworld.js
文件:vim 00-helloworld.js
- 写入 JavaScript 代码:
var foo = 'bar'; console.log(foo);
- 使用
node
命令运行:node 00-helloworld.js
- 预期输出:
bar
B. 核心概念与常用模块
1. 运行环境差异 (无 BOM, DOM)
Node.js 中的 JavaScript 运行在服务器端,与浏览器环境不同:
- 没有 浏览器提供的
window
,document
等 BOM 和 DOM API。 - 拥有 浏览器环境所没有的服务器端能力,如文件操作、网络通信等。
// 01-没有bom和dom.js
// 在 Node 中,采用 EcmaScript 进行编码
// 没有 BOM、DOM
// 和浏览器中的 JavaScript 不一样
// 下面的代码会报错:ReferenceError: window is not defined
// console.log(window)
// console.log(document)
2. 文件系统 (fs
模块)
Node.js 通过内置的 fs
(file-system) 核心模块提供文件操作能力。使用前需要先 require
。
-
读取文件 (
fs.readFile
):- 创建
a.txt
文件,写入内容 (例如:hello nodejs 你好
)。 - 创建
02-读取文件.js
:// 1. 加载 fs 核心模块 var fs = require('fs'); // 2. 异步读取文件 // 第一个参数:文件路径 // 第二个参数:回调函数 (error, data) fs.readFile('a.txt', function (error, data) { if (error) { console.log('读取文件失败了:', error); } else { // data 是 Buffer 对象 (二进制数据) // console.log(data); // 使用 toString() 转为可读字符串 (默认utf8) console.log(data.toString()); } });
- 运行
node 02-读取文件.js
,输出a.txt
的内容。
- 创建
-
写入文件 (
fs.writeFile
):- 创建
data
文件夹 (如果需要)。 - 创建
2.4 写文件.js
(原文文件名,建议修改为更有意义的如04-writeFile.js
):var fs = require('fs'); // 第一个参数:文件路径 // 第二个参数:要写入的内容 // 第三个参数:回调函数 (error) fs.writeFile('./data/你好.md', '大家好,给大家介绍一下,我是Node.js', function (error) { if (error) { console.log('写入失败:', error); } else { console.log('写入成功了'); } });
- 运行脚本,会在
data
目录下创建你好.md
文件并写入内容。
- 创建
3. 路径操作 (path
模块)
path
核心模块用于处理和转换文件路径。
- 获取文件扩展名 (
path.extname
):// 2.3 路径 (建议命名为 03-path-extname.js) const path = require('path'); // 文件路径示例 const fpath = '/a/b/c/index.html'; const fext = path.extname(fpath); console.log(fext); // 输出: .html console.log(path.extname('c:/a/b/c/d/hello.txt')); // 输出: .txt
4. HTTP 服务器 (http
模块)
http
核心模块用于创建 HTTP 服务器和客户端。
-
创建基本服务器:
// 2.5 http.js (建议命名为 05-http-server.js) // 1. 加载 http 核心模块 var http = require('http'); // 2. 创建 Web 服务器实例 var server = http.createServer(); // 3. 注册 request 事件监听器 // 当有客户端请求时触发回调函数 server.on('request', function (request, response) { // request: 请求对象, response: 响应对象 console.log('收到客户端的请求了,请求路径是:' + request.url); console.log('请求客户端地址:', request.socket.remoteAddress, request.socket.remotePort); // 4. 发送响应 response.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'}); // 设置响应头,解决中文乱码 response.write('你好,世界!'); response.write(' Hello Node.js'); response.end(); // 必须调用 end() 结束响应 }); // 5. 绑定端口号,启动服务器 server.listen(3000, function () { console.log('服务器启动成功,可以通过 http://127.0.0.1:3000/ 来进行访问'); });
运行
node 05-http-server.js
,然后在浏览器访问http://127.0.0.1:3000/
。 -
根据 URL 响应不同内容:
// 2.7 http-url-res.js (建议命名为 06-http-routing.js) var http = require('http'); var server = http.createServer(); server.on('request', function (req, res) { var url = req.url; // 获取请求路径 // 设置响应头 Content-Type res.setHeader('Content-Type', 'text/plain; charset=utf-8'); if (url === '/') { res.end('首页 index page'); } else if (url === '/login') { res.end('登录 login page'); } else if (url === '/products') { var products = [ { name: '苹果 X', price: 8888 }, { name: '菠萝 X', price: 5000 }, { name: '小辣椒 X', price: 1999 } ]; // 响应 JSON 数据需要先序列化,并设置正确的 Content-Type res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.end(JSON.stringify(products)); } else { res.setHeader('Content-Type', 'text/html; charset=utf-8'); // 可以返回HTML res.statusCode = 404; // 设置状态码 res.end('<h1>404 Not Found.</h1>'); } }); server.listen(3000, function () { console.log('服务器启动成功,可以访问了...'); });
-
Content-Type (MIME 类型):
Content-Type
响应头用于告诉浏览器服务器发送的数据类型。text/plain
: 普通文本。text/html
: HTML 文档。application/json
: JSON 数据。image/jpeg
,image/png
: 图片。text/css
: CSS 文件。application/javascript
: JavaScript 文件。- 重要: 对于文本类型 (
text/*
,application/json
,application/javascript
等),务必指定charset=utf-8
以避免中文乱码。图片等二进制文件不需要指定字符集。 - 常见 MIME 类型可参考: http://tool.oschina.net/commons
// 2.11 http-content-type.js (示例已整合到上方代码中) // ... // 根据请求的 url 设置不同的 Content-Type if (url === '/plain') { res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.end('hello 世界'); } else if (url === '/html') { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end('<p>hello html <a href="">点我</a></p>'); } // ...
5. 结合 http
与 fs
提供文件服务
可以读取文件内容并将其作为 HTTP 响应发送给客户端。
- 创建
resource
目录,并放入index.html
,main.css
,main.js
,ab2.jpg
等资源文件。 - 创建
2.12 http-fs.js
(建议命名为07-http-serve-files.js
):var http = require('http'); var fs = require('fs'); var path = require('path'); // 引入 path 模块 var server = http.createServer(); var resourceDir = path.join(__dirname, 'resource'); // 资源目录路径 server.on('request', function (req, res) { var url = req.url; var filePath = path.join(resourceDir, url); // 默认请求路径对应文件名 // 如果请求根路径,则默认返回 index.html if (url === '/') { filePath = path.join(resourceDir, 'index.html'); } fs.readFile(filePath, function (err, data) { if (err) { // 文件不存在或读取错误,返回 404 res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('<h1>404 Not Found</h1>'); } else { // 根据文件扩展名设置 Content-Type var ext = path.extname(filePath); var contentType = 'text/plain'; // 默认类型 switch (ext) { case '.html': contentType = 'text/html; charset=utf-8'; break; case '.css': contentType = 'text/css; charset=utf-8'; break; case '.js': contentType = 'application/javascript; charset=utf-8'; break; case '.jpg': case '.jpeg': contentType = 'image/jpeg'; break; case '.png': contentType = 'image/png'; break; // 添加更多类型... } res.writeHead(200, { 'Content-Type': contentType }); res.end(data); // 发送文件内容 (Buffer 或 String) } }); }); server.listen(3000, function () { console.log('Server is running at http://127.0.0.1:3000/'); });
http://127.0.0.1:3000/
会显示index.html
,访问http://127.0.0.1:3000/ab2.jpg
会显示图片。
6. 操作系统信息 (os
模块)
os
核心模块提供与操作系统相关的方法。
// 2.8 node中的js-核心模块.js (建议命名为 08-os-module.js)
// 用来获取机器信息的
var os = require('os');
// 获取 CPU 信息
console.log('CPU Info:', os.cpus());
// 获取总内存大小 (字节)
console.log('Total Memory (bytes):', os.totalmem());
// 获取操作系统平台 ('darwin', 'freebsd', 'linux', 'sunos', 'win32')
console.log('Platform:', os.platform());
7. IP 地址和端口号
- IP 地址: 用于在网络中定位计算机。
- 端口号: 用于定位计算机上运行的具体应用程序。每个需要网络通信的应用都会监听一个或多个端口号。
// 2.10 ip地址和端口号.js (内容已在 05-http-server.js 中体现)
// 在 http server 的 request 事件回调中可以获取客户端 IP 和端口
// console.log('请求我的客户端的地址是:', req.socket.remoteAddress, req.socket.remotePort)
C. 模块化 (require
, exports
, module.exports
)
Node.js 采用了 CommonJS 模块规范。每个 .js
文件都是一个独立的模块,拥有自己的作用域。
require
: 用于加载模块。- 加载核心模块:
require('fs')
,require('http')
- 加载文件模块:
require('./b')
(相对路径,./
不能省略,.js
后缀可省略)。 - 加载第三方模块 (npm 安装的):
require('art-template')
。
- 加载核心模块:
exports
: 每个模块内部都有一个exports
对象。可以将需要暴露给外部的成员挂载到exports
对象上。module.exports
:exports
是module.exports
的一个引用。可以直接给module.exports
赋值来导出单个成员(如函数或类),或者直接导出一个新的对象。通常推荐使用exports.xxx = ...
方式,或者直接module.exports = ...
。
示例:
- 创建
b.js
:// b.js console.log('b.js start'); var foo = 'bbb'; function add(x, y) { return x + y; } exports.foo = 'hello from b'; // 导出字符串 exports.add = add; // 导出函数 exports.readFile = function (path) { // 导出匿名函数 console.log('Simulating reading file:', path); }; const age = 18; exports.age = age; // 导出变量 // module.exports = { // 或者直接导出整个对象 // foo: 'hello from b', // add: add, // readFile: function(path){...}, // age: age // } require('./c'); // 加载 c.js console.log('b.js end');
- 创建
c.js
:// c.js console.log('c.js executed');
- 创建
a.js
(加载b.js
):// a.js (对应原文 2.9 简单的模块化 部分) console.log('a.js start'); var fs = require('fs'); // 加载核心模块 // 加载自定义模块 b.js,require 返回 b.js 的 exports 对象 var bExports = require('./b'); console.log('Accessing exports from b.js:'); console.log('bExports.foo:', bExports.foo); // 输出: hello from b console.log('bExports.add(10, 30):', bExports.add(10, 30)); // 输出: 40 console.log('bExports.age:', bExports.age); // 输出: 18 bExports.readFile('./a.js'); // 输出: Simulating reading file: ./a.js // 模块内的变量 foo 不会被导出,这里访问的是 a.js 自己的 foo var foo = 'aaa'; console.log('foo in a.js:', foo); // 输出: aaa console.log('a.js end'); // 示例:使用核心模块 fs fs.readFile('./a.js', function (err, data) { if (err) { console.log('读取 a.js 失败'); } else { // console.log(data.toString()); // 打印 a.js 自身内容 } });
- 运行
node a.js
,观察输出顺序和导出成员的访问。a.js start b.js start c.js executed b.js end Accessing exports from b.js: bExports.foo: hello from b bExports.add(10, 30): 40 bExports.age: 18 Simulating reading file: ./a.js foo in a.js: aaa a.js end
三、 包管理 (NPM) 与项目配置
A. NPM 安装第三方包
NPM (Node Package Manager) 是 Node.js 的包管理器。
- 本地安装 (Local Installation):
- 命令:
npm install <package-name>
- 作用: 将包安装到当前项目目录下的
node_modules
文件夹中,并记录到package.json
的dependencies
。只对当前项目有效。 - 使用: 在项目代码中通过
require()
或import
使用。
- 命令:
- 全局安装 (Global Installation):
- 命令:
npm install <package-name> -g
(或--global
) - 作用: 将包安装到系统全局路径下,通常用于安装命令行工具(如
pm2
,nodemon
,http-server
)。 - 使用: 在命令行中直接使用该工具。
- 命令:
- 开发依赖安装 (Development Dependencies):
- 命令:
npm install <package-name> --save-dev
(或-D
) - 作用: 将包安装到
node_modules
,并记录到package.json
的devDependencies
。这些包只在开发和构建过程中需要(如测试框架、构建工具、代码检查工具)。 - 例如:
npm install jest --save-dev
- 命令:
B. package.json
文件
package.json
是 Node.js 项目的配置文件,通常位于项目根目录。
-
作用:
- 项目元数据: 记录项目名称、版本、描述、作者、许可证等。
- 依赖管理:
dependencies
(运行时依赖) 和devDependencies
(开发时依赖) 字段列出项目所需的第三方包及其版本范围。运行npm install
时会根据此文件安装依赖。 - 脚本命令:
scripts
字段可以定义常用的项目命令,如启动 (start
)、测试 (test
)、构建 (build
) 等。可以通过npm run <script-name>
执行。 - 版本控制与共享: 将
package.json
纳入版本控制(如 Git),方便团队成员共享和恢复项目环境。 - 项目初始化: 通过
npm init
或npm init -y
命令可以快速生成一个基本的package.json
文件。
-
npm init
报错处理:- 如果在运行
npm install <package-name>
时遇到ENOENT: no such file or directory, open '.../package.json'
错误,说明当前目录缺少package.json
文件。 - 解决方法: 在项目根目录下运行
npm init
,根据提示填写信息,或直接运行npm init -y
使用默认配置快速生成。
- 如果在运行
C. 使用模板引擎 (示例: art-template
)
模板引擎用于将数据和 HTML 模板结合生成最终的 HTML 页面。
- 安装: 在项目目录下执行:
npm install art-template
npm init -y
) - 创建 HTML 模板文件 (
tpl.html
):<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ title }}</title> <!–– 使用模板变量 --> </head> <body> <p>大家好,我叫:{{ name }}</p> <p>我今年 {{ age }} 岁了</p> <h1>我来自 {{ province }}</h1> <p>我喜欢: {{each hobbies}} <!–– 遍历数组 --> {{ $value }} <!–– 当前项的值 --> {{/each}} </p> </body> </html>
- 创建 Node.js 脚本 (
05-在node中使用模板引擎.js
,建议命名为09-template-engine.js
):// 1. 加载 art-template 模块 var template = require('art-template'); var fs = require('fs'); var path = require('path'); // 模板文件路径 var tplPath = path.join(__dirname, 'tpl.html'); // 读取模板文件内容 fs.readFile(tplPath, function (err, data) { if (err) { return console.log('读取模板文件失败了:', err); } // data 是 Buffer,需要转为字符串 var tplString = data.toString(); // 准备数据 var viewData = { name: 'Jack', age: 18, province: '北京市', hobbies: [ '写代码', '唱歌', '打游戏' ], title: '个人信息' }; // 3. 使用 template.render(模板字符串, 数据对象) 渲染模板 var renderedHtml = template.render(tplString, viewData); // 输出渲染后的 HTML 字符串 console.log(renderedHtml); // 实际应用中,可以将 renderedHtml 通过 http response 发送给浏览器 // res.setHeader('Content-Type', 'text/html; charset=utf-8'); // res.end(renderedHtml); });
- 运行脚本:
node 09-template-engine.js
,会输出渲染好的 HTML 内容。
四、 进程管理 (PM2)
PM2 是一个流行的 Node.js 应用程序进程管理器,可以简化应用的部署、监控和管理。
A. 安装 PM2
- 使用
npm
全局安装:sudo npm install -g pm2
- 如果遇到权限问题,可能需要
sudo
,或者配置 NPM 全局安装路径权限。
- 如果遇到权限问题,可能需要
B. 启动和管理应用
-
创建 Node.js 应用 (
app.js
):// app.js - 一个简单的 HTTP 服务器示例 const http = require('http'); http.createServer(function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'}); res.end('你好,PM2 管理的应用!\n'); }).listen(8080, '127.0.0.1'); // 监听本地 8080 端口 console.log('服务器运行在 http://127.0.0.1:8080/');
-
使用 PM2 启动应用:
在app.js
所在目录下运行:pm2 start app.js
- 输出会显示应用的状态信息,包括 app name, id, status, memory usage 等。
-
常用 PM2 命令:
pm2 list
: 列出所有由 PM2 管理的应用。pm2 stop <app_name|id>
: 停止指定应用。pm2 restart <app_name|id>
: 重启指定应用。pm2 delete <app_name|id>
: 从 PM2 列表中移除应用(并停止)。pm2 logs [<app_name|id>]
: 查看应用日志。pm2 monit
: 监控所有应用的 CPU 和内存使用情况。pm2 startup
: 生成开机自启动脚本 (需要按提示执行生成的命令)。pm2 save
: 保存当前 PM2 进程列表,以便重启后恢复。
五、 使用 Nginx 作为反向代理
当 Node.js 应用监听在非 80 端口(如 8080)时,通常使用 Nginx 作为反向代理,将来自公网 80 端口的请求转发给 Node.js 应用。
A. 配置 Nginx
-
安装 Nginx (如果尚未安装):
- Ubuntu:
sudo apt update && sudo apt install nginx -y
- CentOS:
sudo yum update && sudo yum install nginx -y
- Ubuntu:
-
编辑 Nginx 配置文件:
通常是/etc/nginx/sites-available/default
(Ubuntu) 或/etc/nginx/nginx.conf
或/etc/nginx/conf.d/default.conf
(CentOS)。sudo nano /etc/nginx/sites-available/default # Ubuntu 示例
-
在
server
块中配置location /
:
找到server { ... }
块,修改或添加location / { ... }
部分:server { listen 80 default_server; listen [::]:80 default_server; # ... 其他配置 ... server_name your_domain_or_ip; # 替换为你的域名或服务器 IP location / { # 将请求转发给本地监听 8080 端口的 Node.js 应用 proxy_pass http://127.0.0.1:8080; # 设置必要的头信息,以便后端应用获取真实信息 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; # 支持 WebSocket proxy_set_header Connection 'upgrade'; # 支持 WebSocket proxy_set_header Host $host; # 传递原始 Host 头 proxy_set_header X-Real-IP $remote_addr; # 传递真实客户端 IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 传递协议 (http/https) proxy_cache_bypass $http_upgrade; } # 可以添加其他 location 块代理到不同端口的应用 # location /secondapp { # proxy_pass http://127.0.0.1:8081; # # ... 其他 proxy_set_header 等配置 ... # } # ... 其他配置 ... }
-
检查 Nginx 配置语法:
sudo nginx -t
如果显示
syntax is ok
和test is successful
,则配置无误。 -
重启 Nginx 服务:
sudo systemctl restart nginx
或者
sudo service nginx restart
。
现在,假设你的 Node.js 应用(如上面 app.js
示例)正在运行并监听 8080 端口,你应该可以通过访问服务器的 IP 地址或域名(默认 80 端口)来访问该应用了。
六、 Node.js 编码风格与实践
A. 通用编码规范建议
- 缩进: 使用 2 个空格进行缩进。
- 字符串: 优先使用单引号 (
'
),除非字符串内包含单引号需要转义。 - 变量: 避免声明未使用或冗余的变量。
- 分号:
- 可以选择不加分号(ASI - Automatic Semicolon Insertion 会在多数情况下自动处理)。
- 注意: 如果不加分号,行首不要以
(
,[
, 或`
(模板字符串) 开头。如果必须这样写,请在该行 前面加上分号;
,例如;(function(){...})()
。这是省略分号最容易出问题的地方。
- 空格:
- 关键字后加空格:
if (condition) { ... }
- 函数名后加空格 (函数声明):
function name (arg) { ... }
(匿名函数表达式通常不加:var fn = function() {}
)
- 关键字后加空格:
- 比较: 坚持使用全等 (
===
) 和不全等 (!==
),避免使用 (==
) 和 (!=
),除非是判断null
或undefined
(obj == null
)。 - 错误处理: 一定要处理 Node.js 异步回调中的
err
参数。 - 浏览器全局变量: 在 Node.js 中避免直接使用
window
,document
,navigator
等浏览器全局变量(除非你在写特定环境的代码如 Electron 渲染进程)。 - 避免全局变量冲突: 避免使用常见的、易冲突的全局变量名,如
open
,length
,event
,name
。
B. 无分号风格的注意事项示例
// 00-代码无分号问题.js (建议命名 10-semicolon-issues.js)
function say() {
console.log('hello world')
}
// 如果上一行没有分号,下一行以 ( 开头,会被解析为函数调用
// say() // 正确调用
// (function () { ... })() // IIFE
// 正确写法(如果省略分号):
say() // 上一行有换行符,ASI 会插入分号
;(function () { // 在 IIFE 前加分号,避免与上一行连接
console.log('IIFE executed')
})()
;['苹果', '香蕉'].forEach(function (item) { // 在数组字面量前加分号
console.log(item)
})
;`hello`.toString() // 在模板字符串前加分号
// 结论:
// 无论是否使用分号,都建议在一行代码是以 (、[、` 开头的,
// 并且可能与前一行产生语法歧义时,在其前面加上分号 ;
七、 补充脚本与示例
A. 自定义日志打印函数 (带脚本名、行号、时间)
// print.js
const path = require('path');
function customPrint(...args) {
// 获取调用堆栈信息
const stack = (new Error()).stack.split('\n');
// 尝试匹配调用者的信息 (通常是 stack[2],stack[0]是Error, stack[1]是customPrint自身)
const callerFrame = stack[2] || ''; // 获取调用 customPrint 的那一行信息
const match = callerFrame.match(/\s*at.*?\((.*?):(\d+):(\d+)\)|\s*at\s*(.*?):(\d+):(\d+)$/); // 兼容不同格式
let scriptPath = '';
let lineNumber = '';
let columnNumber = '';
let scriptName = 'unknown';
if (match) {
scriptPath = match[1] || match[4] || ''; // 文件路径
lineNumber = match[2] || match[5] || ''; // 行号
columnNumber = match[3] || match[6] || ''; // 列号
if (scriptPath) {
scriptName = path.basename(scriptPath); // 文件名
}
}
// 获取当前时间并格式化
const now = new Date();
const formattedDate = `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
// 构造前缀
const printStr = `[${scriptName}:${lineNumber}:${columnNumber} ${formattedDate}] `;
// 处理参数,对象转为 JSON 字符串
const argStrings = args.map(arg => {
if (typeof arg === 'object' && arg !== null) {
try {
return JSON.stringify(arg);
} catch (e) {
return '[Object]'; // 处理循环引用等无法序列化的情况
}
} else {
return String(arg); // 其他类型转为字符串
}
});
// 输出日志
console.log(printStr + argStrings.join(' '));
}
// 导出函数,以便其他模块使用
module.exports = {
customPrint
};
// --- 使用示例 (在另一个文件,如 main.js) ---
// const myLogger = require('./print');
// myLogger.customPrint('Hello,', 'world!', { key: 'value' });
// 输出类似: [main.js:2:10 2023/08/24 10:30:00] Hello, world! {"key":"value"}
B. 发送邮件 (使用 nodemailer
)
-
安装
nodemailer
:npm install nodemailer
-
发送基本文本邮件:
// sendTextEmail.js const nodemailer = require('nodemailer'); async function sendEmail(to, subject, text) { try { // 创建一个 SMTP 传输器 (以 QQ 邮箱为例) let transporter = nodemailer.createTransport({ host: 'smtp.qq.com', // QQ邮箱的 SMTP 服务器 port: 465, // SSL 端口 secure: true, // 使用 SSL auth: { user: 'your_qq_email@qq.com', // 你的 QQ 邮箱地址 pass: 'your_qq_smtp_password' // 你的 QQ 邮箱 SMTP 授权码 (不是邮箱密码) } }); // 设置邮件选项 let mailOptions = { from: '"Your Name" <your_qq_email@qq.com>', // 发件人显示名称和地址 to: to, // 收件人地址 (可以是多个,用逗号分隔) subject: subject, // 邮件主题 text: text // 纯文本邮件内容 // html: '<b>Hello world?</b>' // 或者使用 HTML 内容 }; // 发送邮件 let info = await transporter.sendMail(mailOptions); console.log('邮件发送成功: %s', info.messageId); // console.log('预览 URL: %s', nodemailer.getTestMessageUrl(info)); // 仅用于 Ethereal 测试账户 } catch (error) { console.error('邮件发送失败:', error); } } // 调用函数发送邮件 sendEmail('recipient@example.com', '来自 Node.js 的问候', '这是一封通过 Node.js 发送的测试邮件!');
- 注意: 需要将
your_qq_email@qq.com
和your_qq_smtp_password
替换为你的实际邮箱和生成的 SMTP 授权码。
- 注意: 需要将
-
发送带嵌入图片的 HTML 邮件:
// sendHtmlEmailWithImages.js const nodemailer = require('nodemailer'); const fs = require('fs'); const path = require('path'); // 创建邮件传输对象 (同上) const transporter = nodemailer.createTransport({ host: 'smtp.qq.com', port: 465, secure: true, auth: { user: 'your_qq_email@qq.com', pass: 'your_qq_smtp_password' } }); // 生成带嵌入图片的 HTML 内容 function generateHTMLContent(images) { let attachments = []; // 用于存放附件信息 (嵌入图片) let imageTagsHTML = ''; // 用于存放 <img> 标签 images.forEach((imagePath, index) => { const cid = `image${index}@nodemailer.com`; // 为每张图片生成唯一的 CID const filename = path.basename(imagePath); attachments.push({ filename: filename, path: imagePath, cid: cid // 关键:指定 CID }); // 在 HTML 中使用 cid 引用图片 imageTagsHTML += `<p><img src="cid:${cid}" alt="${filename}" style="max-width: 100%; height: auto;"/></p>`; }); // 完整的 HTML 内容 let emailHTML = ` <h1>这是一封带嵌入图片的HTML邮件</h1> <p>下面是嵌入的图片:</p> ${imageTagsHTML} <p>邮件结束。</p> `; return { html: emailHTML, attachments: attachments }; } // 封装发送邮件函数 async function sendEmailWithImages(to, subject, imagePaths) { try { const { html, attachments } = generateHTMLContent(imagePaths); // 邮件选项 const mailOptions = { from: '"Your Name" <your_qq_email@qq.com>', to: to, subject: subject, html: html, // HTML 内容 attachments: attachments // 附件 (包含嵌入图片) }; // 发送邮件 let info = await transporter.sendMail(mailOptions); console.log('带图片邮件发送成功: %s', info.messageId); } catch (error) { console.error('带图片邮件发送失败:', error); } } // 使用示例 const recipientEmail = 'recipient@example.com'; const emailSubject = '带多张图片的 HTML 邮件测试'; const imagePaths = [ path.join(__dirname, 'resource/ab2.jpg'), // 图片文件的实际路径 // path.join(__dirname, 'path/to/another_image.png') ]; sendEmailWithImages(recipientEmail, emailSubject, imagePaths);
C. Electron 倒计时关闭窗口示例
Electron 用于构建跨平台的桌面应用程序。
- 创建项目并安装 Electron:
mkdir electron-countdown-app cd electron-countdown-app npm init -y npm install electron --save-dev
- 创建
main.js
(主进程):// main.js const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); const url = require('url'); let mainWindow; function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, // 允许在渲染进程中使用 Node.js API (为了 require) contextIsolation: false, // 关闭上下文隔离 (为了 require, 不推荐在生产环境直接这样用) // preload: path.join(__dirname, 'preload.js') // 更安全的做法是使用 preload 脚本 }, }); // 加载 index.html mainWindow.loadURL(url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', slashes: true })); // mainWindow.webContents.openDevTools(); // 打开开发者工具 mainWindow.on('closed', () => { mainWindow = null; }); } app.whenReady().then(createWindow); // 监听渲染进程发送的 'close-window' 事件 ipcMain.on('close-window', () => { console.log('收到关闭窗口请求,执行关闭...'); if (mainWindow) { mainWindow.close(); } }); // 其他应用生命周期事件处理... app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (mainWindow === null) { createWindow(); } });
- 创建
index.html
(渲染进程):<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>倒计时关闭窗口</title> <style> body { font-family: sans-serif; text-align: center; padding-top: 50px; } #countdown { font-size: 72px; color: firebrick; margin: 20px 0; } button { padding: 10px 20px; font-size: 16px; cursor: pointer; } </style> </head> <body> <h1>窗口将在倒计时结束后自动关闭</h1> <div id="countdown">10</div> <button id="cancelButton">取消关闭</button> <button id="closeNowButton">立即关闭</button> <script> const { ipcRenderer } = require('electron'); // 引入 ipcRenderer let remainingTime = 10; // 初始倒计时秒数 const countdownElement = document.getElementById('countdown'); const cancelButton = document.getElementById('cancelButton'); const closeNowButton = document.getElementById('closeNowButton'); let intervalId = null; function updateCountdown() { countdownElement.textContent = remainingTime; } function startCountdown() { updateCountdown(); intervalId = setInterval(() => { remainingTime--; updateCountdown(); if (remainingTime <= 0) { clearInterval(intervalId); // 发送事件到主进程请求关闭窗口 ipcRenderer.send('close-window'); } }, 1000); // 每秒执行一次 } cancelButton.addEventListener('click', () => { if (intervalId) { clearInterval(intervalId); countdownElement.textContent = '已取消'; console.log('倒计时已取消'); // 可以在这里禁用按钮等 cancelButton.disabled = true; closeNowButton.disabled = true; } }); closeNowButton.addEventListener('click', () => { if (intervalId) clearInterval(intervalId); ipcRenderer.send('close-window'); }); // 页面加载完成后开始倒计时 startCountdown(); </script> </body> </html>
- 在
package.json
中添加启动脚本:{ "name": "electron-countdown-app", "version": "1.0.0", "description": "", "main": "main.js", // 确认入口文件是 main.js "scripts": { "start": "electron .", // 添加 start 脚本 "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "electron": "^..." // 版本号可能不同 } }
- 运行应用:
npm start
npx electron .
。
D. MySQL 数据库操作 (使用 mysql2
)
mysql2
是一个流行的 Node.js MySQL 驱动库,性能较好,支持 Promise。
-
安装
mysql2
:npm install mysql2
-
连接数据库并执行插入操作:
// mysqlInsert.js const mysql = require('mysql2/promise'); // 使用 promise 版本的 mysql2 async function main() { let connection; // 将 connection 声明在 try 外部,以便 finally 中访问 try { // 创建数据库连接池 (推荐) const pool = mysql.createPool({ host: 'localhost', // 数据库主机名 user: 'your_db_user', // 数据库用户名 password: 'your_db_password', // 数据库密码 database: 'your_db_name', // 数据库名 waitForConnections: true, connectionLimit: 10, // 连接池最大连接数 queueLimit: 0 // 排队请求限制 (0表示不限制) }); console.log('连接池创建成功'); // 从连接池获取一个连接 connection = await pool.getConnection(); console.log('获取连接成功'); // 准备要插入的数据 const newRecord = { name: 'Alice', // 假设表中有 name 和 email 字段 email: `alice_${Date.now()}@example.com` }; // 执行插入 SQL 语句 // 使用占位符 (?) 防止 SQL 注入 const [results, fields] = await connection.query('INSERT INTO your_table_name SET ?', newRecord); console.log('插入成功,记录 ID:', results.insertId); // 查询刚插入的数据 (示例) const [rows] = await connection.query('SELECT * FROM your_table_name WHERE id = ?', [results.insertId]); console.log('查询到的新记录:', rows[0]); } catch (error) { console.error('数据库操作出错:', error); } finally { // 无论成功或失败,最后都要释放连接回连接池 if (connection) { connection.release(); console.log('连接已释放'); } // 如果不再需要连接池,可以关闭它 (通常在应用退出时) // pool.end(); } } main(); // 执行主函数
- 注意:
- 将
your_db_user
,your_db_password
,your_db_name
,your_table_name
替换为你的实际配置。 - 确保数据库和表已存在,且表结构包含
name
,email
,id
(假设 id 是自增主键) 字段。 - 使用
async/await
处理 Promise,代码更简洁。 - 使用连接池 (
createPool
) 比每次创建连接 (createConnection
) 更高效。 - 必须 在
finally
块中释放连接 (connection.release()
)。
- 将
- 注意:
-
mysql
vsmysql2
:- 性能:
mysql2
通常性能更好,尤其在高并发下。 - API:
mysql2
提供了更好的 Promise 支持 (通过require('mysql2/promise')
),使得async/await
使用更方便。mysql
主要基于回调。 - 连接池:
mysql2
内建了连接池支持。mysql
需要额外库或手动实现。 - Prepared Statements:
mysql2
对预处理语句的支持更好,有助于提升性能和安全性。 - 推荐: 对于新项目或追求性能和现代语法的项目,推荐使用
mysql2
。
- 性能:
E. 导入自定义函数
正如前面 模块化 部分和 自定义日志函数 部分所示,Node.js 通过 module.exports
或 exports
导出模块成员,通过 require()
导入和使用这些成员。
导出 (例如 print.js
):
// print.js
function customPrint(text) {
console.log('My Custom Print:', text);
}
function anotherUtil() {
// ...
}
// 导出需要被外部使用的函数
module.exports = {
customPrint: customPrint, // 可以重命名导出的接口
anotherUtil // 如果导出名和内部名相同,可以简写
};
导入和使用 (例如 main.js
):
// main.js
// 使用 require 导入 print.js 导出的对象
const myUtils = require('./print'); // 假设 print.js 在同级目录
// 调用导出的函数
myUtils.customPrint('Hello from main.js!');
// myUtils.anotherUtil();
这份整理后的文档应该更清晰、更有条理。希望对您有帮助!
本文作者: 永生
本文链接: https://yys.zone/detail/?id=227
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
发表评论
评论列表 (0 条评论)
暂无评论,快来抢沙发吧!