Node.js基础知识

7/10/2021 Nodejs

# 1-node环境和浏览器区别

/*
内置对象不同
浏览器环境中提供了window全局对象
NodeJS环境中的全局对象不叫window,叫global
 */
console.log(global);
/*
this默认指向不同
浏览器this默认指向window
Nodejs环境默认指向空对象{}
Node禁止函数函数this指向global
 */
console.log(this); // {}
/*
Node中没有DOM BOM相关的api
 */



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# function

//1.Node中任何一个模块(js) 都被外层函数所包裹
function (exports, require, module, __filename, __dirname) {
    exports:用于支持CommonJs模块化规范的暴露语法
    require:用于支持CommonJs模块化规范的引入语法
    module:用于支持CommonJs模块化规范的暴露语法
    __filename:当前文件的绝对路径
    __dirname:当前文件夹的绝对路径
}
设计外层函数作用
    1.用于支持模块化语法
    2.隐藏服务器内部实现
1
2
3
4
5
6
7
8
9
10
11

# Node-模块 exports/require

NodeJS采用CommonJS规范现了模块系统  
CommonJS规范规定了如何定义一个模块,如何暴露(导出)模块中的变量函数,以及如何使用定义好的模块
在CommonJS规范中一个文件就是一个模块
在CommonJS规范中每个文件中的变量函数都是私有的,对其他文件不可见的
在CommonJS规范中每个文件中的变量函数必须通过exports暴露(导出)之后其它文件才可以使用
在CommonJS规范中想要使用其它文件暴露的变量函数必须通过require()导入模块才可以使用

let name = "feifan";
function sum(a,b) {
    return a + b;
}
//exports.xxx = xxx 暴露给外界
exports.str = name;
exports.sum = sum;
//方法2
module.exports.name = name;

//方法3 
global.sum = sum;

//require("路径") //导入模块
let aModule = require("./06-a");
console.log(aModule.str);
console.log(aModule.sum(9, 8));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Buffer对象

1.什么是Buffer?
*    1.它是一个【类似于数组】的对象,用于存储数据(存储的是二进制数据)。
*    2.Buffer的效率很高,存储和读取很快,它是直接对计算机的内存进行操作。
*    3.Buffer的大小一旦确定了,不可修改。
*    4.每个元素占用内存的大小为1字节。
*    5.Buffer是Node中的非常核心的模块,无需下载、无需引入,直接即可使用

Buffer是NodeJS全局对象上的一个类,是一个专门用于存储字节数据的类Node
JS提供了操作计算机底层API,而计算机底层只能识别01,所以就提供了一个专门用于存储字节数据的类

2.如何创建一个Buffer对象
2.1创建一个指定大小的Buffer
Buffer.alloc(size[, fill[, encoding]])
//size 新 Buffer 所需的长度。
//fill 用于预填充新 Buffer 的值。 默认值: 0。
//encoding  如果 fill 是字符串,则这就是它的编码。 默认值: 'utf8'。

2.2根据数组/字符串创建一个Buffer对象
Buffer.from(string[, encoding])
//string 要编码的字符串。encoding 默认值: 'utf8'。
 */
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Buffer常用实例方法

