Node.js

为什么选择Node?

  • 使用JavaScript语法开发后端应用
  • 一些公司要求前端工程师node开发
  • 生态系统活跃,有大量的开源库可以使用
  • 前端开发工具大多基于node开发

Node是什么?

Node是一个基于Chrome V8引擎的JavaScript代码运行环境。

运行环境。

  • 浏览器(软件)能够运行JavaScript代码,浏览器就是JavaScript代码的运行环境
  • Node(软件)能够运行JavaScript代码,Node就是JavaScript代码的运行环境

Node.js运行环境安装

官网:https://nodejs.org/en/
我是图片

  • LTS = Long Term Support 长期支持版 稳定版
  • Current 拥有最新特性 实验版

Node.js 的组成

  • JavaScript 由三部分组成,ECMAScript,DOM,BOM。
  • Node.js是由ECMAScript及Node 环境提供的一些附加API组成的,包括文件、网络、路径等等一些更加强大的 API。
    我是图片

Node.js基础语法

所有ECMAScript语法在Node环境中都可以使用。
在Node环境下执行代码,使用Node命令执行后缀为.js的文件即可
我是图片
我是图片

Node.js全局对象global

在浏览器中全局对象是window,在Node中全局对象是global。
Node中全局对象下有以下方法,可以在任何地方使用,global可以省略。

  • console.log() 在控制台中输出
  • setTimeout() 设置超时定时器
  • clearTimeout() 清除超时时定时器
  • setInterval() 设置间歇定时器
  • clearInterval() 清除间歇定时器

我是图片

模块化开发

JavaScript开发弊端

JavaScript在使用时存在两大问题,文件依赖和命名冲突。
我是图片

什么是模块化开发

一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行。

Node.js中模块化开发规范

Node.js规定一个JavaScript文件就是一个模块,模块内部定义的变量和函数默认情况下在外部无法得到
模块内部可以使用exports对象进行成员导出, 使用require方法导入其他模块。
我是图片

模块成员的导入与导出

a.js 导出

1
2
3
const add=(n1,n2)=>n1+n2;
//第一个add是导出的名字 第二个add是const声明的add
exports add=add;

b.js 导入

1
2
3
4
//require负责接收
const a= require('b.js');

console.log(a.add(10,20));

我是图片

注:require(‘b.js’);的后缀名可以省略

模块成员导出的另一种方式

1
2
module.exports.version = version;
module.exports.sayHi = sayHi;

exports是module.exports的别名(地址引用关系),当exports和module.exports对象指向的不是同一个对象时,导出对象最终以module.exports为准

系统模块

什么是系统模块

Node运行环境提供的API. 因为这些API都是以模块化的方式进行开发的, 所以我们又称Node运行环境提供的API为系统模块
我是图片

系统模块fs 文件操作

f:file 文件 ,s:system 系统,文件操作系统。

读取文件内容

1
2
3
const fs = require('fs');
//callback 是回调函数
fs.reaFile('文件路径/文件名称'[,'文件编码'], callback);
1
2
3
4
5
6
7
8
9
10
11
//读取上级css目录下的base.css
fs.reaFile('../css/base.css', 'utf-8', err => {
//如果文件读取发生错误 参数err的值为错误对象 否则err的值为null
//doc参数为文件内容
if (err = null) {
//在控制台输出文件内容
console.log(doc);

}

});

写入文件内容

1
2
3
4
5
6
7
8
9
10
fs.writeFile('文件路径/文件名称', '数据', callback);
const content = '<h3>正在使用fs.writeFile写入文件内容</h3>';
fs.writeFile('../index.html', content, err => {
if (err != null) {
console.log(err);
return;
}
console.log('文件写入成功');
});

系统模块path 路径操作

为什么要进行路径拼接

  • 不同操作系统的路径分隔符不统一
  • /public/uploads/avatar
  • Windows 上是 \ /
  • Linux 上是 /

    路径拼接语法

1
2
3
4
5
6
7
path.join('路径', '路径', ...)
// 导入path模块
const path = require('path');
// 路径拼接
let finialPath = path.join('itcast', 'a', 'b', 'c.css');
// 输出结果 itcast\a\b\c.css
console.log(finialPath);

相对路径VS绝对路径

  • 大多数情况下使用绝对路径,因为相对路径有时候相对的是命令行工具的当前工作目录
  • 在读取文件或者设置文件路径时都会选择绝对路径
  • 使用__dirname获取当前文件所在的绝对路径

第三方模块

什么是第三方模块

别人写好的、具有特定功能的、我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所以又名包。

