初识Nodejs

将JavaScript运行在Nodejs中,使其可以用于后端开发

​ ——如果写了一段js在浏览器运行,那么是作为前端;如果写了一段js在Nodejs中运行,那么是作为后端开发。

Nodejs基于V8引擎解析和运行js代码。

Nodejs无法调用BOMDOM浏览器内置API。Nodejs提供一些基础的API类似浏览器内置的API。

fs模块

官方提供的,用于操作文件的模块。它提供了一系列方法和属性,用来满足用户对文件操作的需求。

模块使用

导入模块

1
const fs= require('fs')

方法和属性

读取文件

1
fs.readFile('1文件路径','编码格式',回调函数(失败后返回的结果,成功后返回的结果){执行的函数操作})
1
2
3
4
5
6
7
8
9
10
//1 导入
const fs=require('fs')

//2 调用
//fs.readFile('文件路径','编码格式',回调函数(失败后返回的结果,成功后返回的结果){函数要执行的操作})
fs.readFile('1.txt','utf-8',function(err,data){
console.log(err)
console.log('————我是分隔符————')
console.log(data)
})

写入数据

1
fs.writeFile('1文件路径','2写入的内容','(3可选参数)数据编码格式',4回调函数(失败后返回的结果){执行的函数操作})
1
2
3
4
5
6
7
8
const fs=require('fs')

fs.writeFile('2.txt','我是要写入的数据','utf-8',function(err){
console.log(err)
})
fs.writeFile('2.txt','我是要写入的数据',function(err){
console.log(err)
})

fs动态路径拼接问题

注意每次执行node命令时,会以执行命令所在目录作为路径,这会影响文件中使用相对路径的代码。

如下:

1
2
3
4
5
6
7
8
9
10
PS D:\Onedrive\NodejsWork\01 fs模块的使用> cd ../
PS D:\Onedrive\NodejsWork> node '.\01 fs模块的使用\01-readFile方法的使用.js'
[Error: ENOENT: no such file or directory, open 'D:\Onedrive\NodejsWork\1.txt'] {
errno: -4058,
code: 'ENOENT',
syscall: 'open',
path: 'D:\\Onedrive\\NodejsWork\\1.txt'
}
————我是分隔符————
undefined

解决方法:在代码中使用绝对路径

简易方法

使用__dirname变量,可以获取当前文件所在目录的路径。

path模块

官方提供的用于处理路径的模块。

模块使用

1
const path=requre('path')

方法和属性

连接路径

用于连接路径。该方法的主要用途在于,会正确使用当前系统的路径分隔符,Unix系统是”/“,Windows系统是”"。

1
path.join([path1][, path2][, ...]) 
1
2
path.join('/a/b/c','../','d')
//结果为:/a/b/d 注意: “ ../ ”会抵消一层路径

获取文件名

1
path.basename('路径'[, '需要被移除的后缀'])
1
2
3
4
const path=require('path')
const pth='/a/b/c.haha'
const name=path.basename(pth,'.haha')
console.log(name) //c

获取文件后缀名

1
path.extname('文件路径')

http模块

官方提供的用于创建web服务器的模块,通过模块提供的http.createServer()方法可以方便的创建一台web服务器,对外提供Web资源服务。

模块使用

1
const http=require('http')

Web服务器组成

  1. 引入 required 模块:我们可以使用 require 指令来载入 Node.js 模块。
  2. 创建服务器:服务器可以监听客户端的请求,类似于 Apache 、Nginx 等 HTTP 服务器。
  3. 接收请求与响应请求 服务器很容易创建,客户端可以使用浏览器或终端发送 HTTP 请求,服务器接收请求后返回响应数据。

创建第一个简单的web服务器

例1

1
2
3
4
5
6
7
8
9
10
11
12
//1 导入
const http = require('http');
//2 创建服务器实例
const server = http.createServer();
//3 为服务器绑定request事件
server.on('request',function(){
console.log('Someone visit our server!')
})
//4 启动服务器
server.listen(80,function(){
console.log('server running at http://127.0.0.1:80')
})