/*
1.将二进制数据转换成字符串
返回: <string> 转换后的字符串数据。
buf.toString();

 2.往Buffer中写入数据
 buf.write(string[, offset[, length]][, encoding])

 string <string> 要写入 buf 的字符串。
 offset <integer> 开始写入 string 之前要跳过的字节数。默认值: 0。
 length <integer> 要写入的字节数。默认值: buf.length - offset。
 encoding <string> string 的字符编码。默认值: 'utf8'。
 返回: <integer> 已写入的字节数。



  3.从指定位置截取新Buffer
  buf.slice([start[, end]])
  start <integer> 新 Buffer 开始的位置。默认值: 0。
  end <integer> 新 Buffer 结束的位置(不包含)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# Buffer常用静态方法

1.检查是否支持某种编码格式
Buffer.isEncoding(encoding)

2.检查是否是Buffer类型对象
Buffer.isBuffer(obj)

3.获取Buffer实际字节长度
Buffer.byteLength(string[, encoding])
注意点: 一个汉字占用三个字节

4.合并Buffer中的数据
Buffer.concat(list[, totalLength])
1
2
3
4
5
6
7
8
9
10
11
12

# 路径Patn

1.路径模块(path)
封装了各种路径相关的操作
和Buffer一样,NodeJS中的路径也是一个特殊的模块
不同的是Buffer模块已经添加到Global上了, 所以不需要手动导入
而Path模块没有添加到Global上, 所以使用时需要手动导入

2.获取路径的最后一部分
path.basename(path[, ext])

3.获取路径
path.dirname(path)

4.获取扩展名称
path.extname(path)

5.判断是否是绝对路径
path.isAbsolute(path)

6.获取当前操作系统路径分隔符
path.delimiter  (windows中使用; linux中使用:)

7.获取当前路径环境变量分隔符
path.sep (windows是\ Linux是/1.路径的格式化处理
// path.parse()  string->obj
// path.format() obj->string

2.拼接路径
path.join([...paths])

3.规范化路径
path.normalize(path)

4.计算相对路径
path.relative(from, to)

5.解析路径
path.resolve([...paths])

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

# npm包管理器

  1.什么是包?
      我们电脑上的文件夹,包含了某些特定的文件,符合了某些特定的结构,就是一个包。

  2.一个标准的包,应该包含哪些内容?
      1)   package.json ------- 描述文件(包的 “说明书”,必须要有!!!)
      2)   bin -----------------可执行二进制文件
      3)   lib ---------------- 经过编译后的js代码
      4)   doc    ---------------- 文档(说明文档、bug修复文档、版本变更记录文档)
      5)   test --------------- 一些测试报告

  3.如何让一个普通文件夹变成一个包?
        让这个文件夹拥有一个:package.json文件即可,package.json里面的内容要合法。
        执行命令:npm init
        包名的要求:不能有中文、不能有大写字母、同时尽量不要以数字开头、不能与npm仓库上其他包同名。

  4.npm与node的关系?(npm:node package manager)
        安装node后自动安装npm(npm是node官方出的包管理器,专门用于管理包)

  5.npm的常用命令?

      一、【搜索】:
            1.npm search xxxxx
            2.通过网址搜索:www.npmjs.com

      二、【安装】:(安装之前必须保证文件夹内有package.json,且里面的内容格式合法)

            1.npm install xxxxx --save   或   npm i xxxx -S   或   npm i xxxx
                备注:
                (1).局部安装完的第三方包,放在当前目录中node_modules这个文件夹里
                (2).安装完毕会自动产生一个package-lock.json(npm版本在5以后才有),里面缓存的是每个下载过的包的地址,目的是下次安装时速度快一些。
                (3).当安装完一个包,该包的名字会自动写入到package.json中的【dependencies(生产依赖)】里。npm5及之前版本要加上--save后缀才可以。

            2.npm install xxxxx --save-dev  或  npm i xxxxx -D  安装包并将该包写入到【devDependencies(开发依赖中)】
              备注:什么是生产依赖与开发依赖?

                    1.只在开发时(写代码时)时才用到的库,就是开发依赖 ----- 例如:语法检查、压缩代码、扩展css前缀的包。
                    2.在生产环境中(项目上线)不可缺少的,就是生产依赖 ------ 例如:jquery、bootStrap等等。
                    3.注意:某些包即属于开发依赖,又属于生产依赖 -------例如:jquery。

            3.npm i xxxx -g  全局安装xxxx包(一般来说,带有指令集的包要进行全局安装,例如:browserify、babel等)
              全局安装的包,其指令到处可用,如果该包不带有指令,就无需全局安装。
              查看全局安装的位置:npm root -g

             npm view xxx versions 查看有那些版本
            4.npm i xxx@yyy :安装xxx包的yyy版本

            5.npm i :安装package.json中声明的所有包

      三、【移除】:
            npm remove xxxxx  在node_module中删除xxxx包,同时会删除该包在package.json中的声明

      四、【其他命令】:
            1.npm aduit fix :检测项目依赖中的一些问题,并且尝试着修复。

            2.npm view xxxxx versions :查看远程npm仓库中xxxx包的所有版本信息

            3.npm view xxxxx version :查看npm仓库中xxxx包的最新版本

            4.npm ls xxxx :查看我们所安装的xxxx包的版本

       五、【关于版本号的说明】:
            "^3.x.x" :锁定大版本,以后安装包的时候,保证包是3.x.x版本,x默认取最新的。
            "~3.1.x" :锁定小版本,以后安装包的时候,保证包是3.1.x版本,x默认取最新的。
            "3.1.1" :锁定完整版本,以后安装包的时候,保证包必须是3.1.1版本。
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

# mongooDB-mongoose连接数据库

/*
* mongoDB:一个数据库品牌的名字
* mongod:启动数据库的命令
* mongo:连接接数据库的命令
* mongoose:在Node平台下,一个知名的用于帮助开发者连接mongoDB的包
* */
//为什么用mongoose? 想在Node平台下,更加简单、高效、安全、稳定的操作mongoDB
//当引入第三方库的时候,如果在本文件夹内没有找到node_modules,找外层文件夹,直到根目录

//引入mongoose
let mongoose = require('mongoose');

//1.连接数据库
mongoose.connect('mongodb://localhost:27017/test')