第三方模块有两种存在形式:

  • 以js文件的形式存在,提供实现项目具体功能的API接口。
  • 以命令行工具形式存在,辅助项目开发

获取第三方模块

我是图片
npmjs.com:第三方模块的存储和分发仓库

npm (node package manager) : node的第三方模块管理工具

  • 下载:npm install 模块名称
  • 卸载:npm unintall package 模块名称

全局安装与本地安装

  • 命令行工具:全局安装
  • 库文件:本地安装

第三方模块 nodemon

nodemon是一个命令行工具,用以辅助项目开发。
在Node.js中,每次修改文件都要在命令行工具中重新执行该文件,非常繁琐。

使用步骤

  • 使用npm install nodemon –g 下载它
  • 在命令行工具中用nodemon命令替代node命令执行文件

我是图片

第三方模块 nrm

nrm ( npm registry manager ):npm下载地址切换工具

npm默认的下载地址在国外,国内下载速度慢

使用步骤

  • 使用npm install nrm –g 下载它
  • 查询可用下载地址列表 nrm ls
  • 切换npm下载地址 nrm use 下载地址名称
    我是图片

node_modules文件夹的问题

  • 文件夹以及文件过多过碎,当我们将项目整体拷贝给别人的时候,,传输速度会很慢很慢.
  • 复杂的模块依赖关系需要被记录,确保模块的版本和当前保持一致,否则会导致当前项目运行报错

package.json文件的作用

项目描述文件,记录了当前项目信息
例如项目名称、版本、作者、github地址、当前项目依赖了哪些第三方模块等。
使用npm init -y命令生成。

项目依赖

1
2
3
4
5
6
{
"dependencies": {
"jquery": "^3.3.1“
}
}

  • 在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖
  • 使用npm install 包名命令下载的文件会默认被添加到 package.json 文件的 dependencies 字段中

开发依赖

1
2
3
4
5
6
{
"devDependencies": {
"gulp": "^3.9.1“
}
}

  • 在项目的开发阶段需要依赖,线上运营阶段不需要依赖的第三方包,称为开发依赖
  • 使用npm install 包名 –save-dev命令将包添加到package.json文件的devDependencies字段中

package-lock.json文件的作用

  • 锁定包的版本,确保再次下载时不会因为包版本不同而产生问题
  • 加快下载速度,因为该文件中已经记录了项目所依赖第三方包的树状结构和包的下载地址,重新安装时只需下载即可,不需要做额外的工作

模块查找规则-当模块拥有路径但没有后缀时

1
2
require('./find.js');
require('./find');
  • require方法根据模块路径查找模块,如果是完整路径,直接引入模块。
  • 如果模块后缀省略,先找同名JS文件再找同名JS文件夹
  • 如果找到了同名文件夹,找文件夹中的index.js
  • 如果文件夹中没有index.js就会去当前文件夹中的package.json文件中查找main选项中的入口文件
  • 如果找指定的入口文件不存在或者没有指定入口文件就会报错,模块没有被找到

模块查找规则-当模块没有路径且没有后缀时

1
require('./find');
  • Node.js会假设它是系统模块
  • Node.js会去node_modules文件夹中
  • 首先看是否有该名字的JS文件
  • 再看是否有该名字的文件夹
  • 如果是文件夹看里面是否有index.js
  • 如果没有index.js查看该文件夹中的package.json中的main选项确定模块入口文件
  • 否则找不到报错

创建服务器

访问 localhost:3000
执行命令 nodemon app.js

