NodeJs简介

NodeJs是一个JavaScript运行环境,它让JavaScript可以开发后端程序,几乎能实现其它后端语言能实现的所有功能。是一个基于 Chrome V8 引擎的 JavaScript 运行时
NodeJs擅长处理高并发。它不会为每个客户连接创建一个新的线程,而仅仅使用一个线程。当有用户连接了,就会触发一个内部事件,通过非阻塞I/O,事件驱动机制,让NodeJs程序宏观上也是并行的

关于进程和线程:
进程:负责为程序的运行提供必备的环境。相当于工厂中的车间
线程:计算机中的最小计算单位,线程负责执行进程中的程序。相当于工厂中车间的工人
JS是单线程的

NodeJS文档:http://nodejs.cn/learn

NodeJs的安装

下载地址:http://nodejs.cn/download/
验证NodeJs是否安装成功:在cmd中输入命令node -v,若显示版本号则安装成功

命令行窗口(cmd)

相关常用的命令:
dir:列出当前目录下的所有文件或子目录
cd 目录名:进入到指定的目录
md 目录名:创建一个文件夹
rd 目录名:删除一个文件夹
磁盘名::进入到对应的磁盘
cd ./:表示当前目录
cd ../:表示上一级目录
cd /:返回根文件夹

当我们在命令行窗口打开一个文件,或调用一个程序时,
系统会首先在当前目录下寻找文件程序,如果找到了则直接打开
如果没有找到,则会依次到环境变量中的path的路径中寻找
如果还是没有找到,则会报错

注:我们可以将一些经常需要访问的程序好文件的路径添加到path中,这样我们可以在任意位置来访问这些文件和程序了

运行NodeJs程序

在cmd(或者编译软件中内置的控制台)中,进入到对应的要运行的文件夹,然后输入命令:node 要执行的文件名
要运行谁,就node谁

NodeJs自启动工具

