Node.js Basics (1)

在Node.js环境中执行JavaScript代码: 在命令行中输入: node js文件

  • fs: Node.js中提供的用于操作文件的模块

fs.readFile(): 读取指定文件中的内容

fs.writeFile(): 向指定的文件中写入内容

导入fs模块: const fs = require(’fs’)

  • fs.readFile()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const fs = require('fs')
fs.readFile("./test.txt", "utf8", function(err, dataStr){
console.log(err)
console.log("-----")
console.log(dataStr)
})
// function为回调函数, err为读取失败的结果(若读取成功则为null),
// dataStr为读取成功的结果(若读取成功则为undefined)

const fs = require('fs')
fs.readFile("./test.txt", "utf8", function(err, dataStr){
if(err){
return console.log("读取失败" + err.message)
}
console.log("读取成功" + dataStr)
})
  • fs.writeFile()
1
2
3
4
5
6
7
8
const fs = require('fs')
fs.writeFile("./test.txt", "hello world", function(err){
// 若成功写入, err为null
if(err){
return console.log("写入失败" + err.message)
}
console.log("写入成功")
})
  • path: Node.js提供的用于处理路径的模块

path.join(): 将多个路径片段拼接成一个完整的路径字符串

path.basename(): 将文件名从路径字符串中解析出来

1
2
3
4
5
6
7
8
const fs = require('path')
// "./test.txt" -> path.join(__dirname, "test.txt")

const fpath = "/a/b/c/index.html"
var fullname = path.basename(fpath)
console.log(fullname) // index.html
var nameWithoutExt = path.basename(fpath, ".html")
console.log(nameWithoutExt) // index
  • http: Node.js提供的用来创建web服务器的模块

通过http.createServer()可以把一台普通的电脑变成一台web服务器

服务器与普通电脑的区别: 服务器上安装了web服务器软件(IIS, Apache等)

将本机当作服务器进行访问: 127.0.0.1 (对应的域名为localhost)

url在的80端口可以被省略

  • 创建基本的web服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 导入http模块
const http = require("http")
// 创建web服务器实例
const server = http.createServer()
// 为服务器实例绑定request事件, 监听客户端的请求
server.on("request", function(req, res){ // 当服务器被访问时触发
console.log("Someone visits our web server.")
})
// 启动服务器
server.listen(8080, function(){ // 指定服务器运行的端口
console.log("server is running at <http://127.0.0.1:8080>")
})
// 通过运行 node js文件 来启动

server.on("request", (req) => {
// req是请求对象, 包含了与客户端相关的数据与属性, 如:
// req.url: 客户端请求的url地址
// req.method: 客户端的method的请求类型
const str = "Your request url is ${req.url}, and request method is ${req.method}"
console.log(str)
})

server.on("request", (req, res) => {
// res是响应对象, 包含了与服务器相关的数据与属性, 如:
const str = "Your request url is ${req.url}, and request method is ${req.method}"
res.end(str) // 向客户端发送指定的内容, 并结束这次请求的处理过程
})

// 解决中文乱码的问题
server.on("request", (req, res) => {
const str = "您请求的url地址是${req.url}, 请求的method类型为${req.method}"
res.setHeader("Content-Type", "text/html; charset=utf-8")
res.end(str)
})

// 根据不同的url响应不同的html内容
server.on("request", (req, res) => {
const url = req.url
let content = '<h1>404 Not found!</h1>'
if(url === "/" || url === "/index/html"){
content = "<h1>首页</h1>"
}else if(url === "/about.html"){
content = "<h1>关于页面</h1>"
}
res.setHeader("Content-Type", "text/html; charset=utf-8")
res.end(content)
})

// 加载用户自定义模块
const custom = require("./custom.js")
// 使用require方法加载其它模块时, 会执行被加载模块中的代码

模块作用域: 可避免全局变量污染的问题

module: 在每个.js自定义模块中都有一个module对象, 存储了与当前模块有关的信息

外界用require()方法导入自定义模块时, 得到的就是module.exports所指向的对象

在一个自定义模块中, 默认情况下, module.exports = {}

1
2
3
4
5
6
7
// 在module.exports对象上挂载属性/方法
module.exports.username = "zhangsan"
module.exports.sayHello = function(){
console.log("Hello!")
}
const age = 20
module.exports.age = age

exports: 默认情况下, exports和module.exports指向的是同一个对象(代码简化)

包共享平台:

https://www.npmjs.com(搜索)

https://registry.npmjs.org(下载)

包管理工具:

Node Package Manager(npm包管理工具), 随Node.js的安装一并安装

用npm安装包: npm install 包完整名称 / npm i 包完整名称

初次装包完成后, 项目文件夹下会出现node_modules文件夹和package-lock.json配置文件; node_modules文件夹用于存放所有已安装到项目中的包; package-lock.json配置文件用于记录node_modules目录下每一个包的下载信息(包名, 版本号, 下载地址等)

