前言

  1. 关于Node.js

    我们在上一章节最后总结的时候提到,如果我们想往服务器提交表单数据,由于我们没有后端程序员给你接口,那么只有自己写一个接口,作为我们前端开发,存在一门和后端语言:PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言,就是 node.js

  2. Node.js介绍 [node.js百度百科]

    概述:

    ①、 Node.js 是一个开源、跨平台的 JavaScript 运行时环境。

    作用、应用、优势:

    ① Node.js是脱离浏览器运行JS:我们知道我们之前的课程不管是原生js还是我们刚刚学习的jQuery,都是在浏览器运行的js代码,脱离了浏览器,我们的js就没办法运行了。而我们的node.js可以运行在别的终端上,那么它就可以帮你调用你想要的东西,比如:你想获取一下你电脑现在的CPU配置,内存配置,往电脑磁盘上写入一个如json文件,修改json文件等等,这些功能以前是后端程序做的事情,现在node.js也可以做。

    ② 后台API编写:就是我们说的接口的编写,我们前端的工作大部分不是写页面,就是调接口了。在你不懂的情况下, 还以为接口多神奇,学完node之后,我们也可以写接口。

    ③ Webpack,Gulp,Npm等等,这些工具是依赖node的,也就是前端工程化的这些工具,没有node玩不了。

    ④ (重要应用)中间层:服务器中复杂IO读写的中间层服务器,你可以粗俗的认为一个文件的读写、数据库的查询,这些都是由中间层node来做好一些。

    ⑤ 优势:1、便于前端开发入门,因为node的语法和对象,和我们写js基本上一样,你有了前面的js基础,写node代码非常容易。2、单纯比性能,node的性能比java、php等等语言高出非常多。每年双11,天猫淘宝能抗住一波又一波海量数据请求,node起了非常大的作用。主要是它运行在Chrome V8 JavaScript engine这么一个引擎上面,这个引擎非常强大。3、由于node的写法和前端js非常像,所以相比于其它语言,node更利于前端代码的整合,前端写代码,有的可以直接给node用,比如说表单校验等等。

# 一、Node环境搭建(安装node.js)

# 1、 下载安装node.js

安装非常简单,在官网上下载 node-v-xx.msi(window系统)傻瓜式的安装包,一直下一步就可以完成安装。 nodejs中文官网 nodejs英文官网

LTS 长期支持版建议下载这个,Current 尝鲜版版本较高,还未普及,尝鲜使用新功能可以试试

下载node 选择自己的操作系统 windows Mac Linux windows需要区分64位和32位 Mac需要区分64位还是ARM芯片 Linux同上。 其中msi 和 pkg 可以直接安装较为简单
如果不想下载的同学,可以去群里面下载本节课的课件,里面有安装包

# 2、 检查node.js是否安装成功

# ① 命令行:node -v npm -v npx -v

window系统:快捷键window键 + R键(开始-->运行) 输入:cmd 打开命令行工具,苹果系统也是找到你电脑上的命令行工具

node -v
// v18.12.1 (不同的同学,不同的时期下载的node版本号不一样,重点看一下能不能输出)
npm -v
npx -v

# ② 命令行:node 运行js代码

可以通过 node 回车,在命令行运行js代码

node
// 此时光标换行在闪,就是你可以输入js代码
let a = 1 (回车)
a + 10  (返回11 回车)

# ③ 命令行:运行js文件代码,清屏命令: cls

在D盘创建一个文件夹:mynode(名字随意取最好是英文便于输入),D:\mynode\a.js文件,写入 console.log("迪丽热巴");

运行a.js文件方式一:(打开命令行工具,因为当前的命令行工具在运行js代码,需要重新打开新的命令行工具)

//进入D盘(因为文件在D盘)
D: (注意冒号英文状态 回车)
//进入mynode文件夹
cd mynode (进入文件夹cd空格文件夹名字  回车)
//运行a.js文件
node a.js  (或者)
node a

运行a.js文件方式二:(感觉方式一很麻烦)
直接在a.js文件夹里面,先按住键盘SHIFT(shift)键不要松手然后点鼠标右键,鼠标左键点击:在此处打开Powershell 窗口,就可以跳过方式一里面的找文件路径的操作了

//运行a.js文件
node a.js  (或者)
node a

# 二、NVM(node版本管理工具,切换node版本)

在我们实际开发中,公司或者个人可能有多个项目,由于开发时期的不同,导致使用的node版本也不同,于是我们在运行项目的时候,需要切换到不同的node版本运行不同的项目,这个时候就需要一个管理node版本的工具:NVM。比如说:就以我们本节课而言,不同的同学学习的时候,由于nodejs的更新,你下载的nodejs版本都不同,不利于我们统一学习调试,所以我们也需要安装一下NVM,统一切换到某一个版本来学习。

# 1、 下载安装nvm

① 访问NVM的github仓库:下载github仓库中的nvm ② 去群里面下载本节课的课件,里面有安装包
安装非常简单,跟上面安装nodejs一样,下一步下一步(next)直到安装完成。

# 2、检查nvm是否安装成功:nvm -v

跟上面检查node方式一样:window系统为例,快捷键window键 + R键(开始-->运行) 输入:cmd 打开命令行工具,输入命令:

nvm -v 或者 nvm version  (能输出版本号则安装成功)

# 3、设置nodejs、npm下载源(可选)

我们用nvm来管理和下载nodejs各个版本,在安装完的nvm文件夹里面(一般是这个路径):C:\Users\Administrator\AppData\Roaming\nvm 有一个文件 settings.txt
默认情况下,也就是你不往这个里面指定node的下载源地址,那么:
① node的默认下载源地址是:https://nodejs.org/dist/ (国外地址)

如果你感觉到时候下载nodejs非常慢,可以在settings.txt写入以下代码,指定nodejs和npm包的下载源,它们是国内的淘宝镜像地址,下载会非常快

node_mirror: https://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/

https://npm.taobao.org/mirrors/node/ 淘宝nodejs下载源
https://npm.taobao.org/mirrors/npm/ 淘宝npm包下载源

# 4、使用NVM包管理器

以下是常见命令,不需要记忆,用的时候回来看一下(最好是记住)

nvm -h                  //查看所有nvm命令

nvm ls 或者 nvm list      //查看安装的所有node.js的版本

nvm list available        //查看显示可以安装的所有node.js的版本

nvm install 20.10.0       //安装20.10.0版本的node.js包 (你们可以安装:18.12.1 和 12.16.1)

nvm use 20.10.0           //指定使用node的版本是: 20.10.0 (如果报错是权限不够,切换成管理员模式重新cmd)
node -v  //看一下是否切换过来了

nvm list installed       //查看已经安装的node.js版本

nvm current              //显示当前使用的node.js版本

nvm uninstall 12.16.1    //卸载node.js版本号为:12.16.1的包
nvm ls //在查看一下安装的版本,看是否卸载了

# 三、NPM包管理(npm包管理工具)

我们上一节课讲nvm的时候,提到了npm包下载源,那什么是npm呢?全称:Node Package Manager,是一个NodeJS包管理和分发工具。那它有什么用呢?它可以帮我们去管理我们项目中引用的第三方库、插件、模块等等。我们来回顾一下,我们前端网页怎么引入第三方库和插件的.

<script src="./static/js/jquery.1.11.3.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script src="./static/js/index.jquery.js"></script>

我们先引入jquery库,然后在它的基础上引入jquery的插件,最后利用jquery库和插件,写我们自己的代码。如果到后期库和插件非常多,我们也不敢随便删,管理起来特别不方便。那么在我们的node中,就有这个npm,它可以来管理我们这些库、插件、模块等等。
这个npm包管理中有一个非常重要的文件,是package.json,它会帮你记录你用了哪些库,插件,模块,以及它们之间的依赖。

# ① package.json 文件如何生成

依旧在D:\mynode\文件夹下,打开命令行窗口:先按住键盘SHIFT(shift)键不要松手然后点鼠标右键,鼠标左键点击:在此处打开Powershell 窗口
为了方便我们学习,我们统一将node版本设置到:18.12.1

nvm use 18.12.1 //指定使用node的版本是: 18.12.1
node -v  //看一下是否切换过来了
npm init //初始化项目的意思(发现有一堆英文,不用去管)

package name: (mynode) 包名字叫什么,默认显示的是文件夹的名字 mynode 可以直接回车,不用写,也可以自己写一个英文名字
version: (1.0.0) 版本号是多少,可以直接回车按这个默认的即可
description: 描述是什么 不想填可以空着,回车
entry point: (a.js) 问你入口文件是哪一个 默认找我们写的a.js文件,可以回车,要是写index.js 需要创建一个index.js文件
test command: 测试命名是什么,不懂可以先回车
git repository: github仓库地址是哪里,不懂先回车
keywords: 关键字是什么,别人通过什么关键字搜索你的包,不懂先回车
author: 作者是谁 51yrc 也可以不写
license: (ISC) 协议是什么,默认ISC ,不懂先回车

Is this OK? (yes) 问你是否ok是上面这些东西? 回车即可
//然后去`D:\mynode\`文件夹下看一下是否有`package.json`文件

可以直接将 D:\mynode\文件夹拖到vscode

package.json文件内容

