typora-copy-images-to: media
初识Node
Node的概念
- Nodejs是一个js运行时,它可以像浏览器一样解析和运行js文件,构建于Chrome的V8引擎之上
- 浏览器中的js:ECMAScript、BOM、DOM
- Node中的js:ECMAScript (没有BOM、DOM)、文件读写、网络服务构建、网络通信、http服务器
- Node是一个事件驱动的非阻塞IO模型(异步)
- Node的包生态系统NPM,是世界最大的开源库生态系统
- Node.js 是一种建立在Google Chrome’s v8 engine上的 non-blocking (非阻塞), event-driven (基于事件的) I/O平台
- Node.js平台使用的开发语言是JavaScript,平台提供了操作系统低层的API,方便做服务器端编程,具体包括文件操作、进程操作、通信操作等系统模块
浏览器 | 内核 |
---|---|
IE | Trident |
FireFox | Gecko |
Chrome | WebKit |
Safari | WebKit |
Opera | Presto |
Edge | Chakra |
Node中的 JavaScript 运行环境
- 1、浏览器是 JavaScript 的前端运行环境
- 2、Node 是 JavaScript 的后端运行环境
- 3、Node中无法调用 DOM 和 BOM 等浏览器内置 API
Node.js可以做什么
- 基于 Express 框架(http://www.expressjs.com.cn/),可以快速构建 Web 应用
- 基于 Electron 框架(https://electronjs.org/),可以构建跨平台的桌面应用
- 基于 restify 框架(http://restify.com/),可以快速构建 API 接口项目
- 读写和操作数据库、创建实用的命令行工具辅助前端开发、etc…
Node的学习路径
- 1、JavaScript 基础语法
- 2、Node.js 内置 API 模块(fs、path、http等)
- 3、第三方 API 模块(express、mysql 等)
学习参考资料
- 《深入浅出Node.js》 (偏理论,理解原理,没有实战)
- 《Node.js权威指南》 (API讲解,没有实战)
- JavaScript标准参考教程(alpha)
- Node入门
- 官方API文档
- 中文文档(版本较旧)http://nodeclass.com/api/node.html
- CNODE - 社区https://cnodejs.org/
- CNODE - 新手入门https://cnodejs.org/getstart
终端基本使用
打开应用
- notepad - 打开记事本
- mspaint - 打开画图
- calc - 打开计算机
- write - 写字板
- sysdm.cpl - 打开环境变量设置窗口
常用命令
- md - 创建目录
- rmdir(rd) - 删除目录 (目录内没有文档)
- echo on a.txt - 创建空文件
- del - 删除文件
- rm 文件名 - 删除文件
- cat 文件名 - 查看文件内容
- cat > 文件名 - 向文件中写入内容(覆盖之前的内容)
- cat >> 文件名 - 向文件中追加写入内容(不会覆盖之前的内容)
Node开发环境安装
安装
- 1、官网首页(https://nodejs.org/en/)
- 2、下载Node到本地
- 3、安装Node(全部选默认)
区分 LTS 版本和 Current 版本
- LTS 为长期稳定版,对于追求稳定性的企业级项目来说,推荐安装 LTS 版本的 Node.js。
- Current 为新特性尝鲜版,对热衷于尝试新特性的用户来说,推荐安装 Current 版本的 Node.js。但是,Current 版本中可能存在隐藏的 Bug 或安全性漏洞,因此不推荐在企业级项目中使用 Current 版本的 Node.js。
多版本安装(nvm)
-
1、卸载已有的Node.js
-
2、下载nvm
-
3、在C盘创建目录dev
-
4、在dev目中中创建两个子目录nvm和nodejs
-
5、并且把nvm包解压进去nvm目录中
-
6、在install.cmd文件上面右键选择【以管理员身份运行】
-
7、打开的cmd窗口直接回车会生成一个settings.txt文件,修改文件中配置信息(注意最后2行是淘宝镜像路径)
root: D:Program Files vm vm
path: D:Program Files vm odejs
arch: 64
proxy: nonenode_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/ -
8、配置nvm和Node.js环境变量
- NVM_HOME:C:dev vm
- NVM_SYMLINK:C:dev odejs
-
9、把配置好的两个环境变量加到Path中
%NVM_HOME%
%NVM_SYMLINK%
nvm常用的命令
- nvm list - 查看当前安装的Node.js所有版本
- nvm install 版本号 - 安装指定版本的Node.js
- nvm uninstall 版本号 - 卸载指定版本的Node.js
- nvm use 版本号 - 选择指定版本的Node.js
// nvm常用的命令
nvm version // 查看nvm版本号,顺便可以测试nvm是否已经安装成功
nvm install <version> [arch] // 安装nvm环境下的nodejs,version表示版本号,可以使用latest代表最新版本
nvm uninstall <version> // 卸载nvm环境下的nodejs
nvm list || nvm ls // 列出当前环境下的所有noejs版本号
nvm use <version> [arch] // 使用某个版本的nodejs
node -v || node --version // 查询node版本号,测试node是否安装成功
Node入门
- 创建、编写js脚本文件
- 打开定位到文件所在目录,进入cmd窗口
- 执行
node xxx.js
,运行JS文件 - 注意:文件名不要使用
node.js
命名,最好也不要使用中文名称
1、简单应用 - 读写文件
// 使用require方法加载fs文件核心模块
var fs = require('fs');
// 'data/hello.txt' 文件路径
// function(err,data) 回调函数
// 文件读取成功
// err null
// data 数据对象
// 文件读取失败
// err 错误对象
// data undefined
//
fs.readFile('data/hello.txt', function(err,data){
if( err ){
// 读取文件出错
console.log('读取文件出错');
}else{
console.log('读取文件成功
');
console.log(data);
console.log(data.toString()+'
');
}
});
$ node 02-文件读写.js
读取文件成功
<Buffer 68 65 6c 6c 6f 20 6e 6f 64 65 2e 6a 73>
hello node.js
2、简单应用 - http服务器搭建
// 加载 http 核心模块
var http = require('http');
// 创建服务器
var server = http.createServer();
// 监听request请求事件
server.on('request', function(request,response){
console.log('已经收到客户端请求!,请求路径:'+request.url);
- response.write('hello ');
- response.write('nodejs');
- response.end();
+ response.end('hello nodejs');
});
// 监听端口号
server.listen(3000, function() {
console.log('服务器启动成功,请通过127.0.0.1:3000/访问');
});
$ node 03-http简单服务器.js
服务器启动成功,请通过127.0.0.1:3000/访问
已经收到客户端请求!,请求路径:/
已经收到客户端请求!,请求路径:/favicon.ico
已经收到客户端请求!,请求路径:/login
已经收到客户端请求!,请求路径:/favicon.ico
REPL
- REPL:read evel print loop(读取代码->执行->打印结果->循环这个过程)
- 在REPL环境中,
_
表示最后一次执行结果,.exit
可以退出REPL环境 - 在REPL环境中,可以直接使用url.parse()这种API,而不用require()
全局成员global
在nodejs中没有window对象,但是有一个类似的对象global,访问全局成员的时候可以省略global
- __filename - 文件的绝对路径(包含文件名)
- __dirname - 目录的绝对路径(不包含文件名)
- setTimeout() -
- setInterval() -
- clearTimeout() -
- clearInterval -
- console -
- process - 进程对象
- process argv - 返回一个数组,第一个元素:process.execPath(Nodejs的环境路径),第二个元素:当前js文件的路径,后面的元素:命令行参数
- process arch - 当前操作系统的CPU架构:32位 / 64位
- exports - 导出模块中的成员(module.exports的简写)
- module - 导出模块中的成员(成员多时使用)
- require() - 用于引入模块、JSON、本地文件
核心模块
Node为JavaScript提供了很多服务器级别的API,这些API绝大多数都被包装到了一个具名的核心模块中
- fs - 文件操作
- http - HTTP服务构建
- path - 路径操作
- os - 操作系统信息
// 常用模块引入
var fs = require('fs'); // 文件操作
var http = require('http'); // http服务器
var path = require('path');
var os = require('os');
核心模块-FS
Node.js 文件系统(fs 模块)模块中的方法均有异步和同步版本,例如读取文件内容的函数有异步的 fs.readFile() 和同步的 fs.readFileSync()。
异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error)。
建议大家使用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞。
异步编程概念
-
js的运行是单线程的,所以为了防止程序阻塞,引入了事件队列机制。Node.js和浏览器的事件模型类似,都是:单线程 + 事件队列
-
浏览器中的异步操作
- 定时任务
- 事件处理
- Ajax回调处理
-
Node.js中的异步操作
- 文件I/O
- 网络I/O
-
Node.js是基于回调函数的编码风格
案例:初始化目录结构
/* 初始化项目目录 */
let fs = require('fs');
let path = require('path');
let initData = {
projectName: 'mydemo',
data: [
{ name: 'css', type: 'dir' },
{ name: 'js', type: 'dir' },
{ name: 'iamges', type: 'dir' },
{ name: 'index.js', type: 'file' },
{ name: 'index.html', type: 'file' }
]
}
let tpl = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的项目</title>
</head>
<body>
<h1>我的项目</h1>
</body>
</html>
`;
// 创建第一层目录
fs.mkdir( path.join(__dirname, initData.projectName),(err)=>{
if(err) return console.log('创建目录失败');
console.log('目录创建成功:' + initData.projectName);
// 创建第二层目录
initData.data.forEach((item, index)=>{
if( item.type === 'dir' ){
fs.mkdir( path.join(__dirname, initData.projectName, item.name), (err)=>{
if(err) return console.log('创建目录失败');
console.log('目录创建成功:' + path.join(initData.projectName, item.name));
} );
}else if ( item.type === 'file' ){
let tmpTpl = '';
if( item.name === 'index.html' ){
tmpTpl = tpl;
}
fs.writeFile( path.join(__dirname, initData.projectName, item.name), tmpTpl, (err)=>{
if(err) return console.log('写入文件内容失败');
console.log('写入文件成功:', path.join(__dirname, initData.projectName, item.name));
} );
}
});
} );
// 结果
D: 00web2015-Nodejs>node 27-case-fs-initdir.js
目录创建成功:mydemo
目录创建成功:mydemocss
目录创建成功:mydemojs
目录创建成功:mydemoimages
写入文件成功: D: 00web2015-Nodejsmydemoindex.js
写入文件成功: D: 00web2015-Nodejsmydemoindex.html
路径动态拼接的问题
在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 ../ 开头的相对路径时,很容易出现路径动态拼接错误的问题。
原因:代码在运行的时候,会以执行 node 命令时所处的目录 动态拼接出被操作文件的完整路径。
解决方案:在使用 fs 模块操作文件时,直接提供完整的路径(绝对路径),不要提供 ./ 或 ../ 开头的相对路径,从而防止路径动态拼接的问题。
API
-
文件操作
- fs.stat(path, callback) - 获取文件信息
- fs.readFile( path, ['utf8',] callback ) - 读取文件
- fs.writeFile() - 写入文件
- fs.unlink - 删除文件
-
目录操作
- fs.mkdir(path[,mode], callback) - 创建目录
- fs.readdir(path[, options], callback) - 读取目录
- fs.rmdir(path, callback) - 删除目录
-
文件流(大文件)操作
- fs.createReadStream( path[, option] ) -
- fs.createWriteStream( path[, option] ) -
- readStream.pipe(targetStream) -
fs.readFile()
读取文件
语法
以下为异步模式下读取文件的语法格式:
fs.readFile(path[, options], callback)
参数
参数使用说明如下:
- path - 文件的路径。
- options - 该参数是一个对象,包含 {encoding, flag, signal}。默认编码为 utf8, flag 为 'w',signal允许中止正在进行的读取文件
- callback - 回调函数,有2个参数(err, data),文件读取完成后,通过回调函数拿到读取的结果。
注意
- 1、如果读取成功,则err的值为null
- 2、如果读取失败,则err的值为 错误对象,data的值为 undefined
实例
const fs = require('fs');
// 读取文件
fs.readFile('./test.txt', function(err, data){
if( err ) return console.log(err);
console.log(data.toString());
})
注意
fs.writeFile()
写入文件
语法
以下为异步模式下写入文件的语法格式:
fs.writeFile(file, data[, options], callback)
writeFile 直接打开文件默认是 w 模式,所以如果文件存在,该方法写入的内容会覆盖旧的文件内容。
参数
参数使用说明如下:
- file - 文件名或文件描述符。
- data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(缓冲) 对象。
- options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 'w'
- callback - 回调函数,回调函数只包含错误信息参数(err),在写入失败时返回。
注意
- writeFile只能用来创建文件,不能用来创建目录
实例
接下来我们创建 file.js 文件,代码如下所示:
const fs = require("fs");
console.log("准备写入文件");
fs.writeFile('input.txt', '我是通 过fs.writeFile 写入文件的内容', function(err) {
if (err) {
return console.error(err);
}
console.log("数据写入成功!");
console.log("--------我是分割线-------------")
console.log("读取写入的数据!");
fs.readFile('input.txt', function (err, data) {
if (err) {
return console.error(err);
}
console.log("异步读取文件数据: " + data.toString());
});
});
以上代码执行结果如下:
$ node file.js
准备写入文件
数据写入成功!
--------我是分割线-------------
读取写入的数据!
异步读取文件数据: 我是通 过fs.writeFile 写入文件的内容
fs.open()
打开文件
语法
以下为在异步模式下打开文件的语法格式:
fs.open(path, flags[, mode], callback)
参数
参数使用说明如下:
- path - 文件的路径。
- flags - 文件打开的行为。具体值详见下文。
- mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)。
- callback - 回调函数,带有两个参数如:callback(err, fd)。
flags 参数可以是以下值:
Flag | 描述 |
---|---|
r | 以读取模式打开文件。如果文件不存在抛出异常。 |
r+ | 以读写模式打开文件。如果文件不存在抛出异常。 |
rs | 以同步的方式读取文件。 |
rs+ | 以同步的方式读取和写入文件。 |
w | 以写入模式打开文件,如果文件不存在则创建。 |
wx | 类似 'w',但是如果文件路径存在,则文件写入失败。 |
w+ | 以读写模式打开文件,如果文件不存在则创建。 |
wx+ | 类似 'w+', 但是如果文件路径存在,则文件读写失败。 |
a | 以追加模式打开文件,如果文件不存在则创建。 |
ax | 类似 'a', 但是如果文件路径存在,则文件追加失败。 |
a+ | 以读取追加模式打开文件,如果文件不存在则创建。 |
ax+ | 类似 'a+', 但是如果文件路径存在,则文件读取追加失败。 |
实例
接下来我们创建 file.js 文件,并打开 input.txt 文件进行读写,代码如下所示:
var fs = require("fs");
// 异步打开文件
console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
});
以上代码执行结果如下:
$ node file.js
准备打开文件!
文件打开成功!
fs.stat()
获取文件信息
语法
以下为通过异步模式获取文件信息的语法格式:
fs.stat(path, callback)
参数
参数使用说明如下:
- path - 文件路径。
- callback - 回调函数,带有两个参数如:(err, stats), stats 是
fs.Stats
对象。
fs.stat(path)执行后,会将stats类的实例返回给其回调函数。可以通过stats类中的提供方法判断文件的相关属性。例如判断是否为文件:
var fs = require('fs');
fs.stat('/Users/liuht/code/itbilu/demo/fs.js', function (err, stats) {
console.log(stats.isFile()); //true
})
stats类中的方法有:
方法 | 描述 |
---|---|
stats.isFile() | 如果是文件返回 true,否则返回 false。 |
stats.isDirectory() | 如果是目录返回 true,否则返回 false。 |
stats.isBlockDevice() | 如果是块设备返回 true,否则返回 false。 |
stats.isCharacterDevice() | 如果是字符设备返回 true,否则返回 false。 |
stats.isSymbolicLink() | 如果是软链接返回 true,否则返回 false。 |
stats.isFIFO() | 如果是FIFO,返回true,否则返回 false。FIFO是UNIX中的一种特殊类型的命令管道。 |
stats.isSocket() | 如果是 Socket 返回 true,否则返回 false。 |
实例
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
console.log("准备打开文件!");
fs.stat('input.txt', function (err, stats) {
if (err) {
return console.error(err);
}
console.log(stats);
console.log("读取文件信息成功!");
// 检测文件类型
console.log("是否为文件(isFile) ? " + stats.isFile());
console.log("是否为目录(isDirectory) ? " + stats.isDirectory());
});
以上代码执行结果如下:
$ node file.js
准备打开文件!
{ dev: 16777220,
mode: 33188,
nlink: 1,
uid: 501,
gid: 20,
rdev: 0,
blksize: 4096,
ino: 40333161,
size: 61,
blocks: 8,
atime: Mon Sep 07 2015 17:43:55 GMT+0800 (CST),
mtime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST),
ctime: Mon Sep 07 2015 17:22:35 GMT+0800 (CST) }
读取文件信息成功!
是否为文件(isFile) ? true
是否为目录(isDirectory) ? false
fs.read()
读取文件
语法
以下为异步模式下读取文件的语法格式:
fs.read(fd, buffer, offset, length, position, callback)
该方法使用了文件描述符来读取文件。
参数
参数使用说明如下:
- fd - 通过
fs.open()
方法返回的文件描述符。 - buffer - 数据写入的缓冲区。
- offset - 缓冲区写入的写入偏移量。
- length - 要从文件中读取的字节数。
- position - 文件读取的起始位置,如果 position 的值为 null,则会从当前文件指针的位置读取。
- callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象。
实例
input.txt 文件内容为:
菜鸟教程官网地址:www.runoob.com
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
var buf = new Buffer.alloc(1024);
console.log("准备打开已存在的文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备读取文件:");
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}
console.log(bytes + " 字节被读取");
// 仅输出读取的字节
if(bytes > 0){
console.log(buf.slice(0, bytes).toString());
}
});
});
以上代码执行结果如下:
$ node file.js
准备打开已存在的文件!
文件打开成功!
准备读取文件:
42 字节被读取
菜鸟教程官网地址:www.runoob.com
fs.close()
关闭文件
语法
以下为异步模式下关闭文件的语法格式:
fs.close(fd, callback)
该方法使用了文件描述符来读取文件。
参数
参数使用说明如下:
- fd - 通过
fs.open()
方法返回的文件描述符。 - callback - 回调函数,没有参数。
实例
input.txt 文件内容为:
菜鸟教程官网地址:www.runoob.com
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
var buf = new Buffer.alloc(1024);
console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("准备读取文件!");
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}
// 仅输出读取的字节
if(bytes > 0){
console.log(buf.slice(0, bytes).toString());
}
// 关闭文件
fs.close(fd, function(err){
if (err){
console.log(err);
}
console.log("文件关闭成功");
});
});
});
以上代码执行结果如下:
$ node file.js
准备打开文件!
文件打开成功!
准备读取文件!
菜鸟教程官网地址:www.runoob.com
文件关闭成功
fs.ftruncate()
截取文件
语法
以下为异步模式下截取文件的语法格式:
fs.ftruncate(fd, len, callback)
该方法使用了文件描述符来读取文件。
参数
参数使用说明如下:
- fd - 通过
fs.open()
方法返回的文件描述符。 - len - 文件内容截取的长度。
- callback - 回调函数,没有参数。
实例
input.txt 文件内容为:
site:www.runoob.com
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
var buf = new Buffer.alloc(1024);
console.log("准备打开文件!");
fs.open('input.txt', 'r+', function(err, fd) {
if (err) {
return console.error(err);
}
console.log("文件打开成功!");
console.log("截取10字节内的文件内容,超出部分将被去除。");
// 截取文件
fs.ftruncate(fd, 10, function(err){
if (err){
console.log(err);
}
console.log("文件截取成功。");
console.log("读取相同的文件");
fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){
if (err){
console.log(err);
}
// 仅输出读取的字节
if(bytes > 0){
console.log(buf.slice(0, bytes).toString());
}
// 关闭文件
fs.close(fd, function(err){
if (err){
console.log(err);
}
console.log("文件关闭成功!");
});
});
});
});
以上代码执行结果如下:
$ node file.js
准备打开文件!
文件打开成功!
截取10字节内的文件内容,超出部分将被去除。
文件截取成功。
读取相同的文件
site:www.r
文件关闭成功
fs.unlink()
删除文件
语法
以下为删除文件的语法格式:
fs.unlink(path, callback)
参数
参数使用说明如下:
- path - 文件路径。
- callback - 回调函数,没有参数。
实例
input.txt 文件内容为:
site:www.runoob.com
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
console.log("准备删除文件!");
fs.unlink('input.txt', function(err) {
if (err) {
return console.error(err);
}
console.log("文件删除成功!");
});
以上代码执行结果如下:
$ node file.js
准备删除文件!
文件删除成功!
再去查看 input.txt 文件,发现已经不存在了。
fs.mkdir()
创建目录
语法
以下为创建目录的语法格式:
fs.mkdir(path[, options], callback)
参数
参数使用说明如下:
- path - 文件路径。
- options 参数可以是:
- recursive - 是否以递归的方式创建目录,默认为 false。
- mode - 设置目录权限,默认为 0777。
- callback - 回调函数,没有参数。
实例
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
// tmp 目录必须存在
console.log("创建目录 /tmp/test/");
fs.mkdir("/tmp/test/",function(err){
if (err) {
return console.error(err);
}
console.log("目录创建成功。");
});
以上代码执行结果如下:
$ node file.js
创建目录 /tmp/test/
目录创建成功。
可以添加 recursive: true 参数,不管创建的目录 /tmp 和 /tmp/a 是否存在:
fs.mkdir('/tmp/a/apple', { recursive: true }, (err) => {
if (err) throw err;
});
fs.readdir()
读取目录
语法
以下为读取目录的语法格式:
fs.readdir(path, callback)
参数
参数使用说明如下:
- path - 文件路径。
- callback - 回调函数,回调函数带有两个参数err, files,err 为错误信息,files 为 目录下的文件数组列表。
实例
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
console.log("查看 /tmp 目录");
fs.readdir("/tmp/",function(err, files){
if (err) {
return console.error(err);
}
files.forEach( function (file){
console.log( file );
});
});
以上代码执行结果如下:
$ node file.js
查看 /tmp 目录
input.out
output.out
test
test.txt
fs.rmdir()
删除目录
语法
以下为删除目录的语法格式:
fs.rmdir(path, callback)
参数
参数使用说明如下:
- path - 文件路径。
- callback - 回调函数,没有参数。
实例
接下来我们创建 file.js 文件,代码如下所示:
var fs = require("fs");
// 执行前创建一个空的 /tmp/test 目录
console.log("准备删除目录 /tmp/test");
fs.rmdir("/tmp/test",function(err){
if (err) {
return console.error(err);
}
console.log("读取 /tmp 目录");
fs.readdir("/tmp/",function(err, files){
if (err) {
return console.error(err);
}
files.forEach( function (file){
console.log( file );
});
});
});
以上代码执行结果如下:
$ node file.js
准备删除目录 /tmp/test
读取 /tmp 目录
……
fs.createReadStream()
文件流(大文件)操作
语法
fs.createReadStream( path[, option] )
fs.createWriteStream()
文件流(大文件)操作
语法
fs.createWriteStream( path[, option] )
readStream.pipe()
文件流(大文件)操作
语法
readStream.pipe(targetStream)
示例
/* 文件流操作 */
// createReadStream 和 createWriteStream
let fs = require('fs');
let path = require('path');
let sPath = path.join(__dirname, 'data', 'typora-setup-x64.exe');
let ePath = path.join(__dirname, 'www', 'typora-setup-x64.exe');
let readStream = fs.createReadStream(sPath);
let writeSteam = fs.createWriteStream(ePath);
readStream.on('data', (chunk)=>{
writeSteam.write(chunk);
});
readStream.on('end', ()=>{
console.log('写入文件成功');
});
// pipe
fs.createReadStream('./data/hello.txt').pipe(fs.createWriteStream('./www/hi.txt'));
核心模块-PATH
path 模块提供了一些用于处理文件路径的小工具
API
-
path.basename(path, [ext]) - 获取路径的最后一部分(文件名 或 最后一层目录)
-
path.dirname(path) - 获取路径的目录名
-
path.extname(path) - 获取路径的扩展名
-
path.format(objPath) - 把路径对象转化为路径字符串
-
path.parse(strPath) - 把路径字符串转化为路径对象
-
path.isAbsulute(path) - 判断是否是绝对路径
-
path.join([...paths]) - 拼接路径(会识别 ['.', '..']这2个字符 )
-
path.normalize(path) - 规范化路径写法(会识别 ['.', '..']这2个字符 )
-
path.relative(from, to) - 计算from相对to的路径
-
path.resolve([...paths]) - 解析路径(相当于cmd中的cd命令)
-
-
path.delimiter - 路径分隔符(windows使用号 linux使用/号)
-
path.sep - 环境变量分隔符(windows使用;号 linux使用:号)
let path = require('path');
let url = 'D:/IT资料/笔记/Web笔记-传智39期/15-Nodejs/15-Nodejs.md';
let url2 = 'D:/000/web20/15-Nodejs/06-模拟apache';
// path.basename() 获取路径最后一部分
console.log( path.basename(url) ); // 15-Nodejs.md
console.log( path.basename(url2) ); // 06-模拟apache
console.log( path.basename(url, '.md') ); // 15-Nodejs
// path.dirname() 获取路径的目录名
console.log( path.dirname(url) ); // D:/IT资料/笔记/Web笔记-传智39期/15-Nodejs
// path.extname() 获取路径的扩展名
console.log( path.extname(url) ); // .md
// path.parse(strPath) 路径字符串 -> 路径对象
console.log(path.parse( __filename ));
/*
D:\000\web20\15-Nodejs\18-path-parse.js
结果
{
root: 'D:\',
dir: 'D:\000\web20\15-Nodejs',
base: '18-path-parse.js',
ext: '.js',
name: '18-path-parse'
}
*/
// path.format(objPath) 路径对象 ->路径字符串
let path = require('path');
let objPath = {dir: 'D:\000\web20\15-Nodejs', base: '18-path-parse.js'}
console.log( path.format(objPath) ); // D: 00web2015-Nodejs18-path-parse.js
//
path.join()
连接路径
使用特定于平台的分隔符作为定界符将所有给定的 path
片段连接在一起,然后规范化生成的路径
语法
path.join([...paths])
参数
- paths - 路径片段的序列。
返回 - 连接后的路径字符串
注意
- 1、零长度的
path
片段被忽略。 如果连接的路径字符串是零长度字符串,则将返回'.'
,表示当前工作目录。 - 2、如果任何路径片段不是字符串,则抛出
TypeError
实例
path.join('/foo', 'bar', 'baz/asdf', 'quux', '..');
// 返回: '/foo/bar/baz/asdf'
path.join('foo', {}, 'bar');
// 抛出 'TypeError: Path must be a string. Received {}'
path.dirname()
返回目录名
语法
path.dirname(path)
参数
- path - 路径字符串
如果 path
不是字符串,则抛出 TypeError
返回 - 返回 path
的目录名
实例
path.dirname('/foo/bar/baz/asdf/quux');
// 返回: '/foo/bar/baz/asdf'
path.basename()
返回文件名
语法
path.basename(path[, ext])
参数
- path - 路径字符串。
- ext - 可选的文件扩展名
如果 path
不是字符串,或者如果给定 ext
并且不是字符串,则抛出 TypeError
返回 - 返回 path
的最后一部分,尾随的目录分隔符被忽略
实例
path.basename('/foo/bar/baz/asdf/quux.html');
// 返回: 'quux.html'
path.basename('/foo/bar/baz/asdf/quux.html', '.html');
// 返回: 'quux'
path.extname()
返回扩展名
语法
path.extname(path)
参数
- path -
<string>
,路径字符串。
如果 path
不是字符串,则抛出 TypeError
返回 - <string>
- 返回
path
的扩展名。即path
的最后一部分中从最后一次出现的.
(句点)字符到字符串的结尾。 - 如果
path
的最后一部分中没有.
,或者除了path
的基本名称(参见path.basename()
)的第一个字符之外没有.
个字符,则返回空字符串。
实例
path.extname('index.html');
// 返回: '.html'
path.extname('index.coffee.md');
// 返回: '.md'
path.extname('index.');
// 返回: '.'
path.extname('index');
// 返回: ''
path.extname('.index');
// 返回: ''
path.extname('.index.md');
// 返回: '.md'
path.parse()
解析字符串路径为路径对象
语法
path.parse(path)
参数
- path - 路径字符串。如果
path
不是字符串,则抛出TypeError
。
返回 - <object>
- 返回一个对象,其属性表示
path
的重要元素。 尾随的目录分隔符被忽略 - 返回的对象将具有以下属性:{ dir, root, base, name, ext }
实例
path.parse('C:\path\dir\file.txt');
// 返回:
// { root: 'C:\',
// dir: 'C:\path\dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' }
path.format()
解析路径对象为为字符串路径
语法
path.format(pathObject)
参数
- pathObject - 具有{dir, root, base, name, ext}属性的 JavaScript 对象,标红的参数优先级高于其他
返回 - <string>
- 从对象返回路径字符串
注意
- 1、当向
pathObject
提供属性时,存在一个属性优先于另一个属性的组合- 如果提供
pathObject.dir
,则忽略pathObject.root
- 如果
pathObject.base
存在,则忽略pathObject.ext
和pathObject.name
- 如果提供
实例
// 提供了`dir`,`root` 将被忽略
path.format({
root: '/ignored',
dir: '/home/user/dir',
base: 'file.txt'
});
// 返回: '/home/user/dir/file.txt'
// 提供了`base`,`ext` 将被忽略
path.format({
root: '/',
base: 'file.txt',
ext: 'ignored'
});
// 返回: '/file.txt'
// Windows系统
path.format({
dir: 'C:\path\dir',
base: 'file.txt'
});
// 返回: 'C:\path\dir\file.txt'
path.normalize()
规范化路径
规范化给定的 path,解析 '..'
和 '.'
片段。
语法
path.normalize(path)
参数
- path - 待规范的路径
返回 - string
实例
path.normalize('C:\temp\\foo\bar\..\');
// 返回: 'C:\temp\foo\'
path.resolve()
解析路径片段为绝对路径
语法
path.resolve([...paths])
参数
- ...paths -
<string>
,路径或路径片段的序列
返回 - <string>
- 将路径或路径片段的序列解析为绝对路径
注意
- 1、给定的路径序列从右到左处理,每个后续的
path
会被追加到前面,直到构建绝对路径 - 2、如果在处理完所有给定的
path
片段之后,还没有生成绝对路径,则使用当前工作目录 - 3、生成的路径被规范化,并删除尾部斜杠(除非路径解析为根目录)
- 4、零长度的
path
片段被忽略 - 5、如果没有传入
path
片段,则path.resolve()
将返回当前工作目录的绝对路径
实例
path.resolve('/foo/bar', './baz');
// 返回: '/foo/bar/baz'
// 给定的路径序列从右到左处理,直到构建绝对路径
path.resolve('/foo/bar', '/tmp/file/');
// 返回: '/tmp/file'
// 如果在处理完所有给定的 path 片段之后,还没有生成绝对路径,则使用当前工作目录
path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// 如果当前工作目录是 /home/myself/node,
// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif'
path.relative()
返回从from到to的相对路径
语法
path.relative(from, to)
参数
- from -
<string>
,起始路径 - to -
<string>
,目标路径
返回
- 根据当前工作目录返回从
from
到to
的相对路径
注意
- 如果
from
和to
都解析为相同的路径(在分别调用path.resolve()
之后),则返回零长度字符串 - 如果零长度字符串作为
from
或to
传入,则将使用当前工作目录而不是零长度字符串 - 如果
from
或to
不是字符串,则抛出TypeError
实例
path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// 返回: '../../impl/bbb'
path.isAbsolute()
路径是否为绝对路径
语法
path.relative(path)
参数
- path -
<string>
,路径字符串
返回 - <boolean>
注意
- 1、如果
path
不是字符串,则抛出TypeError
。 - 2、如果给定的
path
是零长度字符串,则将返回false
实例
path.isAbsolute('//server'); // true
path.isAbsolute('\\server'); // true
path.isAbsolute('C:/foo/..'); // true
path.isAbsolute('C:\foo\..'); // true
path.isAbsolute('bar\baz'); // false
path.isAbsolute('bar/baz'); // false
path.isAbsolute('.'); // false
核心模块-HTTP
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer()
方法,就能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
IP地址和端口号
所有联网的程序都需要进行网络通信,计算机中只有一个物理网卡,而且同一个局域网中,网卡的地址必须是唯一的,网卡是通过唯一的IP地址来进行定位
- IP地址用来定位计算机
- 端口号用来定位具体的应用程序(所有需要联网通信软件的软件都必须有端口号)
- 端口号的范围:[0 , 65536]
- 可以同时开启多个服务,但一定要确保不同服务占用的端口号不一致
- 同一台电脑,同一个端口号不能同时被占用
- request.socket.remoteAddress - 远程计算机的IP地址
- request.socket.remotePort - 远程计算机的端口号
// 加载 http 核心模块
var http = require('http');
// 创建服务器
var server = http.createServer();
// 绑定request事件,监听客户端request请求
server.on('request', function(request,response){
console.log('已经收到客户端请求!,请求路径:'+request.url);
console.log('已经收到客户端请求!,远程IP地址、端口号:'+request.socket.remoteAddress+':'+request.socket.remotePort);
response.end('hello nodejs');
});
// 启动服务器
server.listen(3000, function() {
console.log('服务器启动成功,请通过127.0.0.1:3000/访问');
});
响应内容类型 Content-Type
在服务器端默认发送的数据,是utf8编码,但是浏览器不知道你是utf8编码的内容
浏览器在不知道服务器响应内容的编码格式的情况下,会按照当前操作系统默认的编码格式(GBK)去解析
不同资源对应的Content-Type是不一样的,具体参照:https://tool.oschina.net/commons
常用Content-Type:
-
text/plain - 普通文本
-
text/html - HTML文本
-
text/css - CSS文件
-
application/x-javascript - JS文件
-
image/jpeg - jpg,jpeg图片
-
image/png - png图片
-
image/gif - gif图片
图片就不需要指定编码charset=utf-8了,因为常说的编码一般指的是:字符编码
// 解决中文乱码
// text/plain 普通文本格式(直接输出div等标签字符)
// text/html HTML文本格式(会将div等标签字符渲染)
response.setHeader('Content-Type', 'text/plain; charset=utf-8');
response.end();
// PHP中设置编码
header('Content-Type:text/html;charset=utf-8');
// HTML中设置编码
<meta charset="utf-8">
根据不同URL读取不同文件
通过网络发送文件内容,http + fs
/* 模拟Apache,根据URL的不同读取不同的文件 */
// 1. http结合fs发送文件中的数据
// 2. 使用Content-Type指定编码
// 注意:该脚本不能访问html文件中的link、script加载的css、js文件
var http = require('http');
var fs = require('fs');
var server = http.createServer();
server.on('request', function(req, res){
var url = req.url;
if( url == '/' ){
// 读取html文件
fs.readFile('index.html', function (err, data) {
if( err ){
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end('读取文件出错');
}else{
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(data.toString());
}
});
}else if( url == '/img' ){
// 读取图片文件
fs.readFile('title2.png',function(err, data){
if( err ){
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('读取图片出错');
return false;
}
res.setHeader('Content-Type', 'image/png');
res.end(data);
});
}
});
server.listen('3000', function(){
console.log('服务器已经开启,请通过localhost:3000访问');
});
请求对象 request
只要服务器接收到了客户端的请求,就会调用通过 server.on() 为服务器绑定的 request 事件处理函数。
如果想在事件处理函数中,访问与客户端相关的数据或属性,可以使用如下的方式:
server.on('request', function(req, res){
// 访问与客户端请求相关的数据或属性
// 常用
console.log( req.url ); // /
console.log( req.method ); // GET
console.log( req.headers ); // {host,connection, accept}
console.log( req.headers.host ); // 127.0.0.1:3000
console.log( req.complete ); // false
// 不常用
console.log( req.httpVersion ); // 1.1
console.log( req.statusCode ); // null
console.log( req.statusMessage ); // null
console.log( req.aborted ); // false
});
注意
- 一个请求对应一个响应,如果在一个请求的过程中,已经结束了响应,则不能重复发送响应。没有请求就没有响应
响应对象 response
在服务器的 request 事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下的方式:
server.on('request', function(req, res){
// 响应
res.setHeader('Content-Type', 'text/html;charset=utf-8'); // 设置字符编码,解决中文乱码问题
res.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']); // 设置cookies
res.writeHead(200, {'Content-Length': Buffer.byteLength('body'), 'Content-Type': 'text/plain'}); //
res.end('结束本次访问请求'); // 结束请求,并输出文本
});
统一处理静态资源
- 实现请求不同文件时响应对应文件的内容
/* 模拟Apache - 实现请求不同文件时响应对应文件的内容 */
var http = require( 'http' );
var path = require( 'path' );
var fs = require('fs');
var server = http.createServer(function (req, res) {
console.log('服务器已开启' + req.url);
var url = __dirname + req.url;
if( url === '/' ) url = '/index.html';
fs.readFile(rootDir + url, function (err, data) {
if( err ) return res.end('readFile ' + url + ' error!');
res.end( data );
});
}).listen('3000');
// 浏览器
localhost:3000
localhost:3000/index.html
localhost:3000/admin/login.html
// 返回
D: 00web2015-Nodejs 8-http-根据url响应不同的文件>node init.js
服务器已开启/
服务器已开启/favicon.ico
服务器已开启/data.txt
服务器已开启/favicon.ico
服务器已开启/admin/login.html
服务器已开启/favicon.ico
服务器已开启/
服务器已开启/favicon.ico
服务器已开启/index.html
服务器已开启/favicon.ico
- 渲染指定目录的列表文件
/* 模拟Apache - 渲染指定目录的列表文件(技术http,fs,art-template) */
var http = require('http');
var fs = require('fs');
var template = require('art-template');
// 1. 指定需要遍历的文件目录
var rootDir = 'D:/000/web20/15-Nodejs/www';
http.createServer(function(req, res){
// 2. 获取指定目录下的文件列表,files是一个数组:[ 'admin', 'data.txt', 'images', 'index.html', 'index.js' ]
fs.readdir(rootDir, function(err, files){
if( err ) return res.end('dir ' + rootDir + ' not found!');
// 3. 读取模板引擎文件 template10.html
fs.readFile('template10.html', function(err, data){
if( err ) return res.end('file template10.html not found!');
// 4. 使用模板引擎art-template语法对读取到的模板文件进行替换渲染
var newTemplate = template.render(data.toString(), {
files: files,
title: rootDir
});
// 5. 输出渲染之后的模板内容到浏览器
res.end(newTemplate);
});
});
}).listen('3000', function(){
console.log('server runing...');
});
参数的传递和获取
get参数获取
let url = require('url');
let u = new URL('http://www.baidu.com:3633/index.html?name=jack&age=10#101');
let uSearch = new URLSearchParams(u.searchParams);
console.log( uSearch.get('name') ); // jack
console.log( uSearch.get('age') ); // 10
post参数获取
- querystring.parse(str[, sep, eq, options]) - 把url参数字符串转为对象
- querystring.stringify(obj[, sep, eq, options]) - 把对象转化为url字符串
const querystring = require('querystring');
// parse()
let param = 'uname=lisi&age=10&age=13';
let obj = querystring.parse(param);
console.log(obj); // [Object: null prototype] { uname: 'lisi', age: [ '10', '13' ] }
// stringify()
let obj1 = {
flag: '123',
abc: ['hello', 'hi']
}
let str1 = querystring.stringify(obj1);
console.log(str1); // flag=123&abc=hello&abc=hi
API
- http.createServer - 创建服务器
- http.Server类
- 'request'事件 - 监听客户端的request请求
- server.socket
- server.socket.remoteAddress - 获取远程客户端的IP地址
- server.socket.remotePort - 获取远程客户端的端口号
- server.listen([port, ip, callback]) - 监听客户端的访问地址和端口
- http.Incomingmessage类(request)
- message.url - 获取URL中端口之后的路径
- http.ServerResponse类
- response.end([data, encoding, callback]) - 向客户端响应内容,只能写一次
- response.write(chunk[, encoding, callback]) - 向客户端响应内容,可以写多次
- response.setHeader(name, value) - 设置响应内容的类型和字符编码
- response.writeHead(stateCode, {name: value})
http.createServer
创建服务器
语法
http.createServer([options][, requestListener])
参数
- options -
<object>
- - requestListener -
<function>
-
返回
- -
<http.Server>
-
注意
requestListener
是自动添加到request
事件的函数
实例
// 创建一个服务器实例对象
const server = http.creatServer();
// 使用服务器实例的.on()方法,为服务器绑定一个request事件
server.on('request', function(req, res){
...
});
http.Server类
'request'事件
监听客户端的request请求
server.listen()
监听客户端的访问地址和端口
// 调用服务器实例的.listen(port, callback) 方法,启动web服务器
server.listen(80, function(){
console.log('server running at http://127.0.0.1');
})
http.Incomingmessage类(request)
req.url
获取URL中端口之后的路径
req.url // / | /index.html | /about.html...
req.method
获取请求的方法
req.method // GET | POST...
http.ServerResponse类
response.end()
向客户端响应内容,只能写一次
语法
response.end([data[, encoding]][, callback])
参数
- data -
<String> | <Buffer>
- 如果指定了 data,则其效果类似于调用 response.write(data, encoding) 后跟 response.end(callback) - encoding -
<String>
- 字符编码 - callback -
<Function>
- 如果指定了 callback,则将在响应流完成时调用
返回
注意
实例
response.write()
向客户端响应内容,可以写多次
response.setHeader()
为标头对象设置单个标头值
语法
request.setHeader(name, value)
参数
- name -
<String>
- 键 - value -
<any>
- 值
返回
注意
- 1、如果该标头已经存在于待发送的标头中,则其值将被替换
- 2、在此处使用字符串数组发送具有相同名称的多个标头。 非字符串值将不加修改地存储
- 3、
request.getHeader()
可能返回非字符串值。 但是,非字符串值将转换为字符串以进行网络传输
实例
request.setHeader('Content-Type', 'application/json');
request.setHeader('Content-Type', 'text/html;charset=utf-8');
request.setHeader('Cookie', ['type=ninja', 'language=javascript']);
response.writeHead()
向请求发送响应头
语法
response.writeHead(statusCode[, statusMessage][, headers])
参数
- statusCode -
<Number>
- 响应状态码。状态码是 3 位的 HTTP 状态码,如 404 - statusMessage -
<String>
- 响应状态码描述文本 - headers -
<Object> | <Array>
- 响应头。
返回 -<http.ServerResponse>
注意
- 1、此方法只能在消息上调用一次,并且必须在调用
response.end()
之前调用 - 2、如果在调用此之前调用了
response.write()
或response.end()
,则将计算隐式 / 可变的标头并调用此函数 - 3、当标头已使用
response.setHeader()
设置时,则它们将与任何传给response.writeHead()
的标头合并,其中传给response.writeHead()
的标头优先
实例
res.writeHead(200, {'Content-Length': Buffer.byteLength('body'), 'Content-Type': 'text/plain'}); //
response.getHeader()
读取已排队但未发送到客户端的标头
语法
response.getHeader(name)
参数
- name -
<String>
- 标头名。该名称不区分大小写
返回 -<any>
-
- 返回值的类型取决于提供给
response.setHeader()
的参数
实例
response.setHeader('Content-Type', 'text/html');
response.setHeader('Content-Length', Buffer.byteLength(body));
response.setHeader('Set-Cookie', ['type=ninja', 'language=javascript']);
const contentType = response.getHeader('content-type'); // contentType 是 'text/html'
const contentLength = response.getHeader('Content-Length'); // contentLength 是数字类型
const setCookie = response.getHeader('set-cookie'); // setCookie 是 string[] 类型
response.hasHeader()
是否存在某标头
语法
response.hasHeader(name)
参数
- name -
<String>
- 标头名。该名称不区分大小写
返回 -<Boolean>
-
- 如果存在该标头,则返回true
- 吐过不存在该标头,则返回false
实例
const hasContentType = response.hasHeader('content-type'); // 返回true或者false
核心模块-Buffer
Buffer对象是Node处理二进制数据的一个接口。它是Node原生提供的全局对象,可以直接使用,不需要require(‘buffer’)
Buffer本质上就是字节数组
实例化Buffer对象
- Buffer.alloc(size) - 分配一个大小为 size 字节的新 Buffer
- Buffer.from(string | buffer ) - 创建一个包含 string 或 buffer 的新 Buffer
let buf = new Buffer(5); // 不推荐
let buf = Buffer.alloc(5); // 推荐 <Buffer 00 00 00 00 00>
let buf = Buffer.from('this','utf8'); // <Buffer 74 68 69 73>
let buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
静态方法
- Buffer.isEncoding(encoding) - 如果支持 encoding 字符编码,则返回 true,否则返回 false
- Buffer.isBuffer(obj) - 如果 obj 是一个 Buffer,则返回 true,否则返回 false
- Buffer.byteLength(string[, encoding]) - 当使用 encoding 进行编码时,返回字符串的字节长度
- Buffer.concat( [buf1, buf2, buf3...] ) - 返回一个合并了 list 中所有 Buffer 实例的新 Buffer
实例方法
- buf.write(string[, offset, length, encoding]) - 根据 encoding 指定的字符编码将 string 写入到 buf 中的 offset 位置,length 参数是要写入的字节数。 如果 buf 没有足够的空间保存整个字符串,则只会写入 string 的一部分
- buf.slice([start, end]) - 返回一个新的 Buffer,它引用与原始的 Buffer 相同的内存,但是由 start 和 end 索引进行偏移和裁剪
- buf.toString([encoding, start, end]) - 根据 encoding 指定的字符编码将 buf 解码成字符串。 传入 start 和 end 可以只解码 buf 的子集
- buf.toJSON() - toJSON方法不需要显式调用,当JSON.stringify方法调用的时候会自动调用toJSON方法
// buf.write()
let buf = Buffer.alloc(5);
buf.write('hello',2,2);
console.log(buf); // <Buffer 00 00 h e 00>
// buf.slice()
let buf = Buffer.from('hello');
let buf1 = buf.slice(2,3);
console.log(buf === buf1); //false
console.log(buf1.toString()); // llo
// JSON.stringify(buf);
const buf = Buffer.from('hello');
const json = JSON.stringify(buf);
console.log(json);
核心模块-URL
API
-
url.parse(strUrl[, queryType]) - 解析url地址为对象格式
参数
- queryType - true:query解析为对象格式,false:query解析为字符串格式
-
url.format(objUrl[, options])- 将对象格式的URL转化为字符串格式
-
URL类
-
new URL(input[, base]) - 初始化一个URL类的对象
// 代码 let url = require('url'); let u = new URL('http://www.baidu.com:3633/index.html?name=jack&age=10#101'); console.log(u); console.log(u.searchParams); let params = new URLSearchParams(u.search); console.log( params.get('name') ); // jack // 结果 URL { + href: 'http://www.baidu.com:3633/index.html?name=jack&age=10#101', + origin: 'http://www.baidu.com:3633', protocol: 'http:', username: '', password: '', + host: 'www.baidu.com:3633', + hostname: 'www.baidu.com', port: '3633', + pathname: '/index.html', + search: '?name=jack&age=10', + searchParams: URLSearchParams { 'name' => 'jack', 'age' => '10' }, hash: '#101' }
-
u.href
-
u.origin
-
u.host
-
u.hostname
-
u.pathname
-
u.search
-
u.toString()
-
-
URLSearchParams类
- urlSearchParams.get(name) - 通过name获取URL参数值
核心模块-OS
API
- os.cpus() - 获取当前机器的CPU信息
- os.totalmem() - 获取当前机器的内存大小(单位:byte)
模块化开发
模块化的基本概念
什么是模块化
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
一个js文件就是一个模块,模块内部的成员相互独立,Node中是模块作用域
传统的非模块化开发的缺点
- 1、命名冲突
- 2、文件依赖
把代码进行模块化拆分的好处
- 1、提高了代码的复用性
- 2、提高了代码的可维护性
- 3、可以实现按需加载
模块化规范
模块化规范就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:
-
使用什么样的语法格式来引用模块
-
在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处
大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
浏览器端模块化规范
JS天生不支持模块化,浏览器中如果想要像Node一样模块编程,就需要下面的第三方库:
- AMD - requirejs
- CMD - seajs
服务器端模块化规范
- CommonJS - Node.js
Node中模块化
Node中模块的分类
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
-
内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)
-
自定义模块(用户创建的每个 .js 文件,都是自定义模块)
-
第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
加载模块
使用 require()
方法,可以加载需要的内置模块、用户自定义模块、第三方模块进行使用。例如:
注意:
- 1、使用 require() 方法加载其它模块时,会执行被加载模块中的代码。
- 2、在使用 require() 加载用户自定义模块期间,可以省略
.js
的后缀名
Node中的模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
好处 :防止了全局变量污染的问题
向外共享模块作用域中的成员
-
module 对象
在每个 .js 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息,打印如下:
-
module.exports 对象
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。
外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。
注意:使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准
-
exports 对象
由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了
exports 对象
。默认情况下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准。
-
exports 和 module.exports 的使用误区
// exports和module.exports之间的关系 exports = module.exports = {} console.log( exports === module.exports ) // true
时刻谨记,require() 模块时,得到的永远是 module.exports 指向的对象
注意:为了防止混乱,建议大家不要在同一个模块中同时使用 exports 和 module.exports
分析 :
exports = { gender: '男', age: 22 }
,这种写法是无效的,此时的exports只相当于普通的变量- 只有
exports.gender = '男'
这样的写法才是有效的
示例
module.exports = {
name: 'zs',
age:33
}
console.log(module); // exports: { name: 'zs', age: 33 },
console.log('--------------------------------');
module.exports.name = 'Jack';
module.exports.age = 100;
console.log(module); // exports: { name: 'Jack', age: 100 },
console.log('--------------------------------');
exports = {
user: 'Tom',
pass: '123'
}
console.log(module); // exports: {},
// 注意:此处的exports不能直接等于一个对象,
console.log('--------------------------------');
exports.user = 'ZS';
exports.pass = '123';
console.log(module); // exports: { user: 'ZS', pass: '123' },
console.log('--------------------------------');
console.log( exports ); // {}
Node中模块化的规范
Node.js 遵循了 CommonJS 模块化规范,CommonJS 规定了模块的特性和各模块之间如何相互依赖。
CommonJS 规定:
- 1、每个模块内部,module 变量代表当前模块。
- 2、module 变量是一个对象,它的 exports 属性(即 module.exports)是对外的接口。
- 3、加载某个模块,其实是加载该模块的 module.exports 属性。require() 方法用于加载模块。
npm
多个模块可以形成包,不过要满足特定的规则才能形成规范的包
npm概念
npm(node.js package management)
npm网站:全球最大的模块生态系统,里面所有的模块都是开源免费的;网址:https://www.npmjs.com/
npm命令行工具:Node.js的包管理工具
npm安装
本地安装
本地安装的包在当前项目目录下的node_modules目录下,本地安装的包一般用于实际的开发工作
npm install 包名称 // 本地安装
全局安装
全局安装的包位于nodejs环境的node_modules目录下,全局安装的包一般用于命令行工具
npm install -g 包名称 // 全局安装(关键:-g)
解决npm安装包被墙的问题
-
--registry
# 查看当前的下包镜像源 npm config get registry # 把registry选项加到配置文件 npm config set registry https://registry.npm.taobao.org ## 修改回来 npm config set registry https://registry.npmjs.org/ # 如果不设置registry的话,就需要每次安装包的是否在后面加上 npm install 包名 --registry=https://registry.npm.taobao.org # 查看npm 配置信息 npm config list
-
cnpm
- 淘宝NPM镜像,与官方NPM的同步频率目前为10分钟一次
- 官网: http://npm.taobao.org/
# 安装cnpm npm install -g cnpm –registry=https//registry.npm.taobao.org # 使用cnpm安装包 cnpm install 包名
-
nrm
- 作用:修改镜像源
- 项目地址:https://www.npmjs.com/package/nrm
# 安装nrm npm install -g nrm # 查看所有可用的镜像源 nrm ls # npm ---------- https://registry.npmjs.org/ # yarn --------- https://registry.yarnpkg.com/ # tencent ------ https://mirrors.cloud.tencent.com/npm/ # cnpm --------- https://r.cnpmjs.org/ # taobao ------- https://registry.npmmirror.com/ # npmMirror ---- https://skimdb.npmjs.com/registry/ # 切换镜像源 nrm use taobao
npm常用命令
npm 命令中的选项和简写
// install
-g // 全局安装
--save // 向生产环境添加依赖(dependencies)
--save-dev // 向开发环境添加依赖(devDependencies)
--production // 安装包时,只安装生产环境中的依赖包,去掉则2个环境的依赖包都安装
// init
-y // 一直yes 、 一直回车
// 简写
install == i
uninstall == un
--save == -S
--save-dev == -D
list == ls
-
安装包(如果没有指定版本号,安装最新版本)
- npm install 包名称[@版本号] - 本地安装
- npm install -g 包名称[@版本号] - 全局安装
- npm install 包名 --save - 下载并保存依赖项到 dependencies 中(npm@5之后可以省略--save)
- npm install 包名1 包名2... - 一次性安装多个包(包之间有个空格)
- npm install 包名 --save-dev - 只安装到 devDependencies中(只在开发阶段使用,上线后不需要)(简写 npm i 包名 -D)
- npm install - 一次性安装 dependencies 中的全部依赖项
-
更新包(更新到最新版本)(有时并不好使)
- npm update [-g] 包名称
-
查看版本
- npm view 包名 version - 查看最新版本
- npm view 包名 versions - 查看所有版本
-
查看所有已安装包
- npm ls - 查看所有本地安装的包(ls是 list 的简写)
- npm ls -g - 查看所有全局安装的包(好像不好用)
-
卸载包
- npm uninstall [ -g ] 包名称
- npm uninstall 包名称 --save - 删除包的同时也从依赖项删除(npm@5之后可以省略--save)
-
初始化
- npm init [-y] 初始化项目的package.json。 -y:表示直接生成默认的package.json,不在命令窗口写入
-
使用帮助
- npm help - 查看使用帮助
- npm 命令 --help - 查看具体命令的使用帮助
案例:安装 i5ting_toc
// 安装
// 15ting_toc:将.md文件转化为带有目录的.html文件
npm install -g i5ting_toc
// 使用
// 使用命令进行转化
// -f xxx.md 指定要转化的md文件
// -o 在浏览器中打开
i5ting_toc -f 文件名.md -o
yarn基本使用
- 类比npm基本使用
- 解决了npm中出现的一些问题,如:npm update不好使
// 1. 安装
npm install -g yarn
// 2. 初始化包
npm init [-y] // -y:安静模式安装
yarn init
// 3. 安装包
npm install 包名 [--save]
yarn add 包名 // 自动加上--save
// 4. 移除包
npm uninstall 包名
yarn remove 包名
// 5. 更新包
npm update 包名
yarn upgrade 包名
// 6. 安装开发依赖的包
npm install 包名 --save-dev
yarn add 包名 --dev
// 7. 安装生产依赖的包
npm install 包名 --save
yarn add 包名
// 8. 全局安装
npm install -g 包名
yarn global add 包名
// 9. 设置下载镜像地址
npm config set registry 镜像地址
yarn config set registry 镜像地址
// 10. 执行包
npm run 具体的命令
yarn run 具体的命令
包
包的概念
Node.js 中的第三方模块又叫做包。
不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。
注意:Node.js 中的包都是免费且开源的,不需要付费即可免费下载使用。
由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。
包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。
包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 API 之间的关系。
全球最大的包共享平台: https://www.npmjs.com/
包的使用注意
- 1、必须通过npm来下载:npm install 包名
- 2、通过require('包名') 的方式来加载下载的包
- 3、不允许任何一个第三方包的名字与核心模块的名字一样(审核的时候会过滤)
- 4、一个项目中有且只有一个
node_modules
,放在项目根目录中
规范的包结构
一个规范的包,它的组成结构,必须符合以下 3 点要求:
- 1、包必须以单独的目录而存在
- 2、包的顶级目录下要必须包含 package.json 这个包管理配置文件
- 3、package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。
其他规范(不是必要的)
- 4、二进制文件应该在bin目录下
- 5、js代码应该在lib目录下
- 6、文档应该在doc目录下
- 7、单元测试应该在test目录下
自定义包
文件目录
mypac
bin/
lib/
doc/
text/
index.js
package.json
package.json
{
+ "name": "mypac",
+ "version": "1.0.0",
+ "main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"art-template": "^4.13.2",
"es-checker": "^1.4.2"
},
"devDependencies": {}
}
package.json
建议每个项目都要有一个 package.json
文件(包描述文件)
如果把 node_modules
删除了,可以通过 npm install
将保存在package.json中dependencies
的依赖包一次性安装完成
初始化 package.json
npm init [-y]
字段分析
- name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格
- description:包的简要说明
- version:符合语义化版本识别规范的版本字符串
- main:包的入口文件
- keywords:关键字数组,通常用于搜索
- maintainers:维护者数组,每个元素要包含name、email(可选)、web(可选)字段
- contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一- 个元素
- bugs:提交bug的地址,可以是网站或者电子邮件地址
- licenses:许可证数组,每个元素要包含type(许可证名称)和url(链接到许可证文本的- 地址)字段
- repositories:仓库托管地址数组,每个元素要包含type(仓库类型,如git)、url(仓- 库的地址)和path(相对于仓库的路径,可选)字段
- dependencies:生产环境包的依赖,一个关联数组,由包的名称和版本号组成
- devDependencies:开发环境包的依赖,一个关联数组,由包的名称和版本号组成
package-lock.json
- 1、
npm5
以前没有package-lock.json这个文件,npm5之后才加入了这个文件 - 2、npm5之后的版本在安装包的时候不需要再加
--save
参数,会自动保存到dependencies
中 - 3、当安装包的时候,npm都会自动生成或更新package-lock.json这个文件
- 4、
package-lock.json
这个文件保存了node_modules
中所有包的信息(版本、下载地址、依赖包),这样可以加快npm install
的速度 - 5、
package-lock.json
是用来锁定某个版本的,防止自动升级到新版本
模块的加载机制
模块查找机制,加载顺序:
- 优先从缓存加载
- 核心模块加载
- 路径形式的文件模块
- 第三方模块加载
优先从缓存中加载
模块在第一次加载后会被缓存。 这也意味着多次调用 require() 不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率
内置模块的加载机制
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。
例如,require('fs') 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs
自定义模块的加载机制
使用 require() 加载自定义模块时,必须指定以 ./
或 ../
开头的路径标识符
。在加载自定义模块时,如果没有指定 ./ 或 ../这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
- 1、按照确切的文件名进行加载
- 2、补全 .js 扩展名进行加载
- 3、补全 .json 扩展名进行加载
- 4、补全 .node 扩展名进行加载
- 5、加载失败,终端报错
第三方模块的加载机制
如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘../’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 'C:Usersitheimaprojectfoo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:
- 1、C:Usersitheimaproject node_modules ools
- 2、C:Usersitheima node_modules ools
- 3、C:Users node_modules ools
- 4、C: node_modules ools
目录作为模块
当把目录作为模块标识符,传递给 require() 进行加载的时候,有三种加载方式:
- 1、在被加载的目录下查找一个叫做
package.json
的文件,并寻找main
属性,作为 require() 加载的入口 - 2、如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的
index.js
文件。 - 3、如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:
Error: Cannot find module 'xxx'
模板引擎
-
模板引擎的底层原理:字符串的替换
-
在ECMAScript 6的
``
字符串中,可以使用${变量名}
来引用变量 -
art-template 模板引擎,不仅可以在前端使用,也可以在node中使用
-
模板引擎不关心字符串的内容,只关心它能认识的模板标记语法:{{}} / <%%>
安装
// 该命令在哪执行就会把包下载到哪里,默认位置:node_modules。最好不要改node_modules目录
npm install art-template
// 在web中需要引入的文件
node_modules/lib/tepmlate-web.js
art-template的API
-
template(filename, data) - 基于模板名渲染模板
参数
- filename - 模板文件的路径地址
- data - 替换对象键值
-
template.compile(source, options) - 将模板源代码编译成函数
参数
- source - 模板字符串
- options -
-
template.render(source, data, options) - 将模板源代码编译成函数并立刻执行
参数
- source - 模板字符串
- data - 替换对象键值
- options -
服务端渲染
-
在服务端(nodejs)使用模板引擎(art-template)
-
模板引擎一开始是诞生于服务器领域,后来才发展到前端
-
服务端渲染和客户端渲染的区别
-
客户端渲染不利于SEO(搜索引擎优化)
-
服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
-
真正的网站是由服务端渲染和客户端渲染结合实现的(商品列表:服务端渲染,用户评论:客户端渲染)
-
客户端渲染(2次请求)
-
服务端渲染(1次请求)
// npm安装 npm install art-template // 加载art-template var template = require('art-template'); // 使用模板引擎 var result = template.render('原始{{ name }}字符串', { name: 'Tom', age: 18 }); console.log( result );
-
// js文件
var http = require('http');
var fs = require('fs');
// 1. node中引入模板引擎的方法
var template = require('art-template');
var server = http.createServer();
server.on('request', function(req, res){
// 2. 读取模板文件template8.html
fs.readFile('template8.html', function(err, data){
if( err ) return res.end('file template8.html not found');
// 3. template.render(模板文件内容-字符串, 模板中需要替换的参数)
var newTemplate = template.render(data.toString(), {
name: 'Tom',
age: 17,
address: '四川',
salary: 800
});
// 4. 输出解析替换之后的模板内容
res.end(newTemplate);
});
});
server.listen('3000', function(){
console.log('server running...');
});
// 浏览器
我叫 Tom
今年 17
家住 四川
工资 800
案例:留言本
-
把当前模块所有的依赖项都声明在文件模块的最上面
-
为了让目录结构保持统一清晰,约定把所有的HTML文件都放在 views 文件夹内
-
为了方便统一处理网站中的静态资源,约定将所有的静态资源统一存放在 public 目录中
-
需要通过代码控制哪些资源能被用户访问,哪些资源不能被用户访问
-
注意:在服务端中,文件中的路径就不要写 相对路径 了,因为这个时候所有的资源都是通过 url 来获取的。
我们的服务器开放了 /public/目录,所以这里的请求路径就都要写成:/public/xxx 。/ 就标识url根路径
浏览器在真正发送请求的时候会自动把http://127.0.0.1:3000给拼接上
-
表单提交
-
表单中需要提交的表单控件元素必须具有 name 属性
-
表单提交分为:
默认的提交行为
表单异步提交
-
-
通过服务器让客户端重定向
-
状态码设置为:302(临时重定向)
response.statusCode = 302
-
在响应头中通过 Location 告诉客户端往哪重定向
response.setHeader('Location', '/')
-
客户端发现收到的服务器响应的状态码是302,就会自动去响应头中找 Location ,然后就能自动跳转了
-
-
一次请求对应一次响应,响应结束这次请求也就结束了
app.js
/* 入口js */
let fs = require('fs');
let url = require('url');
let http = require('http');
let template = require('art-template');
let comments = [
{ username: '张三', message: '今天星期二!', dateTime: '2021-01-26 16:41:47' },
{ username: '李四', message: '你吃了吗?', dateTime: '2021-01-26 16:42:47' },
{ username: '王五', message: '还没有吃啊!', dateTime: '2021-01-26 16:43:47' },
{ username: '刘七', message: '是么', dateTime: '2021-01-26 16:44:47' }
];
http.createServer((req, res)=>{
let oUrl = url.parse(req.url, true);
let urlPathName = oUrl.pathname;
let urlQuery = oUrl.query;
// 以下是显示页面
if( urlPathName === '/' ){
fs.readFile('./views/index.html', (err, data)=>{
if(err) return console.log( 'ERROR: index.html' );
// 动态渲染
let newTemplate = template.render(data.toString(), {
comments: comments
});
res.end(newTemplate);
});
}else if ( urlPathName === '/post' ){
fs.readFile('./views/post.html', (err, data)=>{
if(err) return console.log( 'ERROR: post.html' );
res.end(data);
});
}else if ( urlPathName.indexOf('/public/') === 0 ){
fs.readFile('.' + urlPathName, (err, data)=>{
if(err) return console.log( 'ERROR: ' + urlPathName );
res.end(data);
});
// 以下是处理前端传递的数据
}else if( urlPathName === '/post_submit' ){
let comment = {};
comment = urlQuery;
comment.dateTime = dateFormat(Date.now());
console.log(comment);
comments.unshift( comment );
// 页面跳转到首页
res.statusCode = 302;
res.setHeader('Location', '/');
res.end();
}else {
fs.readFile('./views/404.html', (err, data)=>{
if (err ) return console.log( '404 Not Found' );
res.end(data);
});
}
}).listen('3000',()=>{
console.log('server running...
');
});
// 转化时间格式为:2021-1-26 17:12:56
dateFormat = (timestamp) => {
let d = new Date(timestamp);
return d.getFullYear() + "-" + d.getMonth()+1 + "-" + d.getDate() + " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>留言本</title>
<link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h3>留言本</h3>
<a class="btn btn-primary" href="/post">发表评论</a>
<div class="list-group">
{{ each comments }}
<li class="list-group-item"><span>{{ $value.username }}:</span> {{ $value.message }} <em class="float-right">{{ $value.dateTime }}</em></li>
{{ /each }}
</div>
</div>
</body>
</html>
post.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发表评论</title>
<link rel="stylesheet" href="/public/lib/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h3>发表评论</h3>
<form action="/post_submit" method="get">
<div>
<label for="username">用户名:</label>
<input class="form-control" type="text" id="username" name="username" placeholder="请输入用户名" minlength="2" maxlength="20"><br>
</div>
<div>
<label for="message">评论内容:</label><br>
<textarea name="message" id="message" cols="155" rows="10" minlength="2"></textarea>
</div>
<div>
<input class="btn btn-danger" type="submit" value="发表评论">
</div>
</form>
</div>
</body>
</html>
404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404</title>
</head>
<body>
<h3>您要找的页面已经被狗子吃了!</h3>
</body>
</html>
Node.js操作数据库
Mysql环境准备
mysql第三方包基本使用
- 操作数据库基本步骤
- 实现增删改成基本操作
基于数据库实现登录功能
Express整合数据库实现增删改查业务
后台API开发
- 基于数据库的json接口开发
- 基于数据库的jsonp接口开发
图书管理系统整合数据库
后台接口开发
- json接口
- jsonp接口
- restful接口
基于Ajax与后台接口的前端渲染案例
- 前端渲染与后端渲染分析
从服务器主动发送请求
- 基于核心模块主动发送请求
调用第三方接口
- 针对第三方接口进行二次封装
- 扩展天气查询功能