1
2
3
npm i moment // 自动安装版本最新的包
npm i moment@2.22.2 // 安装指定版本的包
// 大版本.功能版本.Bug修复版本

包管理配置文件(package.json): 位于项目根目录, 记录与项目有关的一些配置信息, 从而方便在剔除node_modules目录之后在团队成员之间共享项目源代码

注: 在项目开发中, 把node_modules文件夹添加到.gitignore忽略文件中

1
2
3
4
5
6
7
8
9
// 在执行命令所处的目录中, 快速新建package.json文件
npm init -y // 项目文件夹不能出现中文和空格
// 使用npm命令安装包时, npm包管理工具会自动将包名称与版本号记录到package.json文件中

npm install // (或 npm i)
// 包管理工具会读取package.json中的dependencies节点, 并根据读取到的依赖名称和版本号
// 一次性安装这些包

npm uninstall moment // 卸载指定的moment包

对于只在项目开发阶段用到、项目上线之后不会用到的包, 应该把这些包记录到devDependencies中

1
npm i monment -D // 或 npm install moment --save-dev
  • 模块的加载机制

模块在第一次加载后会被缓存, 然后优先从缓存中加载

内置模块(Node.js官方提供的模块)的加载优先级最高

加载自定义模块时, 必须指定以./或../开头的路径标识符

第三方模块从node_modules文件夹中进行加载, 如果没有找到对应的第三方模块, 则移动到再上一层父目录中进行加载, 直到文件系统的根目录

  • 目录作为模块的加载机制

在被加载的目录中查找package.json文件, 并寻找main属性, 作为require加载的入口

若没有package.json文件或main入口不存在或无法解析, 则尝试加载目录下的index.js文件; 若都不存在, 则在终端打印错误信息

  • Express: 基于http内置模块进一步封装出来的, 效率更高, 可以方便、快速地创建web网站服务器或API接口服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
npm i express@4.17.2
// 导入express
const express = require("express")
// 创建web服务器
const app = express()
// 调用app.listen(端口号, 启动成功后的回调函数), 启动服务器
app.listen(80, () => {
console.log("express server is running at <http://127.0.0.1>")
})

// 通过app.get()来监听客户端GET请求
app.get("请求的url地址", function(req, res) {/*处理函数*/})
// req为请求对象, res为响应对象, 都包含了相应的属性和方法

// 通过app.post()来监听客户端的POST请求
app.post("请求的url地址", function(req, res) {/*处理函数*/})

// 将内容响应给客户端
app.get("/user", function(req, res) {
res.send({name: "zhangsan", age: 20})
})

app.post("/user", function(req, res) {
res.send("请求成功")
})

// 通过req.query(默认情况下为空对象)获取客户端发送的查询参数
app.get("/", function(req, res) {
console.log(req.query)
res.send(req.query)
})
// 直接在网址处传入查询参数
// <http://127.0.0.1/?name=zhangsan&age=20>

// 获取url中的动态参数
// 通过req.params对象, 可以访问到url中通过:匹配到的动态参数
app.get("/user/:id/:name", function(req, res) {
console.log(req.params)
res.send(req.params)
})
// 请求: <http://127.0.0.1/user/2/zhangsan>
// 返回: {
// "id": "2"
// "name": "zhangsan"
// }

// express.static(): 创建一个静态资源服务器
app.use(express.static("public")) // 将public目录下的文件对外开放访问
// Express在指定的静态目录在查找文件, 并对外提供提供资源的访问路径,
// 存放静态文件的目录名不会出现在url中
// e.g <http://localhost:3000/image/bg.jpg>

// 托管多个静态资源目录, 根据目录的添加顺序进行查找
app.use(express.static("public"))
app.use(express.static("files"))

// 挂载路径前缀
app.use("public", express.static("public"))
// e.g <http://localhost:3000/public/image/bg.jpg>
  • nodemon: 监听项目文件的变动, 当代码被修改后会自动重启项目, 不需要手动close
1
2
npm install -g nodemon
// 启动时将node app.js替换为nodemon app.js即可
  • Express中的路由: 客户端请求与服务器处理函数之间的映射关系

路由有3部分组成: 请求的类型, 请求的url地址, 处理函数; 若请求的类型、请求的url地址能够匹配成功, 则Express会将这次请求交给对应的处理函数来处理

  • 模块化路由: 将路由抽离为单独的模块, 而不是直接挂载到app上

创建路由模块对应的.js文件; 调用express.Router()函数创建路由对象;

向路由对象上挂载具体的路由; 使用module.exports向外共享路由对象;

使用app.use()函数注册路由模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
var express = require("express")
var router = express.Router()

router.get("/user/list", function(req, res){
res.send("Get user list.")
})

router.post("/user/list", function(req, res){
res.send("Add new user.")
})

module.exports = router // 将创建的路由模块导出
----------------------------------------------
const express = require("express")
const app = express()
const router = require("./myrouter.js") // 导入路由模块
app.use(router) // 注册路由模块
// app.use()的作用为注册全局中间件
// 添加访问前缀/api: app.use("/api", router)
app.listen(80, () => {
console.log("<http://127.0.0.1>")
})