{
  "name": "mynode",
  "version": "1.0.0",
  "description": "",
  "main": "a.js",
  "scripts": { //多了这个脚本
    //输入test 相当于输入了后面这段内容,就是帮你少些一些代码,这个后面会讲
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "51yrc",
  "license": "ISC"
}

# ② NPM (npm) 、 CNPM (cnpm)

# Ⅰ、 npm

接下来我们看一下npm如何帮我们管理第三方的包、库、插件、模块、依赖等等

cls //清理一下屏幕
//比如:安装一下 jquery
//传统网页引入jquery:去cdn网站:https://www.bootcdn.cn/ 搜索jquery
//然后在线引入 后者 下载到本地引入

//在我们node中,是通过 npm install jquery 来下载安装
//下载包的地址,就是我们上面讲nvm的时候,指定的淘宝镜像地址:https://npm.taobao.org/mirrors/npm/
npm install jquery 
//此时发现多了一个文件夹:node_modules 专门用于存放第三方的包、库、插件、模块、依赖等等
//看一下`package.json`文件:
"dependencies": { //多了依赖项
    "jquery": "^3.7.1"  //表明目前项目用的jquery版本是:3.7.1
}
//也就意味着:你不用关系你下载了多少库、插件等等,它都帮你记录好了,后面如果你把项目给你同事
//他只需要看一下这个`package.json`文件,就知道你项目引入的库、插件等等
//安装jquery
npm install jquery 
//删除jquery
npm uninstall jquery 

总结一下:

npm init              //初始化`package.json`文件,需手动输入一些基本信息
npm init -y           //初始化一个默认信息的`package.json`文件

npm install 包名     //安装
npm i 包名           //安装简写

npm uninstall 包名   //移除
npm un 包名          //移除简写

# Ⅱ、 cnpm (可选)

你可以理解成国内版的npm,也就是下载的镜像在国内。我们上面讲nvm的时候,已经将npm包的下载源切换到了国内淘宝镜像了,因此使用npm命令和cnpm命令没有区别。
但是别的很多老师不会跟你讲nvm切换node版本,或者nrm管理npm下载源等,所以在没有安装nvm、nrm等管理工具的情况下,在没有指定npm下载源的情况下,npm包默认的下载地址是在国外,下载会很慢 我们上面的jQuery包文件就会去国外网站下载,下载会很慢。
这个时候可以考虑用cnpm命名下载,下载国内的包资源,如: npm淘宝镜像

# ① 安装cnpm

npm install -g cnpm --registry=https://registry.npmmirror.com
//通过install 安装cnpm 方式是 -g, -g表示:global,全局的意思
//如果不要-g,相当于只是在我们的mynode文件夹里面装,出了这个文件夹,cnpm命令就失效了
//所以全局装,哪里都可以用cnpm命令,后面这串:--registry=https://registry.npmmirror.com
//就是下载源的意思,比如你要下载jquery,就到这个网站里面来下载,非常快

# ② 接下来就可以使用cnpm命令安装各个包、插件、模块等等

说明:如果出现报错,是系统的安全策略问题导致的,认为cnpm命令不安全,需要设置安全策略,具体报错参考:https://zhuanlan.zhihu.com/p/617284262 解决方案参考:https://blog.csdn.net/dreaming317/article/details/128163873
1、开始-->所有程序-->Windows PowerShell-->展开选第一个-->鼠标右键-->以管理员身份运行;
2、输入“Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser”回车;
3、根据提示,输入Y,回车;
4、再次回到控制台 输入cnpm --version 执行就成功,若不成功,关闭vscode软件重新打开执行就好了

cnpm install jquery     //安装jquery
cnpm install 包名     //安装
cnpm i 包名           //安装简写

cnpm uninstall jquery  //移除jquery
cnpm uninstall 包名   //移除
cnpm un 包名          //移除简写

# ③ 在vscode中运行命令

大家如果觉得在文件夹打开PowerShell非常麻烦,也可以直接在vscode运行命令:

快捷键:
CTRL + SHIFT + `
或者
终端-->新建终端-->选择你的项目文件夹-->回车即可

总结:

  1. 不管你通过npm还是cnpm安装第三方的包、库、插件、模块、依赖等等,都会自动生成这个文件夹:node_modules,所有安装的包都在这个文件夹里面,实际项目上线或者给别的程序员用的时候,只需要查看你的package.json文件,就知道你安装了多少第三方的包、库、插件、模块、依赖等等,所以上线的时候,不需要将node_modules文件夹给别人,别人也不需要,它会根据你的package.json文件,自行进行安装,从侧面印证了package.json文件的重要性及node的运行模式。

# ④ npm 或 cnpm 常用命令

以npm为例,cnpm是一个意思

npm init             //初始化`package.json`文件

npm install 包名     //安装
npm i 包名           //安装简写

npm uninstall 包名   //移除
npm un 包名          //移除简写

npm update 包名     //更新某个包到最新版,如:npm update jquery

//重要,别人拿到你的项目或者你项目上线,都不会要你的`node_modules`文件夹
//都会先把你`package.json`文件里面的所有包安装一下
npm i 或者 npm install 

# 四、Node的模块

Node的模块是nodejs中非常重要的部分,把模块学清楚了,咱们对nodejs就相当于已经入门了。node的模块分为三种:全局模块、系统模块、自定义模块。
我们的学习方式是先易后难,先总体讲一下这三种模块,大家先有个了解,然后针对每个模块在进行细致的讲解。

# ① 全局模块 :process为例

粗俗的理解:你可以看成是我们js中的window对象、document对象,不管你的什么作用域,不管你层级有多深,这两个对象都可以直接进行调用。我们node中的全局模块也是这个意思。
全局模块我们随时随地都能够访问,不需要引用

以:process为例,做一个讲解

process字面意思,你可以理解成进程,它下面有很多属性,常用属性env、argv

//process.env  环境变量
//开始->此电脑(右键属性)-> 高级系统设置-> 高级->环境变量
//用户变量: 当前用户(比如管理员)设置的变量  
//系统变量:不管哪个用户都可以访问的变量
//每个电脑的环境变量,用户变量都不一样,你们也可以自己设置
//那它的作用是什么呢:比如设置一个:dev:true
//可以理解成这个电脑是开发用的,如果是false, 那么你可以理解成这个电脑是服务器用的
//那么你可以通过环境变量来判断,你的电脑是开发的电脑还是当服务器的电脑,反正用途非常多

// console.log(process);
// console.log(process.env);
// console.log(process.env.dev);
if(process.env.dev){
   console.log('我是开发电脑')
}else{
    console.log('我是服务器电脑')
}
// console.log(process.argv) // node index a b c d e //node index node -vnode
//简易计算
//console.log(process.argv[2]); //node index 1 2
let num1 = parseInt(process.argv[2]);
let num2 = parseInt(process.argv[3]);
console.log(num1 + num2);//node index 1 2
//process功能其实非常多,这里只是简单举个例子,让大家知道什么是全局模块,后面我们会细讲

# ② 系统模块 : path、fs模块为例

粗俗的理解:就是系统内置好的模块,你只需要通过 require()方法进行引入,但是不需要去下载的模块。也就是我们在安装nodejs的时候,它已经帮我们内置好了的模块。
同样,nodejs中的系统模块也是非常的多,我们下面举几个例子给大家介绍一下

path模块:用于处理文件路径和目录路径的模块工具

//path:用于处理文件路径和目录路径的模块工具
let path = require('path');//那么它有很多的属性方法
console.log(path);

//解析文件或目录路径
console.log(path.dirname('/mynode/index.js'));//打印文件所在目录 /mynode
console.log(path.basename('/mynode/index.js'));//打印文件名字 index.js
console.log(path.extname('/mynode/index.js'));//打印文件扩展名 .js
//那么可以通过这个判断前端给你上传的文件是js文件还是图片文件还是视频文件等等

//比如说path里面还有一个方法resolve,看一下
console.log(path.resolve('/mynode/a/b/c','../../','d'));//D:\mynode\a\d
//也就是resolve方法会根据你的想法走,最后返回你想要的结果路径
//我们上一节课讲了全局模块process, 比如我们在举例一个全局模块
console.log(__dirname);//D:\mynode 得到我们当前文件所在物理路径(绝对路径)
console.log(path.resolve(__dirname,'index.js'));//D:\mynode\index.js

fs模块:用于文件读写操作(这里做个简单介绍,后面会详细介绍)

我们以文件读写为例,简单给大家先介绍一下,有两种操作方式:异步和同步
先看异步

let fs = require('fs');
//简单读
// fs.readFile('./demo.json',(err,data)=>{
//     if(err){
//         console.log(err);
//     }else{
//         //console.log(data);//Buffer的一串字符,node中类似于二进制的东西
//         //我们知道电脑底层都是二进制数据,但是数据量非常庞大,于是用更高进制比如十进制十六进制展示
//         //那么如何转成我们能看得懂的数据呢,字符串的.toString()方法即可
//         console.log(data.toString());
//     }
// });
//简单写
let o = {
    "name":"联系我们",
    "href":"contact.html",
    "id":5,
    "en_name":"contact",
    "active":false
}
// fs.writeFile('./demo.json',JSON.stringify(o),(err)=>{
//     if(err){
//         throw err;
//     }
//     //把之前的干没有了,覆盖了
// })

//追加
fs.writeFile('./demo.json',JSON.stringify(o),{
   flag:'a', //追加的意思
},(err)=>{
    if(err){
        throw err;
    }
});

再看同步

let fs = require('fs');
//同步读
// let data = fs.readFileSync('./demo.json');//同步没有回调函数
// console.log(data.toString());
//同步写
let o = {
    "name":"联系我们",
    "href":"contact.html",
    "id":5,
    "en_name":"contact",
    "active":false
}
//fs.writeFileSync('./demo.json',JSON.stringify(o));//覆盖了
//fs.writeFileSync('./demo.json',JSON.stringify(o),{flag:'a'});//追加

以上只是非常简单的举个例子,演示一下读和写文件,并且简单展示了异步操作和同步操作,更多操作我们会在后面给大家详细讲。

# ③ 自定义模块: exports、module输出、require引入

粗俗的理解:就是我们自己封装的模块,通过 exports、module输出、require引入。
简单举例: 新建/mynode/mod.js,简单导出

exports.a = 1;
//exports粗俗理解成一个对象,类似json,你往它里面设置一个属性a,然后暴漏出去
exports.b = 2;
let c = 3;

/mynode/index.js 先看:require

let mod =  require('./mod');//不加./ 就去node_modules文件夹找
console.log(mod);
console.log(mod.a);//1
console.log(mod.b);//2
console.log(mod.c);//undefined

//require查找模块说明
//1. 如果有路径,如:./ 表示同级目录,就去路径里面找;
//2. 没有路径就去node_modules文件夹找
//3. 即没有路径也没有node_modules文件夹,就去nodejs安装目录里面找
//   nodejs安装目录里面也有一个node_modules文件夹,但是它里面装的都是全局模块
// 找不到则报错

再看:module (粗俗理解批量导出)

/mynode/mod.js

module.exports = {
    name:'迪丽热巴',
    age:31,
    sex:'女'
}
module.exports = {
    name:'古力娜扎',
    age:35
}
//还可以导出 函数、对象、类等等,后面案例再讲
let mod =  require('./mod');
console.log(mod);
console.log(mod.name);
console.log(mod.age);
//注意:两个module.exports,后者覆盖前者,也覆盖了exports的导出

# ④ 重要系统模块:http模块,搭建网页服务器

http模块可以帮我们快速搭建一个web服务器,通过服务器对象: http.createServer()

let http = require('http');
// console.log(http);
http.createServer((request,response)=>{
    // console.log('搭建了一个web服务器了')

    response.setHeader('Content-Type','text/html; charset=utf-8');
    // response.write('首页');
    // response.end();//结束
    //直接向服务器响应一些内容
    //response.end('首页');

    //看一下request ,浏览器输入:localhost:8888/index.html
    //console.log(request);
    //console.log(request.url);//  /index.html

}).listen(8888);//给一个端口号
//端口号的范围是0-65536之间,测试过程中,如果设置的端口被占用,就换一个
//如何执行:在浏览器输入: localhost:8888

//重新执行重启服务器: Ctrl + C

搭建一个简单的网页服务器

把我们做的网页复制过来,搭建网页服务器

let http = require('http');
let fs = require('fs');
http.createServer((request,response)=>{
    //异步读
    let url = request.url;
    // fs.readFile('./' + url,()=>{});
    fs.readFile(`./${url}`,(err,data)=>{
       if(err){
          // throw err;
          response.setHeader('Content-Type','text/html; charset=utf-8');
          response.writeHead(404);//网页状态码
          response.end('404页面');
       }else{
          response.writeHead(200);
          response.end(data);
       }
    });
}).listen(8888);//给一个端口号

# 五、Node中的数据交互,重要系统模块:url模块处理get请求,querystring模块处理post请求

交互原理

关于数据请求方式有很多,我们在前面的Ajax、jQuery章节已经给大家讲了,用得最多的就是我们的GET、POST请求,具体查看:章节6-2、理解get、post请求
通过上面的图片可以看出:
GET请求,一般是将数据放在请求头里面,通过url方式传递数据,传输的数据量较小;
POST请求,一般是将数据放在请求体里面进行传递。

html代码具体查看: 章节6-二、jQuery中的Ajax-4、表单序列化

<form id="myForm" name="yourForm" class="my-5 flex justify-center">
   <select name="usertype">
         <option value="学生value">学生text</option>
         <option value="老师value">老师text</option>
         <option value="管理员value">管理员text</option>
         <option></option>
   </select>
   <input type="radio" name="sex" value="" ><input type="radio" name="sex" value="" checked="checked"><input type="checkbox" name="loves" value="篮球" checked="checked"> 篮球
   <input type="checkbox" name="loves" value="足球" checked="checked">  足球
   <input type="checkbox" name="loves" value="乒乓球" >  乒乓球
   账号:<input type="text" name="username" value="abc">
   <input id="sub"  type="button" value="提交"  >
</form>

js代码具体查看: 章节6-二、jQuery中的Ajax-4、表单序列化-③ $.param()方法将对象转换为字符串键值对格式

我们现在学习nodejs,就可以自己写一个接口了,比如说接口为:./api/test

$(function(){
    $('form input[type=button]').click(function(){
       // console.log($('input:checked[name=loves]'));//集合
       $.ajax({
          type: 'POST', //表单提交我们采用post , get方式数据量小也可以
          url: './xxx',//提交给服务器的接口地址,一般后端程序员给你一个接口(当然你可以自己写一个接口)
          //使用$.param()将对象形式的键值对转为 URL 地址的字符串键值对,可以更加稳定准确的传递表单内容
          data:$.param({ 
             username : $('input[name=username]').val(),
             usertype : $(':selected')[0].value,
             sex : $('input:checked[name=sex]')[0].value,
             loves : function(){
                let loves = $('input:checked[name=loves]');
                let str = '';
                for(let i=0;i<loves.length;i++){
                   if(!str){
                        str = loves[i].value;
                   }else{
                        str += ',' + loves[i].value;
                   }
                }
                return str;
              }
          }),
          beforeSend:function(xhr){
             // console.log(this);
             $('form input[type=button]').val('提交中,请稍后...');
          },
          success: function (response, stutas, xhr) {
             $('form input[type=button]').val('提交');
             console.log(response)
          }
       });
    });
});

# ① url模块处理GET(get)请求:url.parse(url,true)

let http = require('http');
let fs = require('fs');
let $url = require('url');
http.createServer((request,response)=>{
    //看一下get请求
    let url = request.url;
    // console.log(url);
    //  /api/test?username=abc&usertype=%E5%AD%A6%E7%94%9Fvalue&sex=%E5%A5%B3&loves=%E7%AF%AE%E7%90%83%2C%E8%B6%B3%E7%90%83
    // 如何拿接口地址,以及传递的数据,通过系统模块:url模块
    // console.log($url);
    // console.log($url.parse(url));
    // console.log($url.parse(url,true));//true 把query字符串数据转成对象
    let { pathname, query} = $url.parse(url,true);
    console.log(pathname);
    console.log(query);

    fs.readFile(`./${url}`,(err,data)=>{
        if(err){
            response.setHeader('Content-Type','text/html; charset=utf-8');
            response.writeHead(404);
            response.end('404页面');
        }else{
            response.writeHead(200);
            response.end(data);
        }
    });
}).listen(8888);

# ② querystring模块处理POST(post)请求:querystring.parse()

我们上节课说了,post请求是将数据放在请求体里面传输的,简单表单数据get/post都可以,但是文件、图片、视频等大数据,一般用post请求发送。

我们先将上节课的get请求换成post请求:type: 'post'
另外大家需要先了解,我们get请求,数据量较小,服务器一次性拿到请求的数据url及参数。而post请求,数据通过请求体发送,数据量较大,服务器获取数据量的方式是分段获取,不是一次性拿到浏览器发送的数据

let http = require('http');
let fs = require('fs');
let $url = require('url');//处理get请求
let querystring = require('querystring');//处理post请求
http.createServer((request,response)=>{
    //看一下post请求
    // console.log(request.method);
    if(request.method == 'GET'){
        let url = request.url;
        if(url.indexOf('/api/') == -1){
            fs.readFile(`./${url}`,(err,data)=>{
                if(err){
                    response.setHeader('Content-Type','text/html; charset=utf-8');
                    response.writeHead(404);
                    response.end('404页面');
                }else{
                    response.writeHead(200);
                    response.end(data);
                }
            });
        }  
    }else if(request.method == 'POST'){
        //由于是分段获取数据,通过on进行监听data事件,获取每段的数据
        //通过回调函数获取每段的数据结果
        //前面我们讲fs模块的时候说过读写数据,读的是二进制buffer数据,这里也一样
        let result = [];
        request.on('data',buffer=>{
           //  console.log(buffer);//获取每段数据,特别视频大的时候,会多次执行data事件,就会有多个buffer
           result.push(buffer);
        });
        //通过end事件,拿到所有的数据进行处理
        request.on('end',()=>{
           //console.log(result);
           //通过buffer对象将每一段数据拼起来
           let data = Buffer.concat(result);
           // console.log(data.toString());
           //username=abc&usertype=%E5%AD%A6%E7%94%9Fvalue&sex=%E5%A5%B3&loves=%E7%AF%AE%E7%90%83%2C%E8%B6%B3%E7%90%83
           //通过系统模块:querystring 处理
           console.log(querystring.parse(data.toString()));
           //实际开发中,我们处理post请求,比如图片视频有其他方式,大家这里先了解post请求一般处理方式
        });
    }
}).listen(8888);

# 六、nodejs项目监测文件变化,自动重启工具:Nodemon

我们在前面的讲解中,每次修改我们的node文件,都需要重新启动nodejs服务器,很繁琐,主要是为了让大家从基础学起,一步一步了解我们nodejs的特性。那么,从本节课开始,我们学习一下如何在我们修改了js文件的情况下,不用重新启动我们的nodejs服务器,也可以更新我们修改后的内容。

我们需要安装一个监测nodejs项目文件变化的自动重启工具:nodemon。它会自动监测nodejs中文件的变化,帮我们重启node服务。

1、我们可以将我们上节课的代码,换一种写法

let http = require('http');
let fs = require('fs');
let $url = require('url');
let querystring = require('querystring');
const server =  http.createServer((request,response)=>{
    if(request.method == 'GET'){
        let url = request.url;
        if(url.indexOf('/api/') == -1){
            fs.readFile(`./${url}`,(err,data)=>{
                if(err){
                    response.setHeader('Content-Type','text/html; charset=utf-8');
                    response.writeHead(404);
                    response.end('404页面');
                }else{
                    response.writeHead(200);
                    response.end(data);
                }
            });
        }  
    }else if(request.method == 'POST'){
        let result = [];
        request.on('data',buffer=>{
           //  console.log(buffer);//获取每段数据,特别视频大的时候,会多次执行data事件,就会有多个buffer
           result.push(buffer);
        });
        //通过end事件,拿到所有的数据进行处理
        request.on('end',()=>{
           //console.log(result);
           //通过buffer对象将每一段数据拼起来
           let data = Buffer.concat(result);
           // console.log(data.toString());
           //username=abc&usertype=%E5%AD%A6%E7%94%9Fvalue&sex=%E5%A5%B3&loves=%E7%AF%AE%E7%90%83%2C%E8%B6%B3%E7%90%83
           //通过系统模块:querystring 处理
           console.log(querystring.parse(data.toString()));
           //实际开发中,我们处理post请求,比如图片视频有其他方式,大家这里先了解post请求一般处理方式
        });
    }
});

server.listen(8888,'127.0.0.1',()=>{
    console.log('服务器已启动');
});
//我们可以通过:
// http://localhost:8888/index.html 访问首页
// http://127.0.0.1:8888/index.html ip地址访问首页

2、回到我们的package.json文件

//我们正常情况,启动服务:
node index 或者 node index.js //因为index.js在我们的根目录
//如果index.js在根目录src文件夹里面,则:
node src/index 或者 node src/index.js
//我们说package.json里面的script就是给我们调试用的
"scripts": {
    "start":"node index.js", //npm run start
    "dev":"node src/index.js", //npm run dev
    "test": "echo \"Error: no test specified\" && exit 1" //npm run test
 },

如果此时,修改我们的index.js代码,刷新没有生效,需要重启 Ctrl + C , 启动服务:npm run start

简单说一下原因:nodejs在执行的时候,当我们把服务启动的时候,我们写的这些js代码,它会被装载到内存里面去,整个代码在执行的时候,它不会去重新解析我们的文件,这样才会提高它运行的效率,因为你启动可以时间长一点,我把你所有要执行的文件都解析出来,然后在执行的时候,效率就会变得更高。如果我在你启动的时候,没有把你要执行的文件提前做解析,等你运行的时候,当你需要这个文件的时候,我再去解析,这个时候会比较慢,因为你一运行,就想马上看效果,我在解析就会耽误时间,或者你文件丢失还会发生错误,所以这是nodejs做的提高运行效率的机制,因此,如果修改了代码,我们需要把服务先停掉,然后再重新启动,这就相当于把你的新代码重新装载到内存里面了,那么新的代码就会生效。 这就是为什么我们每次修改了代码,都要重启的原因。

当然,当我们在开发的时候,我们会频繁的修改代码,就会去频繁的启动服务器,会非常累。只有我们的代码上线到服务器之后,我们才不会去更改。 因此,在我们开发期间,为了不至于频繁重启服务器,我们可以使用一个工具:nodemon,它会去监测文件的变化然后自动帮我们重启服务。

# ① 安装nodemon

npm install nodemon -D
//加 -D 的意思是:安装在我们dev环境,就是我们的开发环境,上线之后初始化的时候无需安装

# ② 修改package.json 中的启动命令

"scripts": {
   "start":"nodemon index.js", //npm run start
   "start:node":"node index.js", //npm run start:node
   "dev":"node src/index.js", //npm run dev
   "test": "echo \"Error: no test specified\" && exit 1" //npm run test
},
//输入命令运行
npm run start

//观察终端输出信息:
// [nodemon] 3.0.2  表示nodemon版本号

// [nodemon] to restart at any time, enter `rs`
// 表示在任何时间想要重新启动,可以输入:rs 即可重启

// [nodemon] watching path(s): *.*
// 表示监测所有路径的文件变化,不管你是根目录,还是文件夹里面的文件

// [nodemon] watching extensions: js,mjs,cjs,json
// 表示监测文件的类型:js,mjs,cjs,json的文件

也就是说,我把package.json改一下,它也会重启。但是package.json我们很少改动,没必要重启。因此,我们可以给nodemon一个配置文件,告诉它哪些文件需要监视,修改后可以重启

# ③ 配置nodemon,告诉它哪些文件需要修改后重启服务(可选项)

// 根目录新建: /nodemon.json文件
{
    "watch":["./src/**/*.*"] 
    //表示匹配:./src/ 代表跟nodemon.json文件同级别的src文件夹里面的
    // ./src/**/ src文件夹里面的任何子集文件夹
    // ./src/**/*.* 任何子集文件夹里面的任何子集文件
    //说白了:就是监听src文件夹里面的所有文件的变化,包含src文件夹里面还有文件夹里面的文件
    
    "watch":["./src/**/*.js"]  //表示src文件夹里面的所有js文件

    //配置文件改完之后,我们重新启动一下服务,之后观察变化
    //发现修改 src里面的index.js 会重新启动,除了src文件夹里面的文件,其他文件都不会重新启动
    //这个就是指定监控的文件,修改自动重启。
   

    //我们这里可以配置成
    "watch":["./*.js"]  //根目录下所有js文件,也可以删除nodemon.json文件不做配置
}

当然,你如果想监控整个项目,就没必要配置这一项了。

# 七、nrm (使用nrm管理npm下载源)

我们在前面讲nvm的时候,已经让大家配置过npm的下载源,在讲cnpm命令的时候,也让大家设置过npm的下载源(就是我们的淘宝镜像)。
我们本节课再来看一下管理工具:nrm,它可以帮我们管理多个npm下载源地址。

//查看一下老师电脑
nrm ls //如果你电脑没有输出,则需要安装nrm

我们知道我们前面讲的npm源,都是我们自己手动配置的,现在通过nrm配置下载源就非常方便了。

# ① 安装nrm

npm install nrm -g
//-g 的意思是:前面说过了,global,全局的意思,也就是命令可以在任何一个文件夹使用

接下来,我们就可以随意的切换各种下载源

 //首先看一下nrm自己内置的下载源地址
 nrm ls 

 npm ---------- https://registry.npmjs.org/    //npm 官方源
 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内置的命令函数

 nrm -h
 //可以查看nrm提供的使用命令

 nrm ls //nrm内置的下载源地址
 nrm current //查看当前使用的源下载命令

报错查看:https://blog.csdn.net/sinat_36728518/article/details/134492718 处理方式:指定一个源

 nrm use <registry> //registry为源名.如:taobao、npm、yarn、tencent、cnpm、npmMirror
 nrm use taobao //切换为taobao源
 nrm ls //前面就出现了一个星号,表示当前的镜像源在淘宝上

测试源速度: nrm test <registry>

比如:测试官方源和淘宝源的响应时间

nrm test npm //测试官方源响应时间
nrm test taobao //测试淘宝源响应时间,很明显快很多

# ③ 查看当前正在使用的 npm 镜像源

npm config get registry  // https://registry.npmmirror.com/

# ④ 切换 npm 镜像源

npm config set registry https://registry.npmmirror.com/

# 八、系统模块:fs模块详解

我们在前面讲系统模块的时候,给大家简单的讲解了一下fs模块,由于这个模块比较重要,本节课我们详细讲解一下fs模块的语法和使用。
我们在前面的讲解中,分别用json文件和html文件,让大家通过fs模块进行了读取,本节课,我们用txt文本文件为例,给大家讲解一下fs模块的更多语法和使用。
根目录新建/test.txt文件,随便复制些内容,重复多复制几份,我们在终端操作讲解

# ① 读取文件: 异步readFile、同步readFileSync、promise操作

不加Sync的就是异步操作

let fs = require('node:fs');
fs.readFile('./test.txt',{
    encoding:'utf-8',//文本文档改一下编码
    flag:'r', //r读取文件的意思,a(打开文件进行追加。 如果文件不存在,则创建该文件)
},(err,data)=>{
   if(err) throw err;
   // console.log(data.toString());
   console.log(data);//改一下编码格式:'utf-8'
});

flag属性值,具体查看:https://juejin.cn/post/7304948522506436635

同步方式

let fs = require('node:fs');
//同步方式会阻塞代码,必须先把文件读取完了之后,在继续执行下面的代码
let data =  fs.readFileSync('./test.txt',{
    // 'encoding':'utf-8'
});
// console.log(data);//Buffer数据二进制流数据,每两个16进制字符是一个字节
//方式1:console.log(data.toString("utf-8")); 方式2:文本文档改一下编码:'encoding':'utf-8'
console.log(data.toString("utf-8"));
console.log(123);//发现先输出data 说明阻塞了代码的执行
//总结:
// 当文件小的时候,可以使用同步,当文件大了之后,建议使用异步

promise方式(前面讲过,用then()方法接收,出错用catch())异步方式

let fs = require('node:fs');
let fsPromise = require('node:fs/promises');
fsPromise.readFile('./test.txt',{}).then(data=>{
    console.log(data.toString('utf-8'))
}).catch(err=>{
    console.log(err);
});

# ② 可读流模式:createReadStream()方法

let fs = require('node:fs');
//处理大文件使用这个方式,比如你有1G的文件,它会把文件一段一段给你返回
const readStream = fs.createReadStream('./test.txt',{
    // start:3,
    // end:12,
    encoding:'utf-8'
});
//通过on方法监听事件
readStream.on('open',function(fd){
    console.log('开始读取文件');
});
readStream.on('data',function(chunk){
    console.log('读取到数据:',chunk);
});
readStream.on('end',function(){
    console.log('文件已全部读取完毕');
});
readStream.on('close',function(){
    console.log('文件被关闭');
});
readStream.on('error',function(err){
    console.log('读取文件失败'); 
});

# ③ 创建文件夹:mkdirSync , mkdir

一般我们以同步的方式创建文件夹,因为很快

let fs = require('node:fs');
// fs.mkdirSync('./src') 报错,因为文件夹存在
// console.log(fs.existsSync('./src'));;//true 或者 false

// if(!fs.existsSync('./src')){//不存在则创建
//     fs.mkdirSync('./src');
// }

// if(!fs.existsSync('./demo')){
//     fs.mkdirSync('./demo');
// }

// fs.mkdirSync('./demo1/demo2/demo');//创建多层文件夹失败,需要递归创建
//递归创建多个文件夹
fs.mkdirSync('./demo1/demo2/demo',{
    recursive:true, //执行递归创建
});

# ④ 删除文件夹:rmSync , rm

let fs = require('node:fs');
fs.rmSync('./demo1',{
    recursive:true, //执行递归删除,注意传递首个文件夹即可
}); 

# ⑤ 重命名文件:renameSync , rename

let fs = require('node:fs');
//参数1:原文件名,参数2:新文件名
fs.renameSync('./test.txt','test2.txt');

# ⑥ 监听文件变化: watch

let fs = require('node:fs');
//事件类型可以是: rename 文件重命令 或 change 文件内容改变
fs.watch('./test1.txt',(event,filename)=>{
    console.log(event);// rename  change
    console.log(filename);
});

# ⑦ 写入文件:writeFile、writeFileSync,追加写入文件:appendFile、appendFileSync

同步异步在前面都讲过了

//异步
// fs.writeFile('./test.txt','迪丽热巴',(err) => {
//     if (err) throw err;
//     console.log('文件创建成功!');
// }); 
//同步
// fs.writeFileSync('./test.txt','古力娜扎');
//追加
fs.writeFileSync('./test.txt','\n古力娜扎1',{
    flag:'a'
});
flag的值:
// 'a': 打开文件进行追加。 如果文件不存在,则创建该文件。
// 'ax': 类似于 'a' 但如果路径存在则失败。
// 'a+': 打开文件进行读取和追加。 如果文件不存在,则创建该文件。
// 'ax+': 类似于 'a+' 但如果路径存在则失败。
// 'as': 以同步模式打开文件进行追加。 如果文件不存在,则创建该文件。
// 'as+': 以同步模式打开文件进行读取和追加。 如果文件不存在,则创建该文件。
// 'r': 打开文件进行读取。 如果文件不存在,则会发生异常。
// 'r+': 打开文件进行读写。 如果文件不存在,则会发生异常。
// 'rs+': 以同步模式打开文件进行读写。 指示操作系统绕过本地文件系统缓存。
// 这主要用于在 NFS 挂载上打开文件,因为它允许跳过可能过时的本地缓存。 它对 I/O 性能有非常实际的影响,因此除非需要,否则不建议使用此标志。
// 这不会将 fs.open() 或 fsPromises.open() 变成同步阻塞调用。 如果需要同步操作,应该使用类似 fs.openSync() 的东西。
// 'w': 打开文件进行写入。 创建(如果它不存在)或截断(如果它存在)该文件。
// 'wx': 类似于 'w' 但如果路径存在则失败。
// 'w+': 打开文件进行读写。 创建(如果它不存在)或截断(如果它存在)该文件。
// 'wx+': 类似于 'w+' 但如果路径存在则失败。
//同步追加写appendFileSync
// fs.appendFileSync('./test.txt','\n刘德华');
//异步追加写appendFile
fs.appendFile('./test.txt','\n古天乐',(err)=>{
    console.log(err);
})

# ⑧ 写入文件:创建可写流 createWriteStream()

跟上面的可读流一样的用法,也是处理大量数据使用,大量分批插入

let writestream = fs.createWriteStream('./test.txt');
let arr = [
    '离离原上草',
    '一岁一枯荣',
    '野火烧不尽',
    '春风吹又生'
];
arr.forEach(item=>{
    writestream.write(item + '\n');
});

writestream.end();//关掉可写通道
writestream.on('finish',()=>{
    console.log('写完了')
});
//根据编辑器提示可以看一下其他事件
//硬链接:参数1:原始地址  参数2:硬链接之后的地址
// fs.linkSync('./test.txt','./test2.txt');
//1.发现会创建test2.txt文件 内容一样
//2.它们是共享同一个内存地址的,修改test2.txt文件,test.txt文件也修改
//硬链接 可做共享文件 备份文件
//我们删除掉原始文件 test.txt, test2.txt可以正常使用

//软链接(符号链接)  类似window的快捷方式
// fs.symlinkSync('./test.txt','./test2.txt');
//如果报错,说明需要管理员权限
//如果删除掉原始文件test.txt,则test2.txt无法打开

# 九、node.js + jQuery完成:网页 “联系我们” 页面的留言板功能

具体查看:案例:nodejs+jQuery开发企业网页的留言板功能

# 十、系统模块:crypto模块详解(加密:对称加密、非对称加密、哈希函数)

我们再来学习一个nodejs中的系统模块:crypto模块。讲这个模块源自于同学们提出的问题:说我们留言板存储的message.json文件,还是过于暴露,最起码应该将用户的手机号加个密之类的,因此我们本节课来学习一个nodejs内置的系统模块crypto模块,它为我们提供通用的加密和哈希算法
用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。nodejs用C/C++实现这些算法后,通过crypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。

# ① 对称加密

简单理解就是双方协商定义一个密钥以及iv。比如我们商定一个暗号:今晚吃鸡,然后你把你的密文和密钥进行一个混合,然后发给我,因为我知道你的暗号的,所以我可以利用这个暗号去把密文给它解出来,就是双方协定的一个密钥以及iv,称之为对称加密。

//对称加密
const crypto = require('node:crypto');
//参数1:接收一个算法,一般是:aes-256-cbc | aes-192-cbc
//参数2:接收一个密钥,32位的 | 24位的
//参数3:接收一个iv,初始化向量,支持16位

let key = crypto.randomBytes(32); console.log(key);
//iv的作用是保证我们生成的密钥串每次是不一样的,如果密钥串多了还是少了位数,还会自动补码
let iv = Buffer.from(crypto.randomBytes(16)); console.log(iv);
let cipher = crypto.createCipheriv('aes-256-cbc',key,iv);
//通过cipher就可以进行加密操作
//参数1:密文,参数2:格式 utf-8,参数3:输出格式:比如16进制 hex
cipher.update('迪丽热巴','utf-8','hex');
//输出加密后的密文,16进制的
let result = cipher.final('hex');
console.log(result);

//解密 : 相同的算法,相同的key,相同的iv
let decipher = crypto.createDecipheriv('aes-256-cbc',key,iv);
decipher.update(result,'hex','utf-8');//注意换一下位置
console.log(decipher.final('utf-8'));

# 封装加密函数

let crypto = require('crypto')
// data:需要加解密的内容,
// key: 密钥(32位的'aes-256-cbc')(24位的'aes-192-cbc')
// 初始化向量(iv)支持16位
function aesEncrypt(data, key, iv) {
  // 给定的算法,密钥和初始化向量(iv)创建并返回Cipher对象
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv)
  // 指定要摘要的原始内容,可以在摘要被输出之前使用多次update方法来添加摘要内容
  // 数据的编码 utf8 返回值的编码 hex
  var crypted = cipher.update(data, 'utf8', 'hex')
  crypted += cipher.final('hex')
  return crypted
}

function aesDecrypt(data, key, iv) {
  // 给定的算法,密钥和初始化向量(iv)创建并返回Cipher对象
  const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv)
  // 数据的编码 hex 返回值的编码 utf8
  var decrypted = decipher.update(data, 'hex', 'utf8')
  decrypted += decipher.final('utf8')
  return decrypted
}

//自定义IV(16位)、key(密钥(32位的'aes-256-cbc')(24位的'aes-192-cbc'))
const iv = 'IloveYOU@520520!' // 初始化向量(iv)16位
let data = '迪丽热巴' // 需要加解密的内容,
let key = 'a123456789@!*&%bcdef@123456789&&' // 32 位秘钥密钥

let encryptData = aesEncrypt(data, key, iv)
let decryptData = aesDecrypt(encryptData, key, iv)

console.log(encryptData);//e8b917533cea0c134bd2b920c7a0c667
console.log(decryptData);//迪丽热巴 

更多封装方法:参考 封装加密

# ② 非对称加密

非对称加密可以生成公钥和私钥,私钥只能是管理员拥有,不能对外公开,公钥可以对外公开。比如说我们想用你的这个算法,我们可以拿到你的公钥然后去进行加密,得到加密之后的密文可以公开,然后私钥呢,管理员可以对加密之后的结果进行解密,其他人由于没有私钥,所以无法解密。

//非对称加密
const crypto = require('node:crypto');
const {privateKey,publicKey} = crypto.generateKeyPairSync('rsa',{
   modulusLength:2048,//长度越长越安全,但是执行越慢
});
//使用公钥加密
const encrypto = crypto.publicEncrypt(publicKey,Buffer.from('迪丽热巴'));
console.log(encrypto.toString('hex'));
//私钥解密
const decrypto = crypto.privateDecrypt(privateKey,encrypto);
console.log(decrypto.toString());

# ③ 哈希函数加密

不能被解密的,因为它是单向的,不可逆的,常用于用户登录密码、文件内容的比对校验

//哈希函数 
const crypto = require('node:crypto');
//'sha256'加密
/*
let hash = crypto.createHash('sha256'); //md5
hash.update('迪丽热巴');
let result = hash.digest('hex');
console.log(result); 
*/  
// md5加密
let hash = crypto.createHash('md5');
hash.update('迪丽热巴');
let result = hash.digest('hex');
console.log(result); 

关于哈希函数,说明几点:

  1. 固定长度输出:不论输入数据的大小,哈希函数的输出长度是固定的。例如,常见的哈希函数如 MD5 和 SHA-256 生成的哈希值长度分别为 128 位和 256 位,对应的字符串长度'md5'的长度是32位,'sha256'生成的长度是64位。
  2. 不可逆性:哈希函数是单向的,意味着从哈希值推导出原始输入数据是非常困难的,几乎不可能。即使输入数据发生微小的变化,其哈希值也会完全不同。当然,这并不意味着就是百分之百安全,虽然说加密之后的密文无法推算出原文,但是网上有很多撞库操作
    用户在注册的时候,会输入密码,我们不会把用户输入的原始密码存进数据库,一般使用'sha256'或者'md5'等方式,将用户输入的密码加密之后,存进数据库,然后用户登录的时候,在比对加密之后的密码(后面课程讲项目的时候会详细讲),但是网上确有很多撞库的网站,如:https://www.cmd5.com/。这就是为什么要求我们的密码写得复杂一些,加标点符号,特殊字符#$@!,大小写等等,就是为了防止撞库。
  3. 唯一性:哈希函数具有非常低的碰撞概率,即不同的输入数据生成相同的哈希值的可能性非常小。这有助于确保哈希值能够唯一地标识输入数据。因此可以用它来做密码和文件校验,很多时候我们可以在后端数据库读取文件或内容比如密码(已加密的md5密码),然后前端用户在登录的时候,也会输入密码,我们会将用户输入的密码转成md5,然后和后端的数据库的md5密码做比对,当两者相同时候,说明用户密码是对的,可以登录,否则不能登录。当然,也可以验证文件的一致性,原理和这个一样。

    以上先简单的给大家介绍了一下nodejs中的加密解密操作,更多相关功能,我们会在后面的实战项目再给大家讲解,大家先了解这么多即可。

# ④ 对留言板的手机号做一个加密

具体查看:nodejs服务器端app.js文件,对手机号加密







# 【第二学期第2季课程】其它章节

# 章节1.课程介绍

# 章节2.面向对象与原型

# 1、创建对象

# ① 创建对象,剖析问题:传统创建对象方法代码重复冗余,对象无法识别从属于哪个函数
# ② 传统创建对象:工厂模式(没有办法识别某一个对象的引用)
# ③ 构造函数(构造方法)创建特定的对象
# ④ 构造函数知识扩展,对象冒充构造函数,构造函数体内的函数返回值相等,但引用地址不相同

# 2、原型

# ① 原型创建对象
# ② 构造函数与原型对比,深度解析(图片示例)
# ③ isPrototypeOf()方法:判断一个对象是否指向了该构造函数的原型对象
# ④ 原型模式的执行流程(顺序):先实例,在构造函数,最后原型
# ⑤ 删除实例属性访问原型属性:delete方法
# ⑥ hasOwnProperty()方法检测属性是否存在实例中,in操作符判断属性是否存在于实例或原型中,两者结合判断属性是否只存在原型中
# ⑦ 原型创建对象字面量声明方式
# ⑧ 原型创建对象字面量声明方式,原型的声明是有先后顺序,重写原型会覆盖(切断)之前的原型
# ⑨ 内置引用类型:String,Number,Array等本身也使用了原型
# ⑩ 原型创建对象缺点剖析:传参和引用共享问题
# ⑪ 组合构造函数+原型模式:解决 ⑩ 构造传参和引用共享问题
# ⑫ 动态原型模式:解决 ⑪ 组合构造函数+原型模式,代码封装在一起,一种封装的感觉
# ⑬ 寄生构造函数:工厂模式 + 构造函数【备胎模式(了解)】
# ⑭ 稳妥构造函数(了解即可):在一些安全的环境中,比如禁止使用 this 和 new,就是寄生构造函数不能用new

# 3、继承

# ① js的继承方式通过原型链完成
# ② 继承父类属性方法的继承顺序:就近原则(实例化-->构造函数实例属性方法-->原型属性方法)
# ③ 继承后的实例从属关系
# ④ 对象冒充继承及问题:原型里面的属性方法无法继承
# ⑤ 组合继承【广泛应用】:原型链+借用构造函数(对象冒充)的模式,完成对象冒充的原型继承
# ⑥ 原型式继承(了解)
# ⑦ 寄生式继承:原型式+工厂模式结合
# ⑧ 继承终极版模式:寄生组合继承来实现继承:组合模式 + 寄生式继承

# 4、类和对象

# ① 理解类和对象
# ② 类中的constructor()方法(构造函数)
# ③ 类中添加方法
# ④ 类的继承
# ⑤ 类的继承中的super关键字:调用父类的构造函数constructor
# ⑥ 类的继承中的super关键字:调用父类的普通函数
# ⑦ 子类继承父类方法同时扩展自己的方法,子类在构造函数中使用super,必须放到this前面
# ⑧ 类和对象的几个注意点:

# 5、面向对象、原型、继承、类小结

# 章节3.封装js库过渡到jQuery

# 章节4.jQuery

# 1、代码风格:$包裹,加载模式:$(function () {}),获取元素DOM对象:get(索引)方法,多个库之间的冲突

# 2、选择器:

# ① ID 选择器、元素选择器、类(class)选择器,属性 length 或 size()方法来查看返回的元素个数
# ② jQuery对象转成DOM对象:get方法或下标获取
# ③ 群组选择器、后代选择器、通配选择器、指定元素前缀选择器
# ④ 层次选择器:jQuery提供后代选择器find、子选择器children、next 选择器、nextAll 选择器
# ⑤ jQuery提供:prev同级上一个元素,prevAll同级所有上面的元素
# ⑥ jQuery提供:siblings()方法:上下同级所有元素,正好集成了 prevAll()和 nextAll()两个功能的效果
# ⑦ jQuery提供:nextUntil()方法:查找同级后面的节点,遇到指定元素停止选定,prevUntil()方法:查找同级前面的节点,遇到指定元素停止选定
# ⑧ 属性选择器:一般超链接用得多点

# 3、过滤器(伪类选择器)

# ① :first,选取第一个元素,返回单个元素,jQuery提供first()方法
# ② jQuery对象转成DOM对象:get方法或下标获取
# ③:not(selector), :not(.active)选取class不是active的元素,返回元素集合,jQuery提供not(selector)方法
# ④ :eq(index),选择索引(0 开始)等于 index 的元素,返回单个元素,jQuery提供eq()方法
# ⑤ :gt(index),选择索引(0 开始)大于 index 的元素,返回元素集合
# ⑥ :lt(index),选择索引(0 开始)小于 index 的元素,返回元素集合
# ⑦ :even,选择索引(0 开始)是偶数的所有元素,返回元素集合
# ⑧ :odd,选择索引(0 开始)是奇数的所有元素,返回元素集合
# ⑨ :header,选择标题元素,h1 ~ h6,返回元素集合
# ⑩ :focus,选择当前被焦点的元素,一般用在表单元素上

# 4、内容过滤器

# ① :contains(text),选取含有text文本的元素,返回元素集合
# ② :empty,选取不包含子元素或空文本的元素,返回元素集合
# ③ :has(selector),如::has(.active) 选择后代元素含有class 是active 的元素,jQuery提供has()方法
# ④ :parent,与:empty刚好相反,选取含有子元素或文本的元素,返回元素集合

# 5、jQuery提供:parent()、parents()、parentsUntil方法特别说明

# ① jQuery提供:parent()方法:选取当前元素的父元素,注意与 :parent的区别
# ② jQuery提供:parents()方法:选择当前元素的父元素及祖先元素
# ③ jQuery提供:parentsUntil方法,如:parentsUntil('ul') 选择当前元素往上一层级查找,遇到ul元素停止

# 6、可见性过滤器

# ① :hidden,选取所有不可见元素,返回元素集合,一般包含:CSS 样式为 display:none、input 表单类型为type="hidden"和 visibility:hidden 的元素
# ② :visible,选取所有可见元素

# 7、子元素过滤器

# ① :first-child,获取每个父元素的第一个子元素,返回元素集合
# ② :last-child,获取每个父元素的最后一个子元素,返回元素集合
# ③ :only-child,获取只有一个子元素的元素,返回元素集合
# ④ :nth-child(odd/even/eq(index)),获取每个自定义子元素的元素(索引值从 1 开始计算)

# 8、jQuery提供选择器和过滤器方法

# ① is()方法:传递选择器、DOM、jquery 对象、函数
# ② hasClass方法,hasClass(); hasClass(class),判断某个元素是否有某个class,比较常用,和 is 一样,只不过只能传递class
# ③ slice方法,slice(start, end),选择从 start 到 end 位置的元素,如果是负数,则从后开始
# ④ end方法,end(),获取当前元素前一次状态:可以找它的父节点,也可以找它的相邻前一个兄弟节点
# ⑤ contents方法,contents(),获取某元素下面所有元素节点,包括文本节点,如果是 iframe,则可以查找文本内容
# ⑥ filter方法,filter(),比较灵活的选择器,扩展性较好

# 9、表单选择器

# ① jQuery方法:通过type类型或者name字段获取表单组件,通过val()方法获取表单组件的值

# 10、jQuery操作DOM及CSS

# 1、设置元素及内容:html(),html(value),text(),text(value)
# 2、获取或设置表单内容:val(),val(value)
# 3、设置单选框、复选框默认选中状态val(),非常好用
# 4、元素属性操作:attr()和 removeAttr()
# 5、元素CSS样式操作
# Ⅰ、css()方法
# ① css()方法获取、设置元素样式
# ② css()方法传递多个样式属性的数组,得到样式属性值对象数组,$.each(box,function(attr,value){})遍历原生态对象数组,jQuery对象数组采用$(selector).each(function(index,element){})方法遍历
# ③ css()方法传递多个 CSS 样式的键值对
# ④ css()方法可以传匿名函数
# Ⅱ、addClass()方法、removeClass()方法、toggleClass()方法
# ① addClass()方法、removeClass()方法
# ② toggleClass()方法:切换class
# Ⅲ、jQuery提供其他css操作方法
# ① jQuery提供:width()、width(value)、width(function (index, width) {})方法:获取、设置、通过匿名函数设置某个元素的长度
# ② jQuery提供:height()、height(value)、height(function (index, width) {})方法:获取、设置、通过匿名函数设置某个元素的高度
# ③ jQuery提供内外边距和边框尺寸方法:innerWidth(),innerHeight(),outerWidth(),outerHeight(),outerWidth(ture),outerHeight(true)
# ④ jQuery提供元素偏移方法:offset()、position()、scrollTop()、scrollTop(value)、scrollLeft()、scrollLeft(value)

# 11、jQuery提供的DOM节点操作方法

# 1、创建节点
# 2、插入节点
# ① 内部插入节点 append(content):向指定元素内部后面插入节点content
# ② 内部移入节点(不需要创建节点) appendTo(content):将指定元素移入到指定元素content 内部后面
# ③ 内部插入节点 prepend(content):向指定元素 content 内部的前面插入节点
# ④ 内部移入节点(不需要创建节点) prependTo(content):将指定元素移入到指定元素content 内部前面
# ⑤ 外部(同级)插入节点 before(content):向指定元素的外部前面插入节点content
# ⑥ 外部(同级)移到节点 (不需要创建节点)insertBefore(content):将指定节点移到指定元素content 外部的前面
# ⑦ 外部(同级)插入节点 after(content):向指定元素的外部后面插入节点content
# ⑧ 外部(同级)移到节点 (不需要创建节点)insertAfter(content):将指定节点移到指定元素content 外部的后面
# 3、包裹节点
# ① wrap(html):向指定元素包裹一层html 代码
# ② wrap(element):向指定元素包裹一层 DOM对象节点
# ③ wrap(function (index) {}):使用匿名函数向指定元素包裹一层自定义内容
# ④ unwrap():移除一层指定元素包裹的内容
# ⑤ wrapAll(html):用 html 将所有元素包裹到一起
# ⑥ wrapAll(element):用 DOM 对象将所有元素包裹在一起
# ⑦ wrapInner(html)、wrapInner(element)、wrapInner(function (index) {}):向指定元素的子内容包裹一层
# 4、节点操作
# ① 复制节点 clone(true)、替换节点:replaceWith、replaceAll
# ② 删除节点:remove() 或者 detach()
# ③ 删除掉节点里的内容empty()

# 章节5.jQuery事件、动画、插件

# 一、事件

# 1、简写事件

# 2、复合事件:hover([fn1,]fn2)

# 3、jQuery中的事件对象:target、currentTarget、e.stopPropagation()、e.preventDefault()、return false

# 4、jQuery中的高级事件:on、off 和 one

# ① on方法
# ② off方法:移除事件
# ③ one方法:仅触发一次的事件

# 5、jQuery中的模拟操作

# 二、动画

# 1、 显示:show、隐藏:hide

# ① 直接调用:显示show()、隐藏:hide()
# ② 传递一个参数(毫秒):显示show(1000)、隐藏:hide(1000)
# ③ 传递一个预设参数:显示show(slow|normal|fast),隐藏:hide(slow|normal|fast),slow:600 毫秒,normal:默认 400 毫秒,fast:200 毫秒
# ④ 传递第二个参数回调函数,实现列队动画(排队动画):show(毫秒数|slow|normal|fast,function(){}),hide(毫秒数|slow|normal|fast,function(){})
# ⑤ 列队动画,可以使用函数名调用自身或者arguments.callee 匿名函数自调用
# ⑥ toggle()切换show()和hide()

# 2、 滑动:slideUp、卷动:slideDown、切换滑动卷动:slideToggle

# 3、 淡入:fadeIn、淡出:fadeOut、切换淡入淡出:fadeToggle、指定透明度:fadeTo

# 4、 自定义动画 animate

# ① animate基本用法:css样式自定义,同步动画
# ② animate用法:animate(css,动画时间,回调函数)
# ③ animate位移动画(将元素设置绝对定位或相对定位)
# ④ 列队动画方法:queue()方法,连缀执行下一个dequeue()方法,clearQueue()清理列队动画后面还没有执行的

# 5、 动画相关方法:stop()强制停止动画,delay()延迟动画执行

# 6、判断在运动的动画,通过过滤器:animated

# 7、动画全局属性:$.fx.interval(设置每秒运行的帧数),$.fx.off(关闭页面上所有的动画),默认swing(缓动),linear(匀速运动)

# 三、jQuery插件

# 1、引入:下载本地引入、或在线引入

# 2、使用插件方法

# 章节6.Ajax

# 一、原生js中的Ajax

# 1、XMLHttpRequest (简称 XHR,XHR API)

# ① 第一步:调用 open()方法准备发送请求(发送请求前的准备工作):三个参数:要发送的请求类型(get、post)、请求的 URL 和表示是否异步
# ② 第二步:通过 send()方法进行发送请求:一个参数:作为请求主体发送的数据,如果不需要则,必须填 null
# ③ 第三步:发送完了之后,得监听结果(监听服务器给你的请求结果),通过readystatechange 事件监听服务器给你的结果

# 2、理解get、post请求

# ① getAllResponseHeaders()获取整个响应头信息,getResponseHeader()获取单个响应头信息,setRequestHeader()设置请求头信息
# ② get请求
# ③ post请求
# ④ 小结get和post请求

# 3、Fetch API

# ① Fetch API基本用法介绍
# ② XHR 与 Fetch 中的Content-Type(或者小写content-type)

# 4、 XHR(xhr) 与 Fetch(fetch)的区别 (包括:jQuery、Axios、umi-request的说明)

# 二、jQuery中的Ajax

# 1、第二层封装:load()方法,$.get()和$.post()方法

# ① load()方法是局部方法 : 异步加载静态文件如:html文件、json文件等
# ② $.get()和$.post()方法:是全局方法,无须指定某个元素,适合传递参数到服务器请求数据

# 2、最高层封装:$.getJSON() 和 $.getScript()

# ① $.getJSON()方法:专门用于加载 JSON 文件的
# ② $.getScript()方法:按需加载接口或js文件

# 3、最底层的封装:$.ajax()

# 4、表单序列化

# ① 常规形式的表单提交(表单提交数据)
# ② jQuery中的表单序列化提交数据(表单提交数据)
# ③ $.param()方法将对象转换为字符串键值对格式

# 5、jQuery中的跨域jsonp

# ① jQuery中的跨域jsonp使用
# ② 延伸一下:jQuery中的跨域jsonp模拟百度搜索提示数据

# 6、 jqXHR 对象: when()方法、done()方法、always()方法和fail()方法

# 章节7.Node.js基础

# 一、Node环境搭建(安装node.js)

# 1、 下载安装node.js

# 2、 检查node.js是否安装成功

# ① 命令行:node -v npm -v npx -v
# ② 命令行:node 运行js代码
# ③ 命令行:运行js文件代码,清屏命令: cls

# 二、NVM(node版本管理工具,切换node版本)

# 1、 下载安装nvm

# 2、检查nvm是否安装成功:nvm -v

# 3、设置nodejs、npm下载源(可选)

# 4、使用NVM包管理器

# 三、NPM包管理(npm包管理工具)

# 1、 package.json 文件如何生成

# 2、 NPM (npm) 、 CNPM (cnpm)

# 1、npm
# 2、cnpm (可选)
# ① 安装cnpm
# ② 接下来就可以使用cnpm命令安装各个包、插件、模块等等
# ③ 在vscode中运行命令
# ④ npm 或 cnpm 常用命令

# 四、Node的模块

# 1、全局模块 :process为例

# 2、系统模块 : path、fs模块为例

# 3、 自定义模块: exports、module输出、require引入

# 4、 重要系统模块:http模块,搭建网页服务器

# 五、Node中的数据交互,重要系统模块:url模块处理get请求,querystring模块处理post请求

# 1、url模块处理GET(get)请求:url.parse(url,true)

# 2、querystring模块处理POST(post)请求:querystring.parse()

# 六、nodejs项目监测文件变化,自动重启工具:Nodemon

# 1、安装nodemon

# 2、修改package.json 中的启动命令

# 3、配置nodemon,告诉它哪些文件需要修改后重启服务(可选项)

# 七、nrm (使用nrm管理npm下载源)

# 1、安装nrm

# 2、nrm内置的命令函数

# 3、查看当前正在使用的 npm 镜像源

# 4、切换 npm 镜像源

# 八、系统模块:fs模块详解

# 1、读取文件: 异步readFile、同步readFileSync、promise操作

# 2、可读流模式:createReadStream()方法

# 3、创建文件夹:mkdirSync , mkdir

# 4、删除文件夹:rmSync , rm

# 5、重命名文件:renameSync , rename

# 6、监听文件变化: watch

# 7、写入文件:writeFile、writeFileSync,追加写入文件:appendFile、appendFileSync

# 8、写入文件:创建可写流 createWriteStream()

# 九、node.js + jQuery完成:网页 “联系我们” 页面的留言板功能

# 十、系统模块:crypto模块详解(加密:对称加密、非对称加密、哈希函数)

# 1、对称加密、封装加密函数

# 2、非对称加密

# 3、哈希函数加密

# 4、对留言板的手机号做一个加密

# 章节8.正则表达式

# 一、创建正则表达式

# ① new运算符创建正则表达式

# ② 字面量方式创建正则表达式

# 二、测试正则表达式

# ① test方法:在字符串中测试模式匹配,返回 true 或 false

# ② exec方法:在字符串中执行匹配搜索,返回结果数组,执行失败,则返回 null

# 三、字符串的正则表达式方法

# ① match方法:就是一个查找的功能,获取匹配的字符串,返回数组或 null

# ② search方法:根据匹配的字符串,返回位置索引(从0开始)

# ③ split方法:按照匹配模式,拆分成字符串数组

# ④ replace方法: 替换匹配到的数据

# 小案例:模拟百度搜索,搜索的关键字设置成红色

# 四、正则表达式RegExp对象的静态属性、实例属性(了解)

# 1、 静态属性

# ① 属性:input,短名:$_ , 当前被匹配的字符串
# ② 属性:leftContext,短名:$` , 最后一次匹配前的子串
# ③ 属性:rightContext,短名:$' , 在上次匹配之后的子串
# ④ 属性:lastMatch,短名:$& , 最后一个匹配字符串
# ⑤ 属性:lastParen,短名:$+ , 最后一对圆括号内的匹配子串

# 2、 实例属性

# 五、正则表达式元字符(包含特殊含义的字符)

# 一、 单个字符和数字、重复字符

# 1、 点符号匹配除了换行符(\n)外的任意字符

# 2、 点符号和重复字符配合使用

# ① 重复字符:x?,表示:匹配 0 个或 1 个 x (x可以换成任意字符)
# ② 重复字符:x*,表示:匹配 0 个或 1 个 或者任意多个 x (x可以换成任意字符)
# ③ 重复字符:x+,表示:匹配 至少1个 或者任意多个 x (x可以换成任意字符)
# ④ 重复字符:x{m,n},表示:匹配最少 m 个、最多 n 个 x, x{m}表示:只能有m个x, x{m,}表示:有m个x或者以上个x (x可以换成任意字符)
# ⑤ 重复字符:(xyz)+,表示:匹配至少一个(xyz),括号可以看成分组,分组里面的元素可以是任意多个字符
# ⑥ 任意一个匹配:[a-z]匹配26个小写字母任意一个,[A-Z]匹配26个大写字母任意一个,[0-9]匹配0到9的数字任意一个,[a-zA-Z0-9]匹配混合字母和数字中的任意一个
# ⑦ 任意一个不匹配:[^a-z]不匹配26个小写字母,[^A-Z]不匹配26个大写字母,[^0-9]不匹配0到9的数字,[^a-zA-Z0-9]不匹配混合字母和数字

# 3、字符类:锚字符

# ① 锚字符:^ , 表示:从行首开始匹配
# ② 锚字符:$ , 表示:从行尾开始匹配

# 4、字符:\d , 匹配数字,和字符集合 [0-9]相同,字符:\D , 匹配非数字,同[^0-9]相同

# 5、字符:\w , 匹配字母和数字及_,和字符集合 [a-zA-Z0-9_]相同,字符:\W , 匹配非字母和数字及_,同[^a-zA-Z0-9_]相同

# 二、空白字符

# ① 字符:\s,表示:匹配空白字符、空格、制表符和换行符

# ② 字符:\b,表示:到达边界

# 三、选择字符(|)选择模式,匹配如:jpg|png|gif,非相等包含的意思

# 四、分组模式:()做分组,\1或$1匹配第一个分组中的内容,\2或$2匹配第二个分组中的内容,依次类推

# ① 分组模式:()做分组

# ② $1可以获取到第一个分组内容

# ③ 小案例:$1获取到第一个分组内容,并做替换

# ④ 小案例:获取多个分组内容,进行替换

# 六、正则表达式:贪婪和惰性

# 七、正则表达式使用 exec 返回数组

# 八、捕获性分组和非捕获性分组

# 九、分组嵌套、前瞻捕获、特殊字符匹配、换行模式

# 十、书写常用正则表达式

# ① 手机号正则

# ② 邮政编码正则

# ③ 简单的电子邮件正则

# ④ 匹配图片格式

# ⑤ 删除多余空格

# ⑥ 删除首尾的空格,中间的空格不删除

# ⑦ 延伸:将11位手机号中的4-7位号码换成 *

# 章节9.Vue.js基础

# 一、课前准备:启动node服务器,引入vue.js

# 二、体验vue的数据响应式:① 配置项data中的数据响应式,及渲染到页面上的真实DOM效果、② 循环语句,事件处理体验、③ vuejs计算属性体验

# 三、理解vue的注入、虚拟DOM及底层原理:vue实例成员的注入、虚拟DOM、虚拟DOM的底层原理

# 四、案例:node.js + vue.js 渲染企业网站



# 其它学期课程

# 第一学期(学习顺序:01)

第一学期课程专为零基础的学员定制录制的,纯html+css做企业网站的网页,主讲html和css的相关基础知识,flex布局相关知识,封装css基础样式库,引入字体图标及网页开发基础布局思维,完成企业网站网页的开发过程。

[第一学期学习视频]

# 第二学期【第1季】(学习顺序:02)

主讲JavaScript的基础,建议所有学员观看。
[第1季学习文档] [第1季学习视频]

# 第二学期【第2季】(学习顺序:03)

JavaScript中的面向对象,类,ajax,封装js库过渡到jQuery, vue.js基础配置网站页面,建议所有学员观看。
[第2季学习文档] [第2季学习视频]

# 第二学期【第3季】(学习顺序:04)

egg.js基础,响应式网页布局,Bootstrap框架,响应式后台系统管理,完整企业网站前后台开发,建议所有学员观看。
[第3季学习文档] [第3季学习视频]

# 第二学期【第4季】(学习顺序:05)

主要对第三季,同学们开发的企业网站,进行一个完整的上线运维流程的一个讲解,同学们将网站开发完成之后,如何进行上线运维,将项目交付给客户。
[第4季学习文档] [第4季学习视频]

更新时间: 2024年11月19日星期二中午11点54分