supervisor / nodemon(个人推荐) /...:能够不停地监听NodeJs的文件,进行实时修改
supervisor的安装:npm install -g supervisor
nodemon的安装:npm install -g nodemon (官网:https://nodemon.io/)

使用:
可用supervisor/nodemon来代替node命令:supervisor/nodemon 要执行的文件名

NodeJs中的一些输出命令

console.log('...') //普通输出语句
console.dir('...') //显示一个对象所有的属性和方法,完整的显示出对象(在类似json的树中打印元素)
console.error('...')  //警告输出
console.time(标识)  //计时开始
console.timeEnd(标识)  //计时结束
console.assert(表达式,输出文字)  //当表达式为假时,输出文字(抛出错误),否则不输出文字

关于npm,包

包(package):CommonJS的包规范由包结构和包描述文件两个部分组成
包结构:用于组织包中的各种文件
包结构实际上是一个压缩文件,其中包含以下的一些文件:
pachage.json:描述文件(必需的)
bin:可执行二进制文件
lib:js代码
doc:文档
test:单元测试
包描述文件:描述包的相关信息,表达非代码的相关信息,以供外部读取分析(json格式的文件--package.json)
创建package.json文件的命令:npm init(需要手动选择)/npm init -y(默认全部yes)
package.json的常用属性:
name:项目名称
version:版本号
description:项目描述
main:主模块
dependencies:依赖包列表
devDependencies:开发时的依赖
scripts:脚本(可使用npm命令执行)
license:开源协议

新版的nodejs已经集成了npm,所以安装node的同时,npm也一起安装好了
对于node而言,npm帮助其完成了第三方模块的发布,安装和依赖等

npm相关命令:
npm -v:查看npm版本
npm version:查看所有模块的版本
npm search 包名:搜索模块包
npm install 包名:在当前目录安装包
npm install 包名@版本号:在当前目录安装对应版本的包
npm install 包名 -g:全局模式安装包
npm install 包名 --save:安装包并添加到依赖中(npm install会自动下载当前项目依赖中的包)
npm install 包名 --save-dev:安装包并添加到开发时依赖列表
npm remove 包名:删除包
npm uninstall 包名:卸载包
npm view 包名:查看包
npm install 文件路径:从本地安装
npm install 包名 -registry=地址:从镜像源安装
npm config set registry 地址:设置镜像源
npm update 包/模块名:更新包/模块
npm cache clear:清空npm本地缓存

从npm安装的 包/模块 都会放到node_modules文件夹中(上传项目时不会把node_modules都上传)

node在使用模块名字来引入模块时,他会首先在当前目录的node_modules中寻找是否含有该模块,
如果有则直接使用,如果没有则直接去上一级目录的node_modules中寻找,直到找到为止,
若直到找到磁盘的根目录依然没有,则报错

有时候对于一些大的包,用npm安装下载会很慢,可以利用cnpm来安装下载
安装cnpm:npm install -g cnpm --registry=https://registry.npm.taobao.org

若在npm安装包时,总是提示npm resource busy or locked...的报错,
解决方法:npm cache clean
          npm install

npm官网:https://www.npmjs.com/

NodeJs作用域

NodeJs中一个文件就是一个模块
模块中使用var定义的变量为局部作用域,只能在该模块中使用。因为模块在使用时会把NodeJs编译为一个函数,那么使用var的定义的变量,理所当然只能在这个模块(函数)中使用
如:
在某个模块中编写了var a=100;
NodeJs在执行前,编译这个模块为:
function(exports,require,module,__filename,__dirname){
    var a=100;
}

全局对象:global.属性/方法(类似于window)
global.name='xxx'(使用时global关键字可省略不写)

NodeJs的模块化

NodeJs采用模块方式管理和组织代码,NodeJs所有的功能都存在每个模块中

一个具有特定功能的文件就是一个模块,模块间可能存在一定的依赖关系,使用模块可以很好地把这些依赖关系整合起来

模块规范:
AMD:异步模块定义,异步的加载模块,requirejs应用了这一规范,适合客户端浏览器环境
CMD:是seajs推崇的规范(相当于AMD和CommonJs的结合再加上其他东西)
CommonJs:NodeJs采用了此规范来定义模块,但其采用的是同步加载文件方式,只适用于服务端

exports导出(暴露)对象:由于nodejs是基于模块化管理,所有用户编写的代码都是局部的,要与其它模块共享数据可以使用暴露对象
module模块对象:module.exports(真正的暴露对象)
若要对外暴露属性或方法,就用exports就行(exports只能使用.的方式来向外暴露内部变量)
若要暴露对象(如class,包含了很多属性和方法),就用module.exports(module.exports可以通过.的方式,也可以直接赋值)
require函数:引入(加载)模块
require方法的内部加载十分复杂,其加载的优先级也各不相同,如下图:

image.png
image.png

NodeJs的模块分类:
自定义模块:我们自己编写的模块
第三方模块:第三方开发者贡献的模块(npm install 包名)
系统(内置)模块:NodeJs官方开发的模块
以上模块可用require函数引入:var xxx=require('xxx')(若为核心模块(系统的),则xxx为模块名,若为文件模块(用户自定义的),则xxx为相对应的模块相对路径(./不能省略))

实际上模块中的代码都是包装在一个函数中执行的,并且在函数执行时,同时传递了5个实参:
exports:该对象用来将变量或函数暴露到外部
require:函数,用来引入外部的模块
module:代表的是当前模块本身,exports是module的属性
__filename:当前模块文件的完整路径
__dirname:当前模块所在文件夹的完整路径

内置模块---HTTP模块

HTTP协议:
网络是信息传输,接收,共享的虚拟平台
网络传输数据有一定规则,即称为协议。HTTP协议就是其中一种,也是使用最频繁的一种
HTTP协议是TCP/IP协议之上的一个应用层协议,用于定义WEB浏览器与WEB服务器之间交换数据的过程及数据本身的格式。采用的是 请求/应答 方式来传递数据(一次请求对应一次应答)

HTTP模块是基于HTTP协议的通信模块,可用于创建HTTP服务器,也可作为客户端向其他服务器发起请求

引入HTTP模块:var http=require('http')
HTTP模块中的某些方法:(更多方法可查看文档)
HTTP服务端的方法:
1. http.createServer([options][, requestListener]):创建Web服务
   http.createServer((req, res) => {  //req获取url(客户端)传过来的信息,res给浏览器响应信息
    //使用此回调处理每个单独的请求
    res.writeHead(200,{"Content-type":"text/html;charset=utf-8"}); //设置响应头(状态码是200,文件类型是html,字符集是utf-8)(设置charset=utf-8可解决中文乱码问题)
    res.write('xxx'); //页面上显示xxx内容
    res.end();  //结束响应
   }).listen(监听的端口号)
   
HTTP客户端的方法:   
1. http.get(url(目标地址)[, options][, callback]) / http.get(options[, callback]): 模仿客户端从服务端获取数据(将请求方法设为GET请求)
   它将请求方法设置为 GET 并且会自动调用req.end()
   callback调用时只有一个参数:res(返回对象,接收到服务器响应的所有内容)
   var request=http.get({host:'localhost',path:'/user?name=helios&age=22',port:3000},
                        function(res){
                                    res.setEncoding('utf-8');
                                    res.on('data',function(data){
                                        console.log(' 服务端相应回来的数据为:'+data);
                                    })
                        });
2.  http.request(url(目标地址)[, options][, callback]) / http.request(options[, callback]):发起http请求
    callback调用时只有一个参数:res
    var req  =  http.request(options,(res)=>{
        res.setEncoding('utf-8');
        res.on('data',(data)=>{
            console.log(data);
        });
    });
    

内置模块---fs模块

文件系统模块,用于对操作系统中的文件进行相关操作
常见的操作有:读文件,写文件,删除文件等

引入fs模块:var fs=require('fs')

读文件:
直接读取(将硬盘上的所有内容全部读入内存后才触发回调函数)和流读取(将数据从硬盘中读取一节就触发回调函数,实现大文件操作)
直接读取:同步读取(fs.readFileSync('xxx(文件名))
         异步读取(fs.readFile('xxx(文件名)',function(err,txt){...}))

写文件:
引入fs模块后,找到要写入的内容,开始写入
异步写文件:fs.writeFile()
fs.writeFile('xxx(文件名)',data(写入的数据),function(err){...})
同步写文件:fs.writeFileSync()
fs.writeFileSync('xxx(文件名)',data(写入的数据))

删除文件:
fs.unlink('xxx(文件名)',function(err){...})
fs.unlinkSync('xxx(文件名)')

删除非空目录:
1. 读取目录中的文件及文件夹列表:fs.readdir();
2. 读取每一个文件的详细信息:fs.stat();
3. 判断如果是文件,则直接删除:fs.unlink();
4. 若如果是目录,则递归调用自己
5. 删除底下的空目录:fs.rmdir();
具体实现(同步方式):
const fs=require("fs");
function deldir(path) {
    let files = [];
    if( fs.existsSync(path) ) {  //先查看是否有含此路径的目录
        files = fs.readdirSync(path);  //读取目录中的文件及文件夹列表
        files.forEach(function(f,i){
            let curPath = path + "/" + f;
            if(fs.statSync(curPath).isFile()) {  //若是文件,则直接删除
                fs.unlinkSync(curPath);
            } else {   //若是目录则调用自身函数进行递归
                deldir(curPath);
            }
        });
        //删除空目录
        fs.rmdirSync(path);
    }
}
deldir('xxx(要删除的目录)');

读取文件信息:
fs.stat('文件名',function(err,state){
        /*
          state是文件的信息对象
          包含常用的文件信息:
          size:文体大小,字节
          mtime:文件修改时间
          birthtime:文件创建时间
          ...
          
          包含的一些方法:
          .isFile():判断当前查看的对象是不是一个文件
          .isDirectory():判断当前查看的对象是不是一个目录(文件夹)
          ...
        */
})  

检验路径是否存在:
fs.existsSync(path)

读取一个目录的目录结构:
fs.readdir(path[,options],function(err,files){...});  //files是一个字符串数组,每个元素就是一个文件夹或文件的名字
fs.readdirSync(path[,options]);

截断文件(将文件修改为指定大小):
fs.truncate(path,len,callback);
fs.truncateSync(path,len);

创建目录:
fs.mkdir(path[,mode],callback);
fs.mkdirSync(path[,mode]);

删除(空)目录:
fs.rmdir(path,callback);
fs.rmdirSync(path);

重命名文件和目录:
fs.rename(oldpath,newpath,callback);
fs.renameSync(oldpath,newpath);

监视文件更改写入:
fs.watchFile(filename[,options],listener(curr,prev));  //listener为回调函数,当文件发生变化时,回调函数会执行(参数:curr为当前文件状态,prev为修改前文件的状态)

追加文件内容:
fs.appendFile('文件名','追加的内容',(err)=>{...})

创建文件并覆盖写入内容:
fs.writeFile('文件名','内容',(err)=>{...})


流:
一组有序的,有起点和终点的字节数据的传输方式
在应用程序中各种对象之间交换与传输数据时,总是将该对象中所包含的数据转换为各种形式的流数据(字节数据),在通过流的传输,到达目的对象后再将流数据转换为该对象中可使用的数据

四种流类型:Readable(可读),Writable(可写),Duplex(可读可写),Transform(操作被写入数据,然后读出结果)
处理流事件:data(有数据可读时触发),end(没有更多的数据可读时触发),error(在接收和写入过程中发生错误时触发),finish(所有数据已被写入到底层系统时触发)

读取文件流:
var readerStream=fs.createReadStream('xxx(文件名)');  //创建可读流
readerStream.on('data',function(chunk){   //监听data事件,有数据读取出来就会触发,接收到数据
                    ...
              });
readerStream.on('end',function(){});  //监听end事件,当文件读取完毕时,触发该事件
readerStream.on('error',function(){});  //监听error事件,当文件读取出错时,触发该事件
解决从流中读取数据中文乱码的问题:加上readerStream.setEncoding("UTF8");

写入文件流:
var writerStream=fs.createWriteStream('xxx(文件名)');  //创建可写流
writerStream.write('xxxx(写入的内容)',["UTF8"]);  //写入流
writerStream.end();  //写完以后结束
writerStream.on('finish',function(){...});  //绑定一个finish事件来表明写入成功
writerStream.on('error',function(){});  //监听error事件,当文件读取出错时,触发该事件


管道(pipe):
提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另一个流中,这样可以慢慢实现大文件的复制过程(输出流.pipe(输入流))
具体实现:
//创建一个可读流
var readerStream=fs.createReadStream('xxx(文件名)'); 
//创建一个可写流
var writerStream=fs.createWriteStream('xxxx(文件名)'); 
//管道读写操作
readerStream.pipe(writerStream);


链式流:
通过连接输出流到另外一个流并创建多个流操作链的机制
链式一般用于管道操作
可用管道和链式来压缩和解压文件
压缩文件:(将xxx文件压缩为xxx.gz)
var fs=require('fs');
var zlib=require('zlib');
fs.createReadStream('xxx(文件名)').pipe(zlib.createGzip()).pipe(fs.createWriteStream('xxx.gz'));
解压文件:(将xxx.gz解压为xxx文件)
var fs=require('fs');
var zlib=require('zlib');
fs.createReadStream('xxx.gz').pipe(zlib.createGunzip()).pipe(fs.createWriteStream('xxx(文件名)'));

内置模块---path模块

可用来处理与转换文件路径
用于格式化或拼接一个完整的路径

引入path模块:var path=require('path');

path上的方法:
较不太常用的:
path.normalize(p);  //规范化路径(注意./和../)
path.join(path1,path2,...);  //用于连接路径,该方法的主要用途是会正确使用当前系统的路径分隔符(Unix系统是'/',Windows系统是'\')
path.resolve([from...],to);  //将to参数解析为绝对路径。获得相对路径的绝对路径计算(会使用第一个参数作为第二个参数的基准)
path.isAbsolute(path);  //判断path是否为绝对路径
path.relative(from,to);  //将路径转为相对路径
path.dirname(path);  //返回路径中代表文件夹的部分
path.basename(path[,ext]);  //返回路径中的最后一部分 
path.extname(path);  //返回路径中文件的后缀名(即路径中最后一个'.'之后的部分),若路径中并不包含'.'或该路径中只包含一个'.',且这个'.'为路径的第一个字符,则此命令返回空字符串
较为常用的:
path.parse(pathString);  //返回路径字符串的对象
path.format(pathObject);  //从对象中返回路径字符串

内置模块---url模块

url:全球统一资源定位符,对网站资源的一种简洁表达形式,也称为网址

引入url模块:var path=require('url');

url被解析时,会返回一个url对象,它包含每个组成部分作为属性
url的构成(从左到右):
1. 完整结构
协议://用户名:密码@主机名.名.域:端口号/目录名/文件名.扩展名?参数名1=参数值1&参数名2=参数值2&...#hash
2. http协议的URL常见结构
协议://主机名.名.域/目录名/文件名.扩展名?参数名1=参数值1&参数名2=参数值2&...#hash

1.jpg

提供了两套对于url进行处理的API功能:
1. 旧的node.js url模块
   使用传统的 API 解析 URL 字符串:
   const xxx=url.parse('https://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash');
    
2. 新的url模块(WHATWG URL标准模块)
   WHATWG的URL接口:
   使用 WHATWG 的 API 解析 URL 字符串:
   const xxx=new url.URL('https://user:pass@sub.host.com:8080/p/a/t/h?query=string#hash');
   属性:
   xxx.hash:获取及设置URL的分段(hash)部分
   xxx.host:获取及设置URL的主机(host)部分
   xxx.hostname:获取及设置URL的主机名(hostname)部分
   xxx.href:获取及设置序列化的URL
   xxx.origin:获取只读序列化的URL origin部分
   xxx.pathname:获取及设置URL的路径(path)部分
   xxx.search:获取及设置URL的序列化查询(query)部分
   

内置模块---Query String模块

用于处理query(参数)字符串
模块提供用于解析和格式化 URL 查询字符串的实用工具

引入Query String模块:var qs=require('querystring');

将query字符串变成query对象:
qs.parse(str)  //qs.decode()是此方法的别名
还可根据某个符号进行切分键值对和键值:
qs.parse(str,'先根据什么符号来切分键值对','再根据什么符号来切分键和值')
e.g.
let str='name-aa#pass-123#sex-0';
let obj=qs.parse(str,'#','-');
输出obj的结果为:{name:'aa',pass:'123',sex:'0'}

将query对象变成query字符串:
qs.stringfy(obj);  //qs.encode()是此方法的别名
还可根据某个符号进行切分键值对和键值:
qs.stringfy(str,'先根据什么符号来切分键值对','再根据什么符号来切分键和值')
 e.g.
let obj={name:'aa',pass:'123',sex:'0'}
let str=qs.stringfy(obj,'^','?');
输出str的结果为:'name?aa^pass?123^sex?0'

对URL查询字符串的特定要求进行了优化的方式,对给定的str执行URL百分比编码:
qs.escape(str);

在给定的str上执行URL百分比编码字符的解码:
qs.unescape(str);

内置模块---events模块

只提供了一个对象:events.EventEmitter
EventEmitter的核心就是事件触发与事件监听器功能的封装
所有这些产生事件的对象都是event.EventEmitter的实例

引入events模块:var events=require('events');

创建eventEmitter对象:
var eventEmitter=new events.EventEmitter();

单事件监听:
var events=require('events');
var eventEmitter=new events.EventEmitter();
eventEmitter.on('xxx(事件)',function(arg){...});
setTimeout(function(){
    eventEmitter.emit('xxx(对应上面的事件)','参数');  //要调用emit方法对应的事件才会被触发(按照监听器注册的顺序,同步地调用每个注册到名为xxx的事件的监听器,并传入提供的参数)
},1000)

多事件监听:
EventEmitter的每个事件都由一个事件名和若干个参数组成。支持若干个事件监听器
当事件触发时,注册到这个事件的事件监听器被依次调用,事件参数作为回调函数参数传递
var events=require('events');
var eventEmitter=new events.EventEmitter();
eventEmitter.on('xxx(事件)',function(arg){内容1});
 eventEmitter.on('xxx(事件)',function(arg){内容2});
setTimeout(function(){
    eventEmitter.emit('xxx(对应上面的事件)','参数');  //要调用emit方法对应的事件才会被触发(按照监听器注册的顺序,同步地调用每个注册到名为xxx的事件的监听器,并传入提供的参数)
},1000)

有关EventEmitter的一些方法:

image.png
image.png

有关EventEmitter的类方法:

image.png

有关EventEmitter的事件:

image.png

error事件:
EventEmitter定义了一个特殊事件:error,它包含错误的语义,在遇到一场时通常会触发error事件
当error触发时,EventEmitter规定如果没有响应的监听器,Nodejs会把他当做异常,退出程序并输出错误信息
ar events=require('events');
var eventEmitter=new events.EventEmitter();
eventEmitter.emit('error'); 

继承EventEmitter:
大多数情况下,我们不会直接使用EventEmitter,而是在对象中继承它
包括fs,net,http在内的,只要是支持事件响应的核心模块都是EventEmitter的子类
这样做的原因:
1. 具有某个实体功能的对象实现事件符合语义,事件的监听和发生应该是一个对象的方法
2. JavaScript的对象机制是基于原型的,支持部分多重继承,继承EventEmitter不会打乱对象原有的继承关系

第三方模块---nodemailer

一个简单易用的 Node.JS 邮件发送模块(通过 SMTP,sendmail,或者 Amazon SES),支持 unicode

nodemailer的主要特点:
支持Unicode编码
支持Window系统环境
支持HTML内容和普通文本内容
支持附件(传送大附件)
支持HTML内容中嵌入图片
支持SSL/STARTTLS安全的邮件发送
支持内置的transport方法和其他插件实现的transport方法
支持自定义插件处理消息
支持XOAUTH2登录验证

应用场景:注册案例(发送邮箱验证码)

nodemailer的官网:
https://nodemailer.com/about/

nodemailer的安装:
npm install nodemailer

nodemailer的使用:
"use strict";
const nodemailer = require("nodemailer");
//当您没有真正的邮件帐户进行测试时才需要创建测试账户
let testAccount = await nodemailer.createTestAccount();
//创建发送邮件的对象(若是大型企业的发送方,可通过此模块下的文件夹lib/well-know/service.json查找对应企业的相关信息)
let transporter = nodemailer.createTransport({
    host: "smtp.ethereal.email",  //发送方的邮箱smtp
    port: 587,  //端口号
    secure: false, // true for 465(port), false for other ports
    auth: {
  user: testAccount.user, // 发送方的邮箱地址
  pass: testAccount.pass, // mtp验证码
},
});

//邮件的相关信息
let mailObj={
    from: '"Fred Foo" <foo@example.com>', // 发送方邮箱
    to: "bar@example.com, baz@example.com", // 接收方邮箱
    subject: "Hello", // 邮件标题
    text: "Hello world?", // 邮件文本信息
    html: "<b>Hello world?</b>", // 邮件的html显示部分(和text部分两者只能选择一个)
};

//发送邮件
let info = await transporter.sendMail(mailObj,(err,data)=>{
      ...
});

在qq邮箱中设置SMTP服务:
qq邮箱首页 --> 账户 --> POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 (点击对应的SMTP服务开启即可)

image.png

缓存区Buffer

像一个数组,专门用来存储二进制数据,它的元素显示为16进制的两位数(实际上一个元素就表示内存中的一个字节(8bit))
Buffer中的内存不是通过JavaScript分配的,而是在底层通过C++申请的
可以直接通过Buffer来创建内存空间
Buffer的大小一旦确定,则不能被修改

创建一个指定大小的Buffer:
1. var buf=new Buffer(xx) / var buf=new Buffer([...])(按指定数组创建缓存区); (buffer构造函数是不推荐使用的)
2. var buf=Buffer.alloc(xx); (常用)
3. var buf=Buffer.allocUnsafe(xx);  (buffer中可能含有敏感数据)

通过索引来修改操作buf中的元素:
buf[x]=xxx

通过索引来查看buf中的元素:
buf[x]

将一个字符串str转换为buffer:
Buffer.from(str)

将一个buffer中的数据转换为字符串显示出来:
buf.toString()

写入内容到缓存区:
buf.write('xxx')

缓存区的拷贝(复制):
buf1.copy(buf2)  //buf1复制给buf2

NodeJs REPL(交互式解释器)

"读取-求值-输出"循环(Read-Eval-Print Loop),是一个简单的,交互式的编程环境
Node自带交互式解释器,可执行以下任务:
读取:读取用户输入,解析输入了Javascript数据结构并存储再内存中
执行:执行输入的数据结构
打印:输出结果
循环:循环操作以上步骤直到用户两次按下ctrl c按钮退出

在cmd中输入命令node启动Node终端,即可在>后输入简单的表达式,并按下回车来计算结果 

简单的表达式运算:
xx+xx,xx-xx,xx*xx,xx/xx,xx+(xx+xx)/xx,...

多行表达式:
如执行循环:

image.png

三个点的符号是回车换行后系统自动生成的(Node会自动检测是否为连续的表达式)

使用变量:
可以将数据存储在变量中,并在你需要的时候使用它
变量声明需要使用var关键字,如果没有使用var关键字变量会直接打印出来。使用var关键字的变量可以使用console.log()来输出变量

image.png

下划线变量:
可以使用下划线获取上一个表达式的运算结果

image.png

REPL的一些相关命令:
ctrl+c:退出当前终端
ctrl+c按下两次:退出Node REPL
ctrl+d:退出Node REPL
向上/下:查看输入的历史命令
tab键:列出当前命令
.help:列出使用命令
.break/.clear:退出多行表达式
.save filename:保存当前的Node REPL会话到指定文件
.load filename:载入当前的Node REPL会话的文件内容

NodeJs 回调函数

Nodejs异步编程的直接体现就是回调
回调函数,又称为回调。将a函数作为参数传入b函数中,b函数执行过程中根据时机或条件决定是否调用a函数,a函数就是回调函数
在执行代码时没有阻塞或等待文件I/O操作,这就大大提高了Nodejs的性能,可以处理大量的并发请求
回调函数一般作为函数的最后一个参数出现

阻塞代码:(同步)
var fs=require('fs');
var data=fs.readFileSync('xxx');
console.log(data.toString());
console.log('程序执行完毕');

非阻塞代码:(异步)
var fs=require('fs');
fs.readFile('xxx',(err,data)=>{                     
        if(err){
            return console.error(error);
        }
        console.log(data.toString())
});
console.log('程序执行完毕');

回调函数原理:
以setinterval(show,1000)为例:

image.png

回调函数的用途:
注册事件,异步函数,...
通常用于达到某个时机或条件时需要执行代码的情况,就使用回调函数


同步与异步:
同步:一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的,同步的
情景:张三去买奶茶,后面排队的是李四。
      张三:给我来一杯卡布奇诺
      服务员:没有卡布奇诺了
      张三:啊,那我打电话问一下朋友看看他还要喝啥
      此时服务员和李四都在等张三
      随后张三打完电话,就对服务员说来一杯百香果吧
      张三买完后,就轮到李四去买了
代码:console.log('1');
      console.log('2');
      for(var i=0;i<1000;i++){...}
      console.log('3');
      (先循环完后再输出3)
异步:每一个任务有一个或多个回调函数,前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则不等前一个任务结束就执行。所以程序的执行顺序与任务的排列顺序是不一致的,异步的
情景:张三去买奶茶,后面排队的是李四。
      张三:给我来一杯卡布奇诺
      服务员:没有卡布奇诺了
      张三:啊,那我打电话问一下朋友看看他还要喝啥
      此时张三到队列的旁边打电话等着
      随后就到李四买奶茶
      李四买完后,此时张三就打完电话问朋友,就对服务员说那给我来一杯百香果吧
代码:console.log('1');
      console.log('2');
      setTimeout(function(){
        for(var i=0;i<1000;i++){...}
      },1000);
      console.log('3');
     (在这1s内,先输出3,1s后再循环)
     
Nodejs中共有三种异步实现方式:
回调函数(回调函数不一定是异步,但异步一定是回调函数)
事件(基于回调):事件源.on('事件名称',回调函数)
Promise(es6):由于异步的返回结果时间顺序不可控,所以需要使用promise来统一控制输出结果


事件循环驱动
Nodejs单线程类似进入一个while(true)的事件循环,直到没有事件观察者退出,每个异步事件都生成一个事件观察者,如果有事件发生就调用该回调函数

Nodejs使用事件驱动模型,当web server接收到请求,就把它关闭然后进行处理,然后去服务下一个web请求
当这个请求完成,它被放回处理队列,当到达队列开头,这个结果被传递给用户
这个模型非常高效,可扩展性非常强,因为web server一直接受请求而不等待任何读写操作
(以上可称为非阻塞式I/O或事件驱动I/O)
在事件驱动模型中,会生成一个主循环来监听事件,当检测到事件时触发回调函数。事件相当于一个主体,而所有注册到这个事件上的处理函数相当于观察者

实现简易爬虫(http模块,...)

网络爬虫:是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本

可参考的相关步骤:
1. 发起请求获取目标网站:http.get
2. 读取并分析网站内容(html):可利用cherrio模块 cherrio模块可使用jq的选择器
3. 获取有效信息(可利用正则表达式),下载(可利用管道实现)或其他操作

image.png
有下载图片操作的:
image.png
image.png

网页模块

静态网页:随着html代码的生成,页面内容和显示效果就基本不会发生变化了,除非你修改页面代码
动态网页:页面代码虽然没有变,但显示的内容可以随着时间,环境或者数据库操作的结果再发生改变

使用动态网页模板:
模板:将一些固定的结构或表现直接以静态文件形式存储,将需要表现成动态数据的地方用模板语法进行编写,再用模板引擎读取该静态文件,将其他动态的数据替换进去。最终实现产生出一个动态的页面
为防止重复写相关的html代码,可把模板写成一个html文件,然后通过读取文件的方式读取该文件,再通过res.render函数来把相应的数据替换掉,形成一个动态修改数据的页面

关于服务器

服务器的两个概念:硬件服务器(电脑)
                 软件服务器(软件)
服务器要提供HTTP服务,就要安装一个服务器软件
服务器软件:apache,tomcat,iis,ngnix,node,...
服务器ip(确定服务器主机的位置)和端口号(确定服务器里某个程序)
访问服务器:
1. 打开浏览器
2. 输入网址:172.0.0.1(主机地址)
             localhost(当地主机,本地域名)
             局域网IP(Windows系统可通过在cmd中输入命令ipconfig查看,Linux/MAC系统可输入命令ifconfig查看)

局域网:服务器通过网线(无线)连接,每一个电脑都会有一个ip。(指在某一区域内由多台计算机互联成的计算机组)(不同局域网的IP地址可以重复,不会相互影响)
外网(广域网):是一个遍及全世界的网络。它可以连接极其大的物理范围,属于远程性的网络,已经实现了跨国互联。(是许多的计算机相互之间用线路连接形成的)(外网IP地址不能重复)

NodeJs开启服务器:(不需要软件)
1. 引入通信模块(http)
2. 创建服务器
3. 监听request事件
4. 监听端口(Windows系统可通过在cmd输入命令netstat -a查看开启了哪些端口)

express框架

express官网:https://www.expressjs.com.cn/

express特点:
可以设置中间件响应或过滤http请求
可以使用路由实现动态网页,响应不同的http请求
内置支持ejs模板(默认是jade模板)实现模板渲染生成html
自身功能极简,完全由路由和中间件构成的一个web开发框架

安装express:
npm install express --save

引入express模块:
var express = require('express')

创建express实例:
var app = express();

监听3000端口,开启服务器
app.listen(3000,()=>{  
    console.log('server start');
})


关于req(请求对象)和res(响应对象):
req:请求对象,包含了所有客户端请求的数据,请求头,请求主体
res:响应对象,包含了所有服务器端发送给客户端的数据,响应头,响应主体

req上的部分方法:
1. req.url:获取到用户请求的网址
2. req.query:获取url上用户传过来的参数(get请求的数据)
3. req.body:接收post请求的数据
4. req.params.参数名:匹配url网址上的数据(在接请求的地方去匹配,再通过语法进行接收)
   e.g. router.get('/xxx/:xx',()=>{res.send('<h1>'+req.params.xx+'</h1>')})
更多req上的方法:(详细可看express官网)

image.png

 res上的部分方法:
1. res.writeHead(状态码,响应头对象({"Content-Type":"text/html;charset=utf-8"})):设置响应头
响应头对象包含的属性有:Content-Type:响应的文件类型(未指定响应文件类型时,默认是html,编码默认是系统编码)
                      Content-Length:响应内容的长度(长度是多少就响应多少的内容)
                      Access-Control-Allow-Origin:设置跨域(值为*则为全部网站)
                      ...
                      涉及的知识点:MIME类型:可认为是文件类型的表述
                                 常见的MIME类型:.html:text/html
                                                .css:text/css
                                                .js :text/javascript
                                                .png:image/png
                                                .jpg:image/jpeg
                                                .gif:image/gif
                                                .json:text(application)/json
                                                .mp3:audio/mpeg
                                                .mp4:video/mpeg
                                                .pdf:application/pdf
                                                .xml:text/xml
                                                .txt:text/plain
                                                ...
2. res.write("xxx"):把响应的东西xxx返回到页面
3. res.end():结束响应
4. res.send('xxx'):返回任意类型(string,json,...)的数据给客户端(注:若返回的是一个数字类型的,会被当成状态码。此方法只能出现一次,重复使用会无效和报错)
5. res.status(状态码).send('xxx'):设置状态码并返回数据
6. res.json(data):返回json对象,自动设置响应头。一般针对ajax应用
7. res.render('模板名称',{数据}):读取模板文件并拼接数据,自动将结果发送给浏览器(会在模板的默认目录(views)下根据模板名称找对应的模块)
8. res.download('xxx'):下载当前目录下面的xxx文件
   res.download('xxx','yyy'):下载当前目录下面的xxx文件,并重命名为yyy
9. res.redirect('xxx'):重定向到从指定的url路径xxx
更多res上的方法:(详细可看express官网)

image.png
image.png

可通过express框架书写api
通过api接口实现数据的请求
api接口构成的要素:ip,port,pathname,methods(get,post),用户传递的数据(数据格式由后端确定)

用get方法写一个api接口:

image.png

用post方法写一个api接口:

image.png

使用指定的回调函数将HTTP GET请求路由到指定的路径:
app.get('xxx(路由路径,默认为根路径'/')',(req,res)=>{...})
(get请求的数据可直接在网页网址上获取,通过req.query的方法来获取(也可通过软件Postman来进行接口测试))

使用指定的回调函数将HTTP POST请求路由到指定的路径:
app.post('xxx(路由路径,默认为根路径'/')',(req,res)=>{...})
(验证post请求的数据不可直接通过网页网址来获取,在请求体里,通过req.body的方法来获取并用到第三方模块body-parser来进行解析传过来的表单或json或formdata数据。然后可通过软件Postman来进行接口测试)

express---路由

路由:指如何定义应用的断点(URLs)以及如何响应客户的请求(接收用户请求,处理用户数据,返回结果给用户的一套程序)(可理解为生成动态网页的程序)
后端路由的核心:URL

express获取路由实例:
var router=express.Router();

创建对应的路由:
router.请求方式(get/post/all)('/xx(请求地址)',(req,res)=>{...})

可创建一个独立的路由模块供使用:
在routers目录下创建一个独立的文件来编写对应路由:
var express=require('express');
var router=express.Router();
router.get('/xx',function(req,res){...})
module.exports=router;
然后在主文件app.js下引入该文件模块,即可引用对应的路由:
const express=require('express');
const app=express();
let userRouter=require('xxx(上面编写路由的文件路径)')
app.use('/xxx(根路径)',userRouter);  //相当于对应的路由地址为:/xxx/xx...

路由的区分:
大路由(总路由):app.js 负责接受所有请求,对请求进行分配
小路由(分路由):routes目录下的所有路由模块,只负责处理自己管理的目录下的请求

express---midelware(中间件)

中间件本身是一个函数,位于客户端和路由之间,可访问请求对象和响应对象,也可调起下一个中间件。相当于拦截器
一般被命名为next的变量
next():执行下一个任务,若不调用则整个请求响应就会中止不会再往后执行

内置中间件:static(静态资源目录):指定一个目录,该目录可被访问(等同于apache的www)
app.use('/xxx(可加可不加)',express.static('目录的绝对路径'))  

自定义中间件:(要注意next())
全局定义:
app.use('/(若是'/'表示根路径可默认不写)',(req,res,next)=>{
    console.log('中间件');
    let {a}=req.query;
    if(a){
        next();  //是否往下继续执行
    }
    else{
        res.send('缺少a');
    }
})
局部定义:
app.get('/user/get',(req,res,next)=>{...},
(req,res)=>{...}
})

第三方中间件(body-parser(解析数据的中间件),multer(上传文件的中间件)等)
应用级中间件绑定到app对象,使用app.use()和app.METHOD(),其中METHOD是需要处理的HTTP请求的方法

定义错误处理中间件:
需要4个参数
app.use((err,req,res,next)=>{
    console.error(err.stack);
    res.status(500).send('xxx');
})

express---模板引擎

views放模板的文件目录:app.set('views','./views');
view engine模板引擎:app.set('view engine','jade/ejs');

ejs模板的部分语法:
<% code %>:用于执行代码
<%= string %>:会进行html转义
<%- string %>:不会转义,原样输出
...
(ejs官网:https://ejs.bootcss.com/#promo)

express---集成数据库

要为express应用添加连接数据库的能力,只需要加载相应数据库的Node.js驱动即可
可连接的数据库有:Cassandra,mysql,sql server,mongodb,redis等(详细用法可查看官网)
e.g. 连接mysql数据库:

image.png

express---会话管理Session

会话是一种持久的网络协议,用于完成服务器和客户端间的一些交互行为(可进行身份验证...)
一个会话可能包含多次连接,每次连接都被认为是会话的一次操作
HTTP协议是无状态的,本身不支持会话,为了在无状态的HTTP协议上实现会话,就有了Cookie(保存在客户端浏览器中),Session(保存在服务器中)等来存储相关的信息
如果说Cookie机制是通过检查客户身上的"通行证"来确定客户身份的话,那么Session机制就是通过检查服务器上的"客户明细表"来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了
cookie-parser模块和express-session模块配合使用

Cookie和Session的区别:

image.png

安装cookie-parser:npm i cookie-parser

安装Session:npm i express-session

cookie-parser模块和express-session模块同时引入:
const cookieParser=require("cookie-parser");
const session=require("express-session");

Session的配置:app.use(session({option}))
option的主要参数:
name:cookie的名字(原属性名为 key)(默认:’connect.sid’)
store:session存储实例
secret:用它来对session cookie签名,防止篡改
cookie:session cookie设置 (默认:{ path: '/',httpOnly:true,secure:false, maxAge:` `null` `})
genid:生成新session ID的函数 (默认使用uid2库)
rolling:在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
resave:强制保存session即使它并没有变化 (默认:true, 建议设为:false)
proxy:当设置了secure cookies(通过'x-forwarded-proto' header )时信任反向代理。当设定为true时,'x-forwarded-proto' header 将被使用。当设定为false时,所有headers将被忽略。当该属性没有被设定时,将使用Express的trust proxy
saveUninitialized:强制将未初始化的session存储。当新建了一个session且未设定属性或值时,它就处于未初始化状态。在设定一个cookie前,这对于登陆验证,减轻服务端存储压力,权限控制是有帮助的(默认:true)
unset:控制req.session是否取消(例如通过 delete,或者将它的值设置为null)。这可以使session保持存储状态但忽略修改或删除的请求(默认:keep)

设置session:req.session.属性名=属性值

获取session:req.session.属性名

重新设置cookie的过期时间:req.session.cookie.maxAge=xxx

销毁session:req.session.destroy(function(err){...})  (当检测到客户端关闭时调用)

刷新session:req.session.reload()

初始化已有session:req.session.regenerate()

保存session:req.session.save()

Session可以和MongoDB等数据库结合来做持久化的操作:

image.png

express---闪存模块

connect-flash(flash)是一个在session中用于存储信息的特定区域
信息写入flash,下一次显示完毕后即被清除(只显示一次)
典型的应用是结合重定向的功能,确保信息是提供给下一个被渲染的页面

安装connect-flash模块:var flash=require('connect-flash')

在app.js文件下编写,Express使用这个插: app.use(flash());

应用:

image.png

express---express-generator生成器

可以快速生成一个基本的express开发框架
安装:
npm install express-generator
创建项目:
express -e 项目名称  (自动创建项目目录)(-e为ejs模板(推荐),若不加则为jade模板)
express -e (手动创建项目目录)
安装依赖:
npm i
开启项目的方式:
node app(推荐使用,要先在app.js的文件下编写监听相应的端口:app.listen(端口号,callback))
npm start(自动查找当前目录下的package.json文件,找到start对应的命令进行执行)
node ./bin/www
测试项目:
打开浏览器,输入对应的网址

express项目生成的文件夹/文件说明:
bin:可执行文件目录
node_modules:依赖包目录
public:静态文件根目录(放置所有静态文件:静态html,css,js,图片,字体,视频等)
routers:路由模块目录,动态文件的目录(会优先找静态文件,若没有静态文件则找动态路由,若动态路由也没有,则404)
views:视图目录(存放所有的页面模板)
app.js:项目的主文件,对整个项目的所有资源进行统筹的安排
package.json:项目描述文件,声明项目的名称,版本,依赖等信息

image.png

以上是对node的部分整理和总结,希望有用,有什么建议欢迎提出哦!
大家一起进步~


Tap? taq
19 声望1 粉丝

想跟上前端大佬步伐的小虫...


« 上一篇
关于Vue
下一篇 »
关于MongoDB