// 中间件处理函数: function(req, res, next); 路由处理函数: function(req, res)
// next函数是实现多个中间件连续调用的关键, 表示把流转关系转交给下一个中间件或路由
// 即先经过合适的中间件, 再经过路由
const express = require("express")
const app = express()
const mw = function(req, res, next){
console.log("最简单的中间件函数")
next()
}
// 将mw注册为全局生效的中间件(即客户端任何请求到达服务器都会触发的中间件)
// app.use(mw)
// 也可简化定义为
// app.use(function(req, res, next){
// console.log("最简单的中间件函数")
// next()
// })
app.listen(80, () => {
console.log("<http://127.0.0.1>")
})
// 多个中间件之间共享同一份req和res, 都可以挂载自定义的属性和方法

// 定义多个全局中间件
app.use(function(req, res, next){
console.log("调用了第1个全局中间件")
next()
})
app.use(function(req, res, next){
console.log("调用了第2个全局中间件")
next()
})

// 使用局部中间件
app.get("/", mw, function(req, res){
res.send("Home page.")
})
// 使用多个局部中间件
app.get("/", mw1, mw2, (req, res) => {res.send("Home page.")})
app.get("/", [mw1, mw2], (req, res) => {res.send("Home page.")})
// 注: 中间件需要注册在路由前面
// 错误级别的中间件 (这种中间级需要注册在路由后面)
app.get("/", function(req, res){
throw new Error("发生错误!")
res.send("Home page.")
})

app.use(function(err, req, res, next){
console.log("发生错误:" + err.message)
res.send("error" + err.message)
})

// Express内置的中间件
express.static: 快速托管静态资源的内置中间件
express.json: 解析JSON格式的请求体数据
express.urlencoded: 解析URL-encoded格式的请求体数据
app.use(express.json())
app.use(express.urlencoded({extended: false}))
app.post("/user", (req, res) => {
// 在服务器中可以用req.body这个属性来接收客户端发送的请求体数据
// 若不配置解析表单数据的中间件, 则req.body默认等于undefined
console.log(req.body)
res.send("ok")
})

// 第三方中间件
npm install body-parser
const parser = require("body-parser")
app.use(parser.urlencoded({extended: false}))
app.post("/user", (req, res) => {
console.log(req.body)
res.send("ok")
})

// 自定义解析表单的中间件
const qs = require("querystring")
app.use((req, res, next) => {
let str = ""
// 监听req的data事件, 来获取客户端发送给服务器的数据
req.on("data", (chunk) => {
str += chunk
})
// 监听req的end事件(当请求体数据接收完毕后自动触发)
req.on("end", () => {
console.log(str)
const body = qs.parse(str)
console.log(body)
// req.body = body
// next()
})
})
--------------------------------------------------
const bodyParser = (req, res, next) => {
let str = ""
// 监听req的data事件, 来获取客户端发送给服务器的数据
req.on("data", (chunk) => {
str += chunk
})
// 监听req的end事件(当请求体数据接收完毕后自动触发)
req.on("end", () => {
console.log(str)
const body = qs.parse(str)
console.log(body)
// req.body = body
// next()
})
}
module.exports = bodyParser
  • cors: Express的一个第三方中间件, 可以解决跨域问题(协议或域名或端口号不同)

只需在服务器端配置, 客户端无需做任何额外配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
npm install cors
const cors = require("cors") // 一定要在路由之前进行配置
app.use(cors())

...
<body>
<button id="btnGET">GET</button>
<button id="btnPOST">POST</button>
<button id="btnJSONP">JSONP</button>
<script>
$(function(){
$("#btnGET").on("click", function(){
$.ajax({
type: "GET",
url: "<http://127.0.0.1/api/get>",
data: {name: "zhangsan", age: 20},
success: function(res){
console.log(res)
},
})
})

$("#btnPOST").on("click", function(){
$.ajax({
type: "POST",
url: "<http://127.0.0.1/api/post>",
data: {bookname: "test1", author: "test2"},
success: function(res){
console.log(res)
},
})
})

$("#btnJSONP").on("click", function(){
$.ajax({
type: "GET",
url: "<http://127.0.0.1/api/jsonp>",
dataType: "jsonp",
success: function(res){
console.log(res)
},
})
})
})
</script>
</body>
...

res.setHeader("Access-Control-Allow-Origin", "<http://itcast.cn>") // 只允许来自http://itcast.cn的跨域请求
res.setHeader("Access-Control-Allow-Origin", "*") // 允许来自任何域的请求

// 默认情况下, CORS仅支持客户端发起GET, POST, HEAD请求
// 若要使用PUT, DELETE等请求, 则需要进行声明
res.setHeader("Access-Control-Allow-Methods", "GET", "POST", "HEAD", "PUT", "DELETE")
res.setHeader("Access-Control-Allow-Methods", "*")