1
2
3
4
5
6
7
8
9
10
// 用于创建网站服务器的模块
const http = require('http');
// app对象就是网站服务器对象
const app = http.createServer();
// 当客户端有请求来的时候
app.on('request', (req, res) => {
res.end('<h2>欢迎来到首页</h2>');
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

Http协议

概念:超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)
规定了如何从网站服务器传输超文本到本地浏览器,它基于客户端服务器架构工作,是客户端(用户)和服务器端(网站)请求和应答的标准。

报文

在HTTP请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,并且要遵守规定好的格式。

请求报文

1.请求方式 (Request Method)

  • GET 请求数据
  • POST 发送数据

method 里面是什么请求方式 请求之后就返回请求方式

1
2
3
4
5
6
7
8
9
10
11
<body>
<!--
method: 指定当前表单提交的方式
action: 指定当前表单提交的地址
-->
<form method="post" action="http://localhost:3000">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit">
</form>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用于创建网站服务器的模块
const http = require('http');
// app对象就是网站服务器对象
const app = http.createServer();
// 当客户端有请求来的时候
app.on('request', (req, res) => {
if (req.method == 'POST') {
res.end('post')
} else if (req.method == 'GET') {
res.end('get')
}

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

2.请求地址 (Request URL

1
2
3
4
5
6
app.on('request', (req, res) => {
req.headers // 获取请求报文
req.url // 获取请求地址
req.method // 获取请求方法
});


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 用于创建网站服务器的模块
const http = require('http');
// app对象就是网站服务器对象
const app = http.createServer();
// 解决乱码
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
});
// 当客户端有请求来的时候
if (req.url=='/index'||req.url=='/') {
// 如果用户访问的是首页 返回
res.end('<h2>欢迎来到首页</h2>');
}else if (pathname == '/list') {
// 如果用户访问的是list 返回
res.end('welcome to listpage');
}else {
// 如果用户访问的是其他 返回
res.end('not found');
}
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

响应报文

  • 200 请求成功
  • 404 请求的资源没有被找到
  • 500 服务器端错误
  • 400 客户端请求有语法错误
1
2
3
4
5
app.on('request', (req, res) => {
// 设置响应报文
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf8‘
});
});

内容类型

  • text/html
  • text/css
  • application/javascript
  • image/jpeg
  • application/json

请求参数

客户端向服务器端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务器端,比如登录操作。

GET请求参数

  • 参数被放置在浏览器地址栏中,例如:http://localhost:3000/?name=zhangsan&age=20
  • 参数获取需要借助系统模块url,url模块用来处理url地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 用于处理url地址
    const url = require('url');
    console.log(req.url);
    // 1) 要解析的url地址
    // 2) 将查询参数解析成对象形式
    let { query, pathname } = url.parse(req.url, true);
    console.log(query.name)
    console.log(query.age)
    if (pathname == '/index' || pathname == '/') {
    res.end('<h2>欢迎来到首页</h2>');
    }else if (pathname == '/list') {
    res.end('welcome to listpage');
    }else {
    res.end('not found');
    }
    parse 方法可以处理url地址 可以把url处理成一个对象形式 在对象里保存了url里的各个部分 查询参数依旧是字符串类型的
    加第二个参数true转换成对象类型
    我们就可返的对象.上name和age去就可以获取到参数的值
    query 查询参数

POST请求参数

参数被放置在请求体中进行传输
获取POST参数需要使用data事件和end事件
使用querystring系统模块将参数转换为对象格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 导入系统模块querystring 用于将HTTP参数转换为对象格式
const querystring = require('querystring');

app.on('request', (req, res) => {
// 声明一个变量 接受post参数
let postData = '';

// 监听参数传输事件 当触发date的时候和postData进行拼接 chunk就是传递过来值的参数
req.on('data', (chunk) => postData += chunk;);
// 监听参数传输完毕事件会出发end 我们用querystring.parse做了格式的处理 最后吧结果postData输出
req.on('end', () => {
console.log(querystring.parse(postData));
});
});

路由

路由是指客户端请求地址与服务器端程序代码的对应关系。简单的说,就是请求什么响应什么。

1
2
3
4
5
6
7
8
9
10
11
12
// 当客户端发来请求的时候
app.on('request', (req, res) => {
// 获取客户端的请求路径
let { pathname } = url.parse(req.url);
if (pathname == '/' || pathname == '/index') {
res.end('欢迎来到首页');
} else if (pathname == '/list') {
res.end('欢迎来到列表页页');
} else {
res.end('抱歉, 您访问的页面出游了');
}
});

静态资源

服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如CSS、JavaScript、image文件
http://www.itcast.cn/images/logo.png

动态资源

相同的请求地址不同的响应资源,这种资源就是动态资源
http://www.itcast.cn/article?id=1
http://www.itcast.cn/article?id=2

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
const http = require('http');
const url = require('url');
const path = require('path');
const fs = require('fs');
const mime = require('mime');

const app = http.createServer();

app.on('request', (req, res) => {
// 获取用户的请求路径
let pathname = url.parse(req.url).pathname;

pathname = pathname == '/' ? '/default.html' : pathname;

// 将用户的请求路径转换为实际的服务器硬盘路径
let realPath = path.join(__dirname, 'public' + pathname);

let type = mime.getType(realPath)

// 读取文件
fs.readFile(realPath, (error, result) => {
// 如果文件读取失败
if (error != null) {
res.writeHead(404, {
'content-type': 'text/html;charset=utf8'
})
res.end('文件读取失败');
return;
}

res.writeHead(200, {
'content-type': type
})

res.end(result);
});
});