//2.绑定数据库连接的监听
mongoose.connection.on('open', function (err) {
    if (err) {
        console.log("连接失败", err);
    } else {
        console.log("连接成功");
    }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# mogooseCRUD

 -Create

      模型对象.create(文档对象,回调函数)
 -Read

      模型对象.find(查询条件[,投影],回调函数)不管有没有数据,都返回一个数组
      模型对象.findOne(查询条件[,投影],回调函数)找到了返回一个对象,没找到返回null
 -Update

     模型对象.updateOne(查询条件,要更新的内容[,配置对象],回调函数)
     模型对象.updateMany(查询条件,要更新的内容[,配置对象],回调函数)
     备注:存在update方法,但是即将废弃,查询条件匹配到多个时,依然只修改一个,强烈建议用updateOne或updateMany
 -Delete

      模型对象.deleteOne(查询条件,回调函数)
      模型对象.deleteMany(查询条件,回调函数)
      备注:没有delete方法,会报错!
      
备注: 以上所有方法,如果没有指定回调函数,则返回值是一个Promise的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Node文件操作-写入

# 简单文件操作

/*
*1. Node中的文件系统:
*     1.在NodeJs中有一个文件系统,所谓的文件系统,就是对计算机中的文件进行增删改查等操作。
*     2.在NodeJs中,给我们提供了一个模块,叫做fs模块(文件系统),专门用于操作文件。
*     3.fs模块是Node的核心模块,使用的时候,无需下载,直接引入。
*
*2.异步文件写入 (简单文件写入)
*   fs.writeFile(file, data[, options], callback)
*           --file:要写入的文件路径+文件名+后缀
*           --data:要写入的数据
*           --options:配置对象(可选参数)
*                 --encoding:设置文件的编码方式,默认值:utf8(万国码)
*                 --mode:设置文件的操作权限,默认值是:0o666 = 0o222 + 0o444
*                      --0o111:文件可被执行的权限  .exe .msc 几乎不用,linux有自己一套操作方法。
*                      --0o222:文件可被写入的权限
*                      --0o444:文件可别读取的权限
*                 --flag:打开文件要执行的操作,默认值是'w'
*                      --a :追加
*                      --w :写入
*           --callback:回调函数
*                 --err:错误对象
*
*  在Node中有这样一个原则:错误优先
*/

let fs = require('fs')

fs.writeFile(__dirname + '/fs.txt', ',2.0', {mode: 0o666, flag: 'a'}, err => {
    if (err) console.log('文件写入失败', err)
    else console.log('文件写入成功')
})
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

# 流式文件写入

/*
* 创建一个可写流
*   fs.createWriteStream(path[, options])
*       --path:要写入文件的路径+文件名+文件后缀
*       --options:配置对象(可选参数)
*           --flags:
*           --encoding :
*           --fd : 文件统一标识符,linux下文件标识符
*           --mode :
*           --autoClose : 自动关闭 --- 文件,默认值:true
*           --emitClose : 关闭 --- 文件,默认值:false
*           --start : 写入文件的起始位置(偏移量)
* */



let fs = require('fs')

//创建一个可写流
let ws = fs.createWriteStream(__dirname+'/demo.txt')

//只要用到了流,就必须监测流的状态
ws.on('open',function () {
    console.log('可写流打开了')
})
ws.on('close',function () {
    console.log('可写流关闭了')
})

//使用可写流写入数据
ws.write('1号\n')
ws.write('2号?\n')
ws.write('到底是哪一个?\n')
//ws.close() //如果在Node的8版本中,使用此方法关闭流会造成数据丢失
ws.end() //在Node的8版本中,要用end方法关闭流

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

# 追加写入

let fs = require('fs');
let push = require('path');

let res = push.join(__dirname, "data.txt");

fs.appendFile(res, " 新加内容3.0", 'utf8', (err) => {
    if (err) throw new Error("失败");
    console.log("新增成功");
});

fs.appendFileSync(res, "666", 'utf8');
1
2
3
4
5
6
7
8
9
10
11

# Node文件操作-读取

# 简单文件读取

/*
*  fs.readFile(path[, options], callback)
*     --path:要读取文件的路径+文件名+后缀
*     --options:配置对象(可选)
*     --callback:回调
*         --err:错误对象
*         --data:读取出来的数据
* */

//简单文件写入和简单文件读取,都是一次性把所有要读取或要写入的内容加到内存中红,容易造成内存泄露。

let fs = require('fs')

fs.readFile(__dirname+'/test.mp4',function (err,data) {
    if(err) console.log(err)
    //为什么读取出来的东西是Buffer? 用户存储的不一定是纯文本
    else console.log(data)
    fs.writeFile('../haha.mp4',data,function (err) {
      if(err) console.log(err)
      else console.log('文件写入成功')
    })

})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 流式文件读取

/*
* fs.createReadStream(path[, options])
*     --path:尧都区的文件路径+文件名+后缀
*     --options:
*         --flags
*         --encoding
*         --fd
*         --mode
*         --autoClose
*         --emitClose
*         --start :起始偏移量
*         --end : 结束偏移量
*         --highWaterMark:每次读取数据的大小,默认值是64 * 1024
* */

let {createReadStream,createWriteStream} = require('fs')

//创建一个可读流
let rs = createReadStream(__dirname+'/music.mp3',{
  highWaterMark:10 * 1024 * 1024,
  //start:60000,
  //end:120000
})

//创建一个可写流
let ws = createWriteStream('../haha.mp3')

//只要用到了流,就必须监测流的状态
rs.on('open',function () {
  console.log('可读流打开了')
})
rs.on('close',function () {
  console.log('可读流关闭了')
  ws.close()
})
ws.on('open',function () {
  console.log('可写流打开了')
})
ws.on('close',function () {
  console.log('可写流关闭了')
})

//给可读流绑定一个data事件,就会触发可读流自动读取内容。
rs.on('data',function (data) {
  //Buffer实例的length属性,是表示该Buffer实例占用内存空间的大小
  console.log(data.length) //输出的是65536,每次读取64KB的内容
  ws.write(data)
  //ws.close() //若在此处关闭流,会写入一次,后续数据丢失
})
//ws.close() //若在此处关闭流,导致无法写入数据
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

# Node文件操作-大文件操作

前面讲解的关于文件写入和读取操作都是一次性将数据读入内存或者一次性写入到文件中
但是如果数据比较大, 直接将所有数据都读到内存中会导致计算机内存爆炸,卡顿,死机等
所以对于比较大的文件我们需要分批读取和写入

et fs = require('fs');
let res = require('path');

// let str = res.join(__dirname, 'data.txt');
/*
//创建一个读取流
//默认读取64kb数据
let readStream = fs.createReadStream(str, {encoding: 'utf8', highWaterMark: 1});

//添加事件监听
readStream.on("open", () => {
    console.log("数据流和文件建立关系");
});
readStream.on("error", () => {
    console.log("数据流和文件建立关系失败");
});
readStream.on("data", (data) => {
    console.log("通过读取流从文件中读取到了数据", data);
});
readStream.on("close", () => {
    console.log("数据流断开了和文件关系,并且读取到了数据");
});
*/

/*
let writeStream = fs.createWriteStream(str, {encoding: 'utf8'});

writeStream.on("open", () => {
    console.log("写入流和文件建立关系");
});
writeStream.on("error", () => {
    console.log("写入流和文件建立关系失败");
});

writeStream.on("close", () => {
    console.log("写入流断开了和文件关系");
});

let data = '963852741';
let index = 0;
let timeId = setInterval(() => {
    let ch = data[index];
    index++;
    writeStream.write(ch);
    console.log("写入了", ch);
    if (index === data.length) {
        clearInterval(timeId)
        writeStream.end()
    }
    ;

}, 1000);
*/

let readPath = res.join(__dirname, "test.jpg");
let writePath = res.join(__dirname, "aaa.jpg");

/*
let readTest = fs.createReadStream(readPath);
readTest.on("open", () => {
    console.log("数据流和文件建立关系");
});
readTest.on("error", () => {
    console.log("数据流和文件建立关系失败");
});
readTest.on("data", (data) => {
    // console.log("通过读取流从文件中读取到了数据", readTest);
    //写入数据
    writeStream.write(data);
});
readTest.on("close", () => {
    console.log("数据流断开了和文件关系,并且读取到了数据");
    //断开写入数据流
    writeStream.end();
});

let writeStream = fs.createWriteStream(writePath);

writeStream.on("open", () => {
    console.log("写入流和文件建立关系");
});
writeStream.on("error", () => {
    console.log("写入流和文件建立关系失败");
});

writeStream.on("close", () => {
    console.log("写入流断开了和文件关系");
});
*/

let readStream = fs.createReadStream(readPath);
let writeStream = fs.createWriteStream(writePath);

//利用读取流的管道方法来快速实现文件拷贝
readStream.pipe(writeStream);
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

Node文件操作-目录操作

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

2、读取目录
fs.readdir(path[, options], callback)
fs.readdirSync(path[, options])

3、删除目录
fs.rmdir(path, callback)
fs.rmdirSync(path)

---------------------------------

let fs = require('fs');
let path = require('path');
let res = path.join(__dirname, '创建新目录');
fs.mkdir(res, (err) => {
    if (err) {
        throw new Error('创建失败');
    } else {
        console.log('创建成功');
    }
});

fs.rmdir(res, (err) => {
    if (err) {
        throw new Error('删除失败');
    } else {
        console.log('删除成功');
    }
});
fs.readdir(__dirname, function (err, files) {
    if (err) {
        throw new Error("读取失败")
    } else {
        files.forEach(function (obj) {
            let filePath = path.join(__dirname, obj);
            let stats = fs.statSync(filePath);
            if (stats.isFile()){
                console.log("是一个文件",obj);
            }else if (stats.isDirectory()){
                console.log("是一个目录",obj);
            }
        })
    }
});

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

# express服务器

let express = require('express')

let app = express()

app.get('/',function(request,response){
response.send('主页')
})

app.listen(3000,function(err){
if(!err) console.log('服务器启动成功')
else console.log(err)
})
1
2
3
4
5
6
7
8
9
10
11
12
let express = require('express');
//创建app服务对象
let app = express()
//禁止服务器返回X-Powered-By
app.disable('x-powered-by')

//配置路由 对请求的urL进行分类,服务器根据分类决定交给谁去处理。
// 路由可以理解为:一组组key-value的组合,key:请求方式+URI路径﹐value:回调函数

//根路由
app.get('/', function (request, response) {
    /*
    请求方式 get    uri 必须为 /xxx
     */
    console.log(request.query)
    response.send('来到主页')
})
//一级路由
app.get('/haixin', function (request, response) {
    response.send('海星')
})

app.post('/', function (request, response) {
    response.send('我是post请求');
})

//指定端口号
app.listen(3000, err => {
    if (!err) console.log('服务器启动成功')
    else console.log(err)
})
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

# HTTP状态码

###Http状态码(服务器给客户端的东西)

###作用:  
* 告诉客户端,当前服务器处理请求的结果
    
###http状态码的分类
 * 1xx : 服务器已经收到了本次请求,但是还需要进一步的处理才可以。
 * 2xx : 服务器已经收到了本次请求,且已经分析、处理等........最终处理完毕!
 * 3xx : 服务器已经接收到了请求,还需要其他的资源,或者重定向到其他位置,甚至交给其他服务器处理。
 * 4xx :一般指请求的参数或者地址有错误, 出现了服务器无法理解的请求(一般是前端的锅)。
 * 5xx :服务器内部错误(不是因为请求地址或者请求参数不当造成的),无法响应用户请求(一般是后端人员的锅)。
 
###常见的几个状态码
 * 200 :成功(最理想状态)
 * 301 :重定向,被请求的旧资源永久移除了(不可以访问了),将会跳转到一个新资源,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址;
 * 302 :重定向,被请求的旧资源还在(仍然可以访问),但会临时跳转到一个新资源,搜索引擎会抓取新的内容而保存旧的网址。
 * 304 :请求资源重定向到缓存中(命中了协商缓存)。
 * 404 :资源未找到,一般是客户端请求了不存在的资源。
 * 500 :服务器收到了请求,但是服务器内部产生了错误。
 * 502 :连接服务器失败(服务器在处理一个请求的时候,或许需要其他的服务器配合,但是联系不上其他的服务器了)。
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Node原生服务器

//1.引入Node内置的http模块
let http = require('http')
//引入一个内置模块,用于解析key=value&key=value.....这种形式的字符串为js中的对象
/*
备注:
  1.key=value&key=value.....的编码形式:urlencoded编码形式。
  2.请求地址里携带urlencoded编码形式的参数,叫做:查询字符串参数(query参数)。
* */
//引入的qs是一个对象,该对象身上有着很多有用的方法,最具代表性的:parse()
let qs = require('querystring')

//2.创建服务对象
let server = http.createServer(function (request,response) {
    /*
    * (1).request:请求对象,里面包含着客户端给服务器的“东西”
    * (2).response:响应对象,里面包含着服务器要返回给客户端的“东西”
    * */
    //获取客户端携带过来的urlencoded编码形式的参数
    let params = request.url.split('?')[1] //name=zhangsan&age=18
    //console.log(params)
    let objParams = qs.parse(params) //
    //console.log(objParams)
    let {name,age} =  objParams

    response.setHeader('content-type','text/html;charset=utf-8')
    response.end(`<h1>你好${name},你的年龄是${age}</h1>`)
})

//3.指定服务器运行的端口号(绑定端口监听)
server.listen(3000,function (err) {
    if (!err) console.log('服务器启动成功了')
    else console.log(err)
})



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

# 中间件

/*
 中间件:
     概念:本质上就是一个函数,包含三个参数:request、response、next

 作用:
        1) 执行任何代码。
        2) 修改请求对象、响应对象。
        3) 终结请求-响应循环。(让一次请求得到响应)
        4) 调用堆栈中的下一个中间件或路由。
  分类:
        1) 应用(全局)级中间件(过滤非法的请求,例如防盗链)
              --第一种写法:app.use((request,response,next)=>{})
              --第二种写法:使用函数定义
        2) 第三方中间件,即:不是Node内置的,也不是express内置的(通过npm下载的中间件,例如body-parser)
              --app.use(bodyParser.urlencoded({extended:true}))
        3) 内置中间件(express内部封装好的中间件)
              --app.use(express.urlencoded({extended:true}))
              --app.use(express.static('public')) //暴露静态资源
        4) 路由器中间件 (Router)

   备注:
        1.在express中,定义路由和中间件的时候,根据定义的顺序(代码的顺序),将定义的每一个中间件或路由,
        放在一个类似于数组的容器中,当请求过来的时候,依次从容器中取出中间件和路由,进行匹配,如果匹配
        成功,交由该路由或中间件处理,如果全局中间件写在了最开始的位置,那么他就是请求的“第一扇门”。
        2.对于服务器来说,一次请求,只有一个请求对象,和一个响应对象,其他任何的request和response都是对二者的引用。
 */

const express = require('express')
//引入body-parser,用于解析post参数
//const bodyParser = require('body-parser')
const app = express()

//【第一种】使用应用级(全局)中间件------所有请求的第一扇门-------所有请求都要经过某些处理的时候用此种写法
/*app.use((request,response, )=>{
  //response.send('我是中间件给你的响应')
  //response.test = 1 //修改请求对象
  //图片防盗链
  if(request.get('Referer')){
    let miniReferer = request.get('Referer').split('/')[2]
    if(miniReferer === 'localhost:63347'){
      next()
    }else{
      //发生了盗链
      response.sendFile(__dirname+'/public/err.png')
    }
  }else{
    next()
  }
  //next()
})*/

//【第二种】使用全局中间件的方式------更加灵活,不是第一扇门,可以在任何需要的地方使用。
function guardPic(request,response,next) {
  //防盗链
  if(request.get('Referer')){
    let miniReferer = request.get('Referer').split('/')[2]
    if(miniReferer === 'localhost:63347'){
      next()
    }else{
      //发生了盗链
      response.sendFile(__dirname+'/public/err.png')
    }
  }else{
    next()
  }
}

//使用第三方中间件bodyParser

//解析post请求请求体中所携带的urlencoded编码形式的参数为一个对象,随后挂载到request对象上
//app.use(bodyParser.urlencoded({extended: true}))

//解析post请求请求体中所携带的urlencoded编码形式的参数为一个对象,随后挂载到request对象上
app.use(express.urlencoded({extended: true}))

//使用内置中间件去暴露静态资源 ---- 一次性把你所指定的文件夹内的资源全部交出去。
app.use(express.static(__dirname+'/public'))

app.get('/',(request,response)=>{
    console.log(request.demo)
    response.send('ok')
})

app.get('/demo',(request,response)=>{
    console.log(request.demo)
    console.log(request.query)
    response.send('ok2')
})

app.get('/picture',guardPic,(request,response)=>{
  response.sendFile(__dirname+'/public/demo.jpg')
})

app.post('/test',(request,response)=>{
  console.log(request.body)
  response.send('ok')
})



app.listen(3000,function (err) {
  if(!err) console.log('ok')
  else console.log(err)
})
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

# 路由

let express = require('express')

let app = express()

//request和response都有什么API?
/*
  1.request对象:
      request.query    获取查询字符串参数(query参数),拿到的是一个对象
      request.params 获取get请求参数路由的参数,拿到的是一个对象
      request.body 获取post请求体参数,拿到的是一个对象(不可以直接用,要借助一个中间件)
      request.get(xxxx)    获取请求头中指定key对应的value。
  2.response对象:
      response.send()  给浏览器做出一个响应
      response.end()   给浏览器做出一个响应(不会自动追加响应头)
      response.download()  告诉浏览器下载一个文件,可以传递相对路径
      response.sendFile()  给浏览器发送一个文件 备注:必须传递绝对路径
      response.redirect()  重定向到一个新的地址(url)
      response.set(key,value)  自定义响应头内容
      response.get(key)    获取响应头指定key对应的value  很少使用
      response.status(code)    设置响应状态码
 */

//1.过滤掉非法请求、带有攻击性的请求
//2.在匹配路由之前批量处理request
//3.防盗链

//根路由
app.get('/',function (request,response) {
  //.log(request.query)
  //console.log(request.get('Host'))
  //console.log(request.get('Referer'))
  response.send('250') //send方法里不能传入纯数字,express会当成状态码
})

//根路由
app.post('/',function (request,response) {
  console.log(request.body)
  response.send('我是根路由返回的数据--post')
})

//一级路由
app.get('/demo',function (request,response) {
  /*
  * 什么叫做服务器给浏览器响应了?
  *   1.服务器给浏览器一段文字
  *   2.服务器给了浏览器一个图片
  *   3.服务器给了浏览器一个视频
  *   4.服务器告诉浏览器下载一个文件
  *   5.服务器告诉浏览器重定向
  *   备注:多个响应以response.send为主
  * */
  //response.download('./public/vue.png')
  //response.sendFile(__dirname+'/public/demo.zip')
  //response.redirect('https://www.baidu.com')
  //response.redirect('/demo/test')
  //response.set('demo','0719')
  //response.status(200)
  response.send('我是demo路由返回的数据')
  //console.log(response.get('demo'));
  //response.send('等等我还有点东西') //会抛一个异常,不能send两次
})

//二级路由
app.get('/demo/test',function (request,response) {
  response.send('我是demo/test路由返回的数据')
})

//参数路由---可以动态接收参数
app.get('/meishi/:id',function (request,response) {
  console.log(request.params);
  let {id} = request.params
  response.send(`我是变化为${id}的商家`)
})



app.listen(3000,function (err) {
  if(!err) console.log('ok')
  else console.log(err)
})
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

# Cookie


* 关于cookie:
*     1.是什么?
*         本质就是一个【字符串】,里面包含着浏览器和服务器沟通的信息(交互时产生的信息)。
*         存储的形式以:【key-value】的形式存储。
*         浏览器会自动携带该网站的cookie,只要是该网站下的cookie,全部携带。
*     2.分类:
*           --会话cookie(关闭浏览器后,会话cookie会自动消失,会话cookie存储在浏览器运行的那块【内存】上)。
*           --持久化cookie:(看过期时间,一旦到了过期时间,自动销毁,存储在用户的硬盘上,备注:如果没有到过期时间,同时用户清理了浏览器的缓存,持久化cookie也会消失)。
*
*     3.工作原理:
*           --当浏览器第一次请求服务器的时候,服务器可能返回一个或多个cookie给浏览器
*           --浏览器判断cookie种类
*               --会话cookie:存储在浏览器运行的那块内存上
*               --持久化cookie:存储在用户的硬盘上
*           --以后请求该网站的时候,自动携带上该网站的所有cookie(无法进行干预)
*           --服务器拿到之前自己“种”下cookie,分析里面的内容,校验cookie的合法性,根据cookie里保存的内容,进行具体的业务逻辑。
*
*      4.应用:
*           解决http无状态的问题(例子:7天免登录,一般来说不会单独使用cookie,一般配合后台的session存储使用)
*
*      5.不同的语言、不同的后端架构cookie的具体语法是不一样的,但是cookie原理和工作过程是不变的。
*         备注:cookie不一定只由服务器生成,前端同样可以生成cookie,但是前端生成的cookie几乎没有意义。



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

# ejs模板

const express = require('express')

const app = express()
//让你的服务器知道你在用哪一个模板引擎-----配置模板引擎
app.set('view engine','ejs')
//让你的服务器知道你的模板在哪个目录下,配置模板目录
app.set('views','./public')

//如果在express中基于Node搭建的服务器,使用ejs无需引入。
app.get('/show',function (request,response) {
    let personArr = [
        {name:'peiqi',age:4},
        {name:'suxi',age:5},
        {name:'peideluo',age:6}
    ]
    response.render('person',{persons:personArr})
})

app.listen(3000,function (err) {
    if (!err) console.log('服务器启动成功了')
    else console.log(err)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 模块原理分析

//存在依赖关系,字符串可以访问外界数据,不安全
/*
let str = 'enenen'
let name = 'console.log(str)'
eval(name)

let str1 = console.log('enenen')
let fn = Function(str1)
fn()
*/

let vm = require("vm")

/*
global.name = 'jj'
//runInThisContext : 提供了一个安全的环境,给我们执行字符串中的代码
//runInThisContext 不能访问本地变量,但是可以访问 全局global 里的变量

let name = 'lj'
let str = "console.log(name)"
vm.runInThisContext(str) //name is not defined
*/



global.name = 'jj' //name is not defined
let name = 'lj'
let str = "console.log(name)"
vm.runInNewContext(str) //name is not defined

//runInNewContext : 提供了一个安全的环境,给我们执行字符串中的代码
//runInNewContext 不能访问本地变量,也不能访问 全局global 里的变量



/*
1.Node模块
1.1在CommonJS规范中一个文件就是一个模块
1.2在CommonJS规范中通过exports暴露数据
1.3在CommonJS规范中通过require()导入模块

2.Node模块原理分析
既然一个文件就是一个模块,
既然想要使用模块必须先通过require()导入模块
所以可以推断出require()的作用其实就是读取文件
所以要想了解Node是如何实现模块的, 必须先了解如何执行读取到的代码

3.执行从文件中读取代码
我们都知道通过fs模块可以读取文件,
但是读取到的数据要么是二进制, 要么是字符串
无论是二进制还是字符串都无法直接执行

但是我们知道如果是字符串, 在JS中是有办法让它执行的
eval  或者 new Function;

4.通过eval执行代码
缺点: 存在依赖关系, 字符串可以访问外界数据,不安全

5.通过new Function执行代码
缺点: 存在依赖关系, 依然可以访问全局数据,不安全
-->
<!--
6.通过NodeJS的vm虚拟机执行代码
runInThisContext: 无权访问外部变量, 但是可以访问global
runInNewContext:  无权访问外部变量, 也不能访问global
 */
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

# Node模块加载流程分析

/*
1.内部实现了一个require方法
    function require(path) {
      return self.require(path);
    }

2.通过Module对象的静态__load方法加载模块文件
    Module.prototype.require = function(path) {
      return Module._load(path, this, isMain  false);
    };

3.通过Module对象的静态_resolveFilename方法, 得到绝对路径并添加后缀名
    var filename = Module._resolveFilename(request, parent, isMain);

4.根据路径判断是否有缓存, 如果没有就创建一个新的Module模块对象并缓存起来
    var cachedModule = Module._cache[filename];
    if (cachedModule) {
        return cachedModule.exports;
    }
    var module = new Module(filename, parent);
    Module._cache[filename] = module;

    function Module(id, parent) {
        this.id = id;
        this.exports = {};
    }
5.利用tryModuleLoad方法加载模块
    tryModuleLoad(module, filename);
- 6.1取出模块后缀
    var extension = path.extname(filename);

- 6.2根据不同后缀查找不同方法并执行对应的方法, 加载模块
    Module._extensions[extension](this, filename);

- 6.3如果是JSON就转换成对象
    module.exports = JSON.parse(internalModule.stripBOM(content));

- 6.4如果是JS就包裹一个函数
    var wrapper = Module.wrap(content);
    NativeModule.wrap = function(script) {
        return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
    };
    NativeModule.wrapper = [
        '(function (exports, require, module, __filename, __dirname) { ',
        '\n});'
    ];
- 6.5执行包裹函数之后的代码, 拿到执行结果(String -- Function)
    var compiledWrapper = vm.runInThisContext(wrapper);

- 6.6利用call执行fn函数, 修改module.exports的值
    var args = [this.exports, require, module, filename, dirname];
    var result = compiledWrapper.call(this.exports, args);

- 6.7返回module.exports
    return module.exports;
*/

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

# 浏览器事件环

1.JS是单线程的
  JS中的代码都是串行的, 前面没有执行完毕后面不能执行

2.执行顺序
2.1程序运行会从上至下依次执行所有的同步代码
2.2在执行的过程中如果遇到异步代码会将异步代码放到事件循环中
2.3当所有同步代码都执行完毕后, JS会不断检测 事件循环中的异步代码是否满足条件
2.4一旦满足条件就执行满足条件的异步代码

3.宏任务和微任务
在JS的异步代码中又区分"宏任务(MacroTask)"和"微任务(MicroTask)"
宏任务: 宏/大的意思, 可以理解为比较费时比较慢的任务
微任务: 微/小的意思, 可以理解为相对没那么费时没那么慢的任务

4.常见的宏任务和微任务
MacroTask: setTimeout, setInterval, setImmediate(IE独有)...
MicroTask: Promise, MutationObserver ,process.nextTick(node独有) ...
注意点: 所有的宏任务和微任务都会放到自己的执行队列中, 也就是有一个宏任务队列和一个微任务队列
        所有放到队列中的任务都采用"先进先出原则", 也就是多个任务同时满足条件, 那么会先执行先放进去的

5.完整执行顺序
1.从上至下执行所有同步代码
2.在执行过程中遇到宏任务就放到宏任务队列中,遇到微任务就放到微任务队列中
3.当所有同步代码执行完毕之后, 就执行微任务队列中满足需求所有回调
4.当微任务队列所有满足需求回调执行完毕之后, 就执行宏任务队列中满足需求所有回调
... ...
注意点:
每执行完一个宏任务都会立刻检查微任务队列有没有被清空, 如果没有就立刻执行清空
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

# Node.js事件环

1.概述
和浏览器中一样NodeJS中也有事件环(Event Loop),
但是由于执行代码的宿主环境和应用场景不同,
所以两者的事件环也有所不同.

>扩展阅读: 在NodeJS中使用libuv实现了Event Loop.
>源码地址: https://github.com/libuv/libuv

2.NodeJS事件环和浏览器事件环区别
2.1任务队列个数不同
浏览器事件环有2个事件队列(宏任务队列和微任务队列)
NodeJS事件环有6个事件队列
2.2微任务队列不同
浏览器事件环中有专门存储微任务的队列
NodeJS事件环中没有专门存储微任务的队列
2.3微任务执行时机不同
浏览器事件环中每执行完一个宏任务都会去清空微任务队列
NodeJS事件环中只有同步代码执行完毕和其它队列之间切换的时候会去清空微任务队列
2.4微任务优先级不同
浏览器事件环中如果多个微任务同时满足执行条件, 采用先进先出
NodeJS事件环中如果多个微任务同时满足执行条件, 会按照优先级执行
process.nextTick() 能在任意阶段优先执行(不包括主线程)

2.NodeJS中的任务队列
    ┌───────────────────────┐
┌> │timers          │执行setTimeout() 和 setInterval()中到期的callback
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │pending callbacks│执行系统操作的回调, 如:tcp, udp通信的错误callback
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │idle, prepare   │只在内部使用
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │poll            │执行与I/O相关的回调
    │                  (除了close回调、定时器回调和setImmediate()之外,几乎所有回调都执行);
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │check           │执行setImmediate的callback
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└─┤close callbacks │执行close事件的callback,例如socket.on("close",func)
    └───────────────────────┘



    ┌───────────────────────┐
┌> │timers          │执行setTimeout() 和 setInterval()中到期的callback
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │poll            │执行与I/O相关的回调
    │                  (除了close回调、定时器回调和setImmediate()之外,几乎所有回调都执行);
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└─┤check           │执行setImmediate的callback
    └───────────────────────┘

1.注意点:
和浏览器不同的是没有宏任务队列和微任务队列的概念
宏任务被放到了不同的队列中, 但是没有队列是存放微任务的队列
微任务会在执行完同步代码和队列切换的时候执行

什么时候切换队列?
当队列为空(已经执行完毕或者没有满足条件回到)
或者执行的回调函数数量到达系统设定的阈值时任务队列就会切换

2.注意点:
在NodeJS中process.nextTick微任务的优先级高于Promise.resolve微任务
-->
<!--
            ┌───────────────────────┐
            │                同步代码
            └──────────┬────────────┘
                                  │
                                  │
                                  │
            ┌──────────┴────────────┐
        ┌> │timers          │执行setTimeout() 和 setInterval()中到期的callback
        │  └──────────┬────────────┘
        │                        │
        │                        │ <---- 满足条件微任务代码
        │                        │
        │  ┌──────────┴────────────┐
        │  │poll            │执行与I/O相关的回调
        │  │                  (除了close回调、定时器回调和setImmediate()之外,几乎所有回调都执行);
        │  └──────────┬────────────┘
        │                        │
        │                        │ <---- 满足条件微任务代码
        │                        │
        │  ┌──────────┴────────────┐
        └─┤check           │执行setImmediate的callback
            └───────────────────────┘

注意点:
执行完poll, 会查看check队列是否有内容, 有就切换到check
如果check队列没有内容, 就会查看timers是否有内容, 有就切换到timers
如果check队列和timers队列都没有内容, 为了避免资源浪费就会阻塞在poll

——————————————————————————————————————————————————————————————

第一个阶段: timers(定时器阶段--setTimeout,setInterval)
            1.开始记时
            2.执行定时器的回调

第二个阶段: pending callbacks(系统阶段)

第三个阶段: idle,prepare(准备阶段)

第四个阶段: poLl(轮询阶段,核心)
            ---.如果回调队列里有待执行的回调函数
                从回调队列中取出回调函数,同步执行,直到回调队列为空,或者达到系统最大限度
            ---.如果回调队列为空
                设置 setImmediate(),进入下一个check阶段,为了执行setImmediate所设置的回调
                未设置 setImmediate(),则事件循环将等待回调被添加到队列中,然后立即执行。
               1.JS是单线程的
  JS中的代码都是串行的, 前面没有执行完毕后面不能执行

2.执行顺序
2.1程序运行会从上至下依次执行所有的同步代码
2.2在执行的过程中如果遇到异步代码会将异步代码放到事件循环中
2.3当所有同步代码都执行完毕后, JS会不断检测 事件循环中的异步代码是否满足条件
2.4一旦满足条件就执行满足条件的异步代码

3.宏任务和微任务
在JS的异步代码中又区分"宏任务(MacroTask)"和"微任务(MicroTask)"
宏任务: 宏/大的意思, 可以理解为比较费时比较慢的任务
微任务: 微/小的意思, 可以理解为相对没那么费时没那么慢的任务

4.常见的宏任务和微任务
MacroTask: setTimeout, setInterval, setImmediate(IE独有)...
MicroTask: Promise, MutationObserver ,process.nextTick(node独有) ...
注意点: 所有的宏任务和微任务都会放到自己的执行队列中, 也就是有一个宏任务队列和一个微任务队列
        所有放到队列中的任务都采用"先进先出原则", 也就是多个任务同时满足条件, 那么会先执行先放进去的

5.完整执行顺序
1.从上至下执行所有同步代码
2.在执行过程中遇到宏任务就放到宏任务队列中,遇到微任务就放到微任务队列中
3.当所有同步代码执行完毕之后, 就执行微任务队列中满足需求所有回调
4.当微任务队列所有满足需求回调执行完毕之后, 就执行宏任务队列中满足需求所有回调
... ...
注意点:
每执行完一个宏任务都会立刻检查微任务队列有没有被清空, 如果没有就立刻执行清空
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
130
131
132
133
134
135
136
137
138
139
140
141
142

图片

Last Updated: 3/30/2022, 4:21:26 PM