例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 引入 required 模块
var http = require('http');
// 创建服务器
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});

// 发送响应数据 "Hello World"
response.end('Hello World\n');
}).listen(8088);

// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8088/');

request请求对象

绑定的requset方法中的回调函数,可以使用一个请求对象参数

1
2
3
server.on('requset',function(req){
...
})

请求对象常用属性

1
2
3
4
5
req.headers: 返回的是一个对象, 对象中包揽了所有的请求报文头
req.rawHeaders: 返回的是一个数组, 数组中保存的都是请求报文头的字符串
req.httpVersion: 获取客户端请求所使用的HTTP版本
req.method: 获取客户端请求的方法
req.url: 获取客户端请求的路径

response响应对象

1
2
3
4
5
6
7
8
9
10
11
12
13
//1 导入
const http = require('http');
//2 创建服务器实例
const server = http.createServer();
//3 为服务器绑定request事件
server.on('request',function(req,res){
console.log('Someone visit our server!')
res.end()
})
//4 启动服务器
server.listen(80,function(){
console.log('Server running at http://127.0.0.1:80')
})

响应对象常用属性

1
2
3
4
5
6
7
8
9
10
11
12
res.write(): 发送数据
res.end(): 每个请求都必须要调用response.end(), 告诉服务器该响应的报文头, 报文体等等全部都已经响应完毕了, 可以结束本次响应如果要用response.end()来响应数据的话, 数据必须是String类型或者是Buffer类型
res.setHeader(): 设置响应报文头, 如果不设置, 系统也会有默认的响应报文头, 并且会默认发送给浏览器当已经发送过响应报文头后,res.setHeader()再次设置响应报文头, 否则会报错
res.statusCode: 设置HTTP响应状态码
res.statusMessage: 设置HTTP响应状态码对应的消息
res.writeHead(): 直接向客户端响应HTTP响应报文头

用法:
response.writeHead(响应状态码, 响应消息, {
'键': '值',
...
});

中文乱码问题

在使用res.end()方法响应中文内容时乱码,可以尝试设置响应头res.setHeader('Content-Type','text/html;charset=utf-8')

根据不同url响应不同网页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const http = require('http');
const fs = require('fs');
const path = require('path');

const server = http.createServer()
server.on('request', (req, res) => {
let url = req.url;
let content = '<p>404 not found</p>';
res.setHeader('Content-type', 'text/html;charset=utf-8');
if (url === '/') url = '/index.html'
const fpath = path.join(__dirname, './05-根据不同url响应不同网页', url)
fs.readFile(fpath, 'utf-8', (err, data) => {
if (err) {
res.end(content+"<br>"+err.message);
return console.log(err.message)
};
content = data;
res.end(content);
})
})
server.listen(80, () => {
console.log('Server is running at 127.0.0.1:80');
})

模块化

模块化是指在解决一个复杂问题时,自顶而下逐层将系统划分为若干模块的过程(化繁为简)。模块是可以组合、分解、更换的单元。

模块分类

内置模块

由Nodejs官方提供的,例如:fs、path、http等模块。

自定义模块

由用户创建的每个js文件,都是自定义模块。

第三方模块

非官方提供的模块。

模块的加载和创建方法

加载模块

1
2
3
4
// 1. 内置模块和第三方模块
const 常量名=require('直接使用模块名')
// 2. 用户自定义模块·
const 常量名=require('使用相对路径,文件名可以不写后缀')

创建模块

自定义模块需要使用module对象exports属性中挂载成员,外部才能使用模块的成员。

模块作用域

与函数作用域相似,在自定义模块中定义的变量、函数等,只能在模块内访问,这种模块级别的访问限制叫作模块作用域。

作用域的优点

防止了全局变量污染。

image-20230714193949406

全局变量污染

如上图所示,在两个js文件中都对username的值进行了定义,但最终输出在控制台的值为ls,这是因为第二个文件login.js在reg.js之后被引入,所以后来者覆盖了之前的值。

对外共享成员

module对象