app.listen(3000);
console.log('服务器启动成功')

客户端请求途径

GET方式

  • 浏览器地址栏
  • link标签的href属性
  • script标签的src属性
  • img标签的src属性
  • Form表单提交

POST方式

  • Form表单提交

Node.js异步编程

同步API, 异步API

同步API:只有当前API执行完成后,才能继续执行下一个API

1
2
console.log('before'); 
console.log('after');

异步API:当前API的执行不会阻塞后续代码的执行

1
2
3
4
5
console.log('before');
setTimeout(
() => { console.log('last');
}, 2000);
console.log('after');

回调函数

1
2
3
4
5
6
7
8
9
function getData (callback) {
callback('123')
}

getData(function (n) {
console.log('callback函数被调用了')
console.log(n)
});

1
2
3
4
5
6
7
8
9
10
11
12
function getMsg (callback) {
setTimeout(function () {
callback({
msg: 'hello node.js'
})
}, 2000)
}

getMsg(function (data) {
console.log(data);
});

同步API, 异步API的区别(代码执行顺序

同步API从上到下依次执行,前面代码会阻塞后面代码的执行

1
2
3
4
for (var i = 0; i < 100000; i++) { 
console.log(i);
}
console.log('for循环后面的代码');

异步API不会等待API执行完成后再向下执行代码

1
2
3
4
console.log('代码开始执行'); 
setTimeout(() => { console.log('2秒后执行的代码')}, 2000);
setTimeout(() => { console.log('"0秒"后执行的代码')}, 0);
console.log('代码结束执行');

Node.js中的异步API

依次读取A文件、B文件、C文件

回调嵌套回调 俗称回调地狱

1
2
3
4
5
6
7
8
9
10
11
const fs = require('fs');

fs.readFile('./1.txt', 'utf8', (err, result1) => {
console.log(result1)
fs.readFile('./2.txt', 'utf8', (err, result2) => {
console.log(result2)
fs.readFile('./3.txt', 'utf8', (err, result3) => {
console.log(result3)
})
})
});

Promise

Promise出现的目的是解决Node.js异步编程中回调地狱的问题。

使用Promise依次读取A文件、B文件、C文件

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
function p1 () {
return new Promise ((resolve, reject) => {
fs.readFile('./1.txt', 'utf8', (err, result) => {
resolve(result)
})
});
}

function p2 () {
return new Promise ((resolve, reject) => {
fs.readFile('./2.txt', 'utf8', (err, result) => {
resolve(result)
})
});
}

function p3 () {
return new Promise ((resolve, reject) => {
fs.readFile('./3.txt', 'utf8', (err, result) => {
resolve(result)
})
});
}

p1().then((r1)=> {
console.log(r1);
return p2();
})
.then((r2)=> {
console.log(r2);
return p3();
})
.then((r3) => {
console.log(r3)
})

异步函数

异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了

1
2
const fn = async () => {};
async function fn () {}

async关键字

  1. 普通函数定义前加async关键字 普通函数变成异步函数
  2. 异步函数默认返回promise对象
  3. 在异步函数内部使用return关键字进行结果返回 结果会被包裹的promise对象中 return关键字代替了resolve方法
  4. 在异步函数内部使用throw关键字抛出程序异常
  5. 调用异步函数再链式调用then方法获取异步函数执行结果
  6. 调用异步函数再链式调用catch方法获取异步函数执行的错误信息

await关键字

  1. await关键字只能出现在异步函数中
  2. await promise await后面只能写promise对象 写其他类型的API是不不可以的
  3. await关键字可是暂停异步函数向下执行 直到promise返回结果

使用异步函数解决依次读取A文件、B文件、C文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async function p1 () {
return 'p1';
}

async function p2 () {
return 'p2';
}

async function p3 () {
return 'p3';
}

async function run () {
let r1 = await p1()
let r2 = await p2()
let r3 = await p3()
console.log(r1)
console.log(r2)
console.log(r3)
}

run();

异步函数在node.js中的使用

在node.js中的使用使用异步函数解决依次读取A文件、B文件、C文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const fs = require('fs');
// 改造现有异步函数api 让其返回promise对象 从而支持异步函数语法
const promisify = require('util').promisify;
// 调用promisify方法改造现有异步API 让其返回promise对象
const readFile = promisify(fs.readFile);

async function run () {
let r1 = await readFile('./1.txt', 'utf8')
let r2 = await readFile('./2.txt', 'utf8')
let r3 = await readFile('./3.txt', 'utf8')
console.log(r1)
console.log(r2)
console.log(r3)
}

run();