存在于每个自定义模块中,它存储了当前模块的相关信息

1
console.log(module)
共享的方法

使用module对象的export属性共享模块中的成员

方法一 挂载属性和方法

1
2
3
4
5
module.exports.属性='属性值'

module.exports.方法=function(...){
...
}

方法二 直接指向一个新对象

1
2
3
4
5
module.exports={
属性:'属性值',
方法:function(...){...},
...
}

exports对象

与module.exports指向同一个对象。用于简化书写。

用法于module.exports相同。

建议不要混合使用。

使用注意

若将exports对象直接指向一个新对象,在使用require()导入时,则获取不到共享出去的成员了!

exports和module.exports的使用误区

虽然用法上相同,但是使用require()方法导入模块时,永远得到的是module.exports所指向的对象。

模块化规范 CommonJS

Nodejs遵循了CommonJS模块化规范,CommonJS中规定了模块的特性各模块之间如何依赖

CommonJS规定

  1. 每个模块内部,module变量代表当前模块。
  2. module变量是一个对象,它的exports属性是对外的接口。
  3. 加载某个模块,其实是加载模块的module.exports属性。require()方法用于加载模块。

npm和包

Nodejs中第三方模块又称为包。

包由第三方开发,免费使用。

Nodejs内置模块仅提供一些底层API,而包基于内置模块封装而来,提供更高级的API,极大的提高了开发效率。

使用npm工具下载包。

常用npm包

格式化时间moment

模块的加载机制

内置模块

优先从缓存中加载,不会反复执行require方法。

内置模块的优先级高于自定义模块。

自定义模块

匹配自定义模块时Node会依次按照确切文件名补全.js补全.json补全.node方式尝试导入。

第三方模块

首先查找当前目录下的node_modules\tools,其次是上一级目录,再是用户目录下(C:\User\node_modules\tools)、最后是node的安装目录下。

目录作为模块

  1. 查找package.json文件,寻找main属性值,作为required的入口
  2. 找不到package.json或没有main属性值或无法解析,则加载目录下的index.js文件
  3. 上面都失败了,则报错。

Node.js REPL(交互式解释器)

Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Windows 系统的终端或 Unix/Linux shell,我们可以在终端中输入命令,并接收系统的响应。

Node 自带了交互式解释器,可以执行以下任务:

  • 读取 - 读取用户输入,解析输入的 Javascript 数据结构并存储在内存中。
  • 执行 - 执行输入的数据结构
  • 打印 - 输出结果
  • 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。

Node.js 回调函数

Node.js 异步编程的直接体现就是回调。

异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了。

回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数。

回调函数一般作为函数的最后一个参数出现。

1
2
function foo1(name, age, callback) { }
function foo2(value, callback1, callback2) { }

阻塞代码

创建input文件,文件内容为Hello world!

创建zusai.js文件,代码为

1
2
3
4
5
6
var fs = require("fs");

var data = fs.readFileSync('input.txt');

console.log(data.toString());
console.log("程序执行结束!");

非阻塞代码

创建feizusai.js,代码为:

1
2
3
4
5
6
7
8
var fs = require("fs");

fs.readFile('input.txt', function (err, data) {
if (err) return console.error(err);
console.log(data.toString());
});

console.log("程序执行结束!");

两者效果相同,但效率不同。

前者需要先读取文件后执行输出程序,后者同时进行。

事件驱动程序

监听事件触发操作

创建listenEvents.js

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
// 引入 events 模块
var events = require('events');
// 创建 eventEmitter 对象
var eventEmitter = new events.EventEmitter();

// 创建事件处理程序
var connectHandler = function connected() {
console.log('连接成功。');

// 触发 data_received 事件
eventEmitter.emit('data_received');
}

// 绑定 connection 事件处理程序
eventEmitter.on('connection', connectHandler);

// 使用匿名函数绑定 data_received 事件
eventEmitter.on('data_received', function(){
console.log('数据接收成功。');
});

// 触发 connection 事件
eventEmitter.emit('connection');

console.log("程序执行完毕。");