nodejs+express学习笔记

文章目录

      • 基础
        • 回顾js
        • 初识nodejs
      • 内置api
        • fs文件系统模块
        • path路径模块
        • http模块
      • 模块化
        • 基本概念
        • nodejs中的模块化
        • 模块加载机制
      • express
        • 初始express
        • 托管静态资源
        • nodemon
        • 路由
        • 中间件
        • cors跨域资源共享
      • 数据库与身份认证
        • 数据库基本概念
        • 安装并配置MySql
        • mysql常用操作
        • 前后端的身份认证

基础

回顾js

  1. 为什么javascript可以在浏览器中被执行

    待执行的js代码 => javascript解析引擎

    不同浏览器的javascript解析引擎不一样

    1. chrome => V8(性能最好)
    2. firefox => OdinMonkey(奥丁猴)
    3. safri => JSCore
    4. IE => Chakra(查克拉)
  2. 为什么javascript可以操作dom和bom

    1. 浏览器提供了domapi、bomapi、ajaxapi
    2. 可以被js代码调用
    3. 然后交给解析引擎执行
  3. 浏览器的javascript运行环境

    运行环境是指:代码正常运行所需的必要环境

    比如chrome浏览器运行环境:

    1. v8引擎

      负责解析javascript代码

    2. 内置api:dom、bom、canvas、ajax、js内置对象等等

      由运行环境提供的特殊接口、只能在所属的运行环境中被调用

  4. javascript能否做后端开发

    需要在nodejs的运行环境中可以

初识nodejs

  1. nodejs是一个基于chromeV8引擎的javascript运行环境

  2. nodejs的javascript运行环境

    1. v8引擎
    2. 内置api:fs、path、http、js内置对象等等
  3. nodejs可以做什么

    只是提供了基础的功能和api,然而,基于nodejs的基础功能,很多强大的工具和框架如雨后春笋,层出不穷

    1. express:可以快速搭建web应用
    2. electron:构建跨平台的桌面应用
    3. restify:快速构建api接口项目
    4. 读写和操作数据哭、创建实用的命令行工具辅助前端开发
  4. 学习路径

    1. 浏览器的javascript学习路径

      1. 基础语法
      2. 浏览器内置api:bom、dom
      3. 第三方库:jquery、react、vue
    2. nodejs学习路径

      1. 基础语法
      2. nodejs内置api:fs、path、http等
      3. 第三方api模块:express、mysql等

内置api

fs文件系统模块

  1. 定义

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

    1. fs.readFile()方法,用来读取指定文件中的内容
    2. fs.writeFile()方法,用来向指定的文件中写入内容
  2. 使用

    const fs = require('fs');
    
  3. fs.readFile()

    fs.readFile(path[, options], callback);
    
    1. path:必须,文件路径
    2. options:指定编码格式(一般用utf8)
    3. 回调:(err, data),一般用err是否为null来判断是否读取成功
    fs.readFile('./notes.md', 'utf-8', (err, data) => {
        if (err) {
            console.log('err :>> ', err);
            return "文件读取失败";
        }
        console.log('data :>> ', data);
        reurn "文件读取成功";
    })
    
  4. fs.writeFile()

    fs.writeFile(path, data[, options], callback);
    
    1. file: 指定文件路径字符串(如果不存在会自动创建)
    2. data: 写入的内容
    3. options:编码格式(一般用utf8)
    4. callback:(err)
    fs.writeFile("11.txt", "hello", "utf8", (err) => {
        if (err) {
            console.log('err :>> ', err);
            return "文件读取失败";
        }
        return "文件写入成功";
    });
    
  5. fs模块-路径动态拼接问题

    背景:在使用fs模块操作文件时,如果提供的操作路径是以./../开头的相对路径时,很容易出现路径动态拼接错误的问题

    原因:代码在运行的时候,会执行node命令时所处的目录,动态拼接出被操作文件的完整路径

    如下代码:代码路径为/user/local/test.js,读取的文件在/user/local/notes.md,当在/user/local执行node test.js和在/user执行node local/test.js效果时是不一样的

    fs.readFile('./notes.md', 'utf-8', (err, data) => {
        if (err) {
            console.log('err :>> ', err);
            return "文件读取失败";
        }
        console.log('data :>> ', data);
        return "文件读取成功";
    })
    

    解决方案

    1. 使用绝对路径:可移植性差,不利于维护

    2. 使用__dirname表示当前文件所在的绝对目录

      fs.writeFile(__dirname + "/11.txt", "hello", "utf8", (err) => {
          if (err) {
              console.log('err :>> ', err);
              return "文件读取失败";
          }
          return "文件写入成功";
      });
      

      延伸:如果想要读取上一层的文件的话,需要使用path.join来进行拼接

      const path = require('path');
      const fs = require('fs');
      
      const filePath = path.join(__dirname, '../example.txt');
      fs.readFile(filePath, (err, data) => {
      if (err) throw err;
      console.log(data.toString());
      });
      

path路径模块

  1. 定义

    nodejs官方提供的,用来处理路径的模块,提供了一系列的方法和属性,用来满足用户对路径的处理需求

    1. path.join():用来将多哦哥路径片段拼接成一个完整字符串
    2. path.basename():用来从路径字符串中,将文件名解析出来
  2. 导入

    const path = require('path');
    
  3. path.join()

    const pathStr = path.join('a', '/b', 'c', '../', 'd');
    console.log('pathStr :>> ', pathStr); // pathStr :>>  a/b/d
    
    const pathStr1 = path.join(__dirname, pathStr, "../f");
    console.log('pathStr1 :>> ', pathStr1); // /Users/xxx/Documents/code/learnningNotes/js/nodejs/testcode/a/b/f
    

    参数说明

    1. 可以有多个
    2. 如果是../的话会自动去掉最后一个层级
    3. /aa拼接效果是一样的

    注意:凡是涉及到路径拼接的操作,都要使用path.join()方法进行处理,不要直接使用+进行字符串的拼接

  4. path.basename()

    const filePath = '/user/local/test.txt';
    let fullName = path.basename(filePath);
    console.log('fullName :>> ', fullName); // test.txt
    let nameWithoutExtension = path.basename(filePath, '.txt');
    console.log('nameWithoutExtension :>> ', nameWithoutExtension); // test
    

    参数说明

    1. 第一个为文件路径
    2. 第二个可选,为去掉的扩展名
  5. path.extname()

    获取路径中的文件扩展名

    path.extname(path)

    const filePath = '/user/local/test.txt';
    let extname = path.extname(filePath);
    console.log('extname :>> ', extname); // .txt
    
  6. 综合案例:将目录下的index.html中的html、script、style拆分出来,放到同级下的一个test目录中

    步骤分析

    1. 导入需要的模块并创建正则表达式

      const path = require('path');
      const fs = require('fs');
      
      const regStyle = /', '');
          fs.writeFile(path.join(__dirname, 'test/index.css'), newCSS, err => {
              if (err) return console.error('写入css文件失败:', err.message);
              console.log('写入css成功');
          });
      }
      
    2. 自定义resolveJS方法

      function resolveJS(data) {
          const r1 = regScript.exec(data);
          const newJS = r1[0].replace('', '');
          fs.writeFile(path.join(__dirname, 'test/index.js'), newJS, err => {
              if (err) return console.error('写入js文件失败:', err.message);
              console.log('写入js成功');
          });
      }
      
    3. 自定义resolveHTML方法

      function resolveHTML(data) {
          const newHTML = data.replace(regStyle, '')
          .replace(regScript, '');
          fs.writeFile(path.join(__dirname, 'test/index.html'), newHTML, err => {
              if (err) return console.error('写入html文件失败:', err.message);
              console.log('写入html成功');
          });
      }
      
    4. 两个注意点

      1. path.writeFile()方法只能用来写入文件,不能用来创建目录
      2. path.writeFile()方法重复调用,新的内容会覆盖旧的内容

http模块

  1. 什么是http模块

    客户端:在网络节点中,负责消费资源的电脑。
    服务器:负责对外提供网络资源的电脑。

    http模块就是nodejs提供的,用来创建web服务器的模块。通过http模块提供的http.createServer()方法,就能方便的把一台普通的电脑,变成一台web服务器,从而对外提供web资源服务

  2. 导入

    const http = require('http');
    
  3. 了解http模块的作用

    服务器和普通电脑的区别:服务器上安装了web服务器软件,例如IIS、Apache等。通过安装这些服务器软件,就能把一台普通的电脑变成一台web服务器。

    在nodejs中,我们不需要使用IIS、Apache等第三方web服务器软件,因为我们可以基于nodejs提供的http模块,通过几行简单的代码,就能轻松的写一个服务器软件,从而对外提供web服务。

  4. 服务器相关的概念

    1. IP地址

      1. 就是互联网上每台计算机的唯一地址,因此IP地址具有唯一性。
      2. 如果把个人电脑比作一台电话,那么IP地址就相当于电话号码。
      3. 只有在知道对方IP地址的前提下,才能与对应的电脑之间进行数据通信
      4. IP地址的格式,通常用点分十进制表示成a.b.c.d的形式
      5. 其中a,b,c,d都是0-255之间的十进制整数,例如192.168.1.1
      6. 互联网中每台web服务器,都有自己的IP地址
      7. 在开发期间,自己的电脑既可以是一台服务器,也可以是一个客户端,为了方便测试,可以在浏览器中输入127.0.0.1这个IP地址,就能把自己的电脑当成服务器访问了
    2. 域名和域名服务器

      1. 由于IP地址不方便记忆,于是发明了一套字符型的地址方案,就是域名地址
      2. ip和域名地址是一一对应的关系,这份对应关系存放在一个叫做域名服务器(DNS,Domain name server)的电脑中
      3. 使用者只需通过好记的域名访问对应的服务器即可,对应的转换工作由域名服务器实现
      4. 因此域名服务器就是提供IP地址和域名之间转换服务的服务器
      5. 在开发测试期间,127.0.0.1对应的域名是localhost,使用效果上没有任何区别
    3. 端口号

      1. 计算机的端口号,就好像是现实生活中的门牌号一样,通过门牌号,外卖小哥可以在整栋大楼的众多房间中,准确的把外卖送到你的手中
      2. 在一台电脑中,可以运行成百上千个web服务,每一个web服务都对应一个唯一的端口号,客户端发送过来的网络请求,通过端口号,可以被准确的交给对应的web服务进行处理
      3. 每个端口号不能同时被多个web服务占用
      4. 在实际应用中,url中的80端口可以被省略
  5. 创建最基本的web服务器

    1. 创建web服务器的基本步骤

      1. 导入http模块
      2. 创建web服务器实例
      3. 为服务器实例绑定request事件,监听客户端的请求
      4. 启动服务器
      const http = require('http');
      
      // 创建web服务器实例
      const server = http.createServer();
      
      // 为服务器实例绑定request事件,监听客户端的请求
      server.on('request', (req, res) => {
          console.log("监听到了request");
          console.log('req :>> ', req);
          console.log('res :>> ', res);
      });
      
      // 调用服务器实例的`.listen()`方法,即可启动当前的web服务器实例
      server.listen(80, () => {
          console.log('http server running at http://127.0.0.1');
      })
      
    2. req请求对象

      只要服务器接收到了客户端的请求,就会调用通过server.on()为服务器绑定的request事件处理函数

      如果想要在事件处理函数中,访问与客户端相关的数据或者属性,可以使用如下的方式

      server.on('request', (req, res) => {
          console.log('req.url :>> ', req.url);
          console.log('req.method :>> ', req.method);
      });
      
    3. res响应对象

      在服务器的request事件处理函数中,如果想访问与服务器相关的数据或属性,可以使用如下的方式

      res.end()方法的作用:向客户端发送指定的内容,并结束这次请求的处理过程

      server.on('request', (req, res) => {
          res.end("Hello, world!");
      });
      
    4. 解决中文乱码的问题

      当调用res.end()向客户端发送中文内容的时候,会出现乱码的问题,此时,需要手动设置内容的编码格式

      如我返回的是我爱宝宝,接收到的是鎴戠埍瀹濆疂

      需要设置响应头Content-Type的值为text/html;charset=utf-8

      res.setHeader('Content-Type', 'text/html;charset=utf-8');
      res.end("我爱宝宝");
      
    5. 根据不同的url响应不同的html内容

      server.on('request', (req, res) => {
          const url = req.url;
          let content = '

      404 not found

      '
      ; if (url === '/' || url === '/index.html') { content = '

      首页

      '
      } else if (url === '/about.html') { content = '

      关于页面

      '
      ; } res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(content); });
    6. 案例:将上面的拆分index.html的几个文件返回给客户端

      const http = require('http');
      const path = require('path');
      const fs = require('fs');
      
      const server = http.createServer();
      
      // 为服务器实例绑定request事件,监听客户端的请求
      server.on('request', (req, res) => {
          console.log("进入了request");
          const url = req.url;
          console.log('url :>> ', url);
      
          let content = "";
          fs.readFile(path.join(__dirname, 'test', url), 'utf8', (err, data) => {
              console.log("进入了readFile");
              console.log('data :>> ', data);
              if (err) {
                  content = '

      404 not found

      '
      ; } else { content = data; } // res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(content); }); }); // 启动服务器 // 调用服务器实例的`.listen()`方法,即可启动当前的web服务器实例 server.listen(80, () => { console.log('http server running at http://127.0.0.1'); })

模块化

基本概念

  1. 什么是模块化

    1. 是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程
    2. 对于整个系统来说,模块是可组合、分解和更换的单元
  2. 编程领域的模块化

    遵守固定的规则,把一个大文件拆分成独立相互依赖的多个小模块

    好处

    1. 提高了代码的复用性
    2. 提高了代码的可维护性
    3. 可以实现按需加载
  3. 模块化规范

    就是对代码进行拆分和组合时,需要遵守的那些规则

    例如

    1. 使用什么语法格式来引用模块
    2. 在模块中使用什么样的语法格式向外暴露成员

    好处

    1. 降低了沟通成本
    2. 方便了各个模块之间的相互调用

nodejs中的模块化

  1. 分类

    1. 内置模块:nodejs官方提供的(fs,path,http)
    2. 自定义模块:用户创建的每个js文件
    3. 第三方模块:第三方开发出来的模块,使用前需要先下载
  2. 加载模块

    使用require()方法,可以加载需要的模块

    // 加载内置模块
    const fs = require('fs');
    // 加载自定义模块(需要指定路径)(可以省略js后缀)
    const custom = require('./custom.js');
    // 加载第三方模块
    const moment = require('moment');
    

    注意:使用require方法加载其他模块时,会执行被加载模块中的代码

  3. 模块作用域

    和函数作用域蕾丝,在自定义模块中定义的变量、方法等成员,只能在当前模块内访问,这种模块级别的访问限制,叫做模块作用域

    // customer.js
    const username = 'lan';
    function sayHello() {
        console.log("hello:", username);
    }
    
    // test.js
    const customer = require('./customer.js');
    console.log("customer", customer); // {}
    

    好处

    1. 防止了全局变量污染的问题
  4. 向外共享模块作用域中的成员

    1. module对象

      在每个自定义模块中都有一个module对象,它里面存储了和当前模块有关的信息

      Module {
          id: '.',
          path: '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/testcode',
          exports: {},
          filename: '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/testcode/module.js',
          loaded: false,
          children: [
              Module {
                  id: '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/testcode/customer.js',
                  path: '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/testcode',
                  exports: {},
                  filename: '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/testcode/customer.js',
                  loaded: true,
                  children: [],
                  paths: [Array]
              }
          ],
          paths: [
              '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/testcode/node_modules',
              '/Users/feng.lan/Documents/code/learnningNotes/js/nodejs/node_modules',
              '/Users/feng.lan/Documents/code/learnningNotes/js/node_modules',
              '/Users/feng.lan/Documents/code/learnningNotes/node_modules',
              '/Users/feng.lan/Documents/code/node_modules',
              '/Users/feng.lan/Documents/node_modules',
              '/Users/feng.lan/node_modules',
              '/Users/node_modules',
              '/node_modules'
          ]
      }
      
    2. module.exports对象

      在自定义模块中,可以使用module.exports对象,把模块内的成员共享出去,供外界使用

      外界使用require()导入自定义的模块时,得到的就是module.exports所指的对象

      注意:使用require()导入模块时,导入的结果,永远以module.exports指向的对象为准

      const username = "lan";
      function sayHello() {
          console.log("hello:", username);
      }
      
      module.exports.nickname = 'feng';
      module.exports.printName = function() {
          console.log("printName");
      }
      
      module.exports = {
          username,
          sayHello
      }
      
    3. exports对象

      由于module.exports单词写起来比较复杂,为了简化向外共享成员的代码,nodejs提供了exports对象,默认情况下,exports和module.exports指向同一个对象

      最终共享的结果,还是以module.exports指向的对象为准

    4. exports和module.exports的使用误区

      console.log(module.exports === exports); // true
      

      关键点:在没有重新改变引用对象的时候,两者相等,但是一旦其中一个改变引用对象之后,就以module.exports为准

      时刻谨记,require()模块时,得到的永远是module.exports指向的对象

      module.exports.username = 'lan';
      exports.age = 12;
      
      // 这种情况下获取到的是 {username: 'lan', age: 12}
      
      module.exports.username = 'lan';
      
      exports = {
          gender: '男',
          age: 12
      }
      
      // 这种情况下获取到的是 {name: 'lan'}
      
      exports.usename = 'lan';
      
      module.exports = {
          gender: 'man',
          age: 12
      }
      
      // 这种情况得到的是 {gender: 'man', age: 12}
      
  5. nodejs中的模块化规范

    遵循commonjs模块化规范,规定了模块的特性和各模块之间如何相互依赖

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

  1. 什么是包

    nodejs中的第三方模块有叫做包

  2. 包的来源

    由第三方个人或者团队开发出来的,免费供所有人使用

    注意:nodejs中的包都是免费开源的,不需要付费即可免费下载使用

  3. 为什么需要包

    1. 由于nodejs内置模块仅提供了一些底层api,导致基于内置模块进行项目开发时,效率很低
    2. 包是基于内置模块封装出来的,提供了更高级、更方便的api,极大的提高了开发效率
    3. 包和内置模块之间的关系,类似jquery和浏览器内置api之间的关系
  4. 在哪里下载包

    搜索:网站https://www.npmjs.com/,他是全球最大的包共享平台,你可以从这网站上搜索任何你需要的包

    下载:通过https://registry.npmjs.org/的服务器,来对外共享所有的包,我们可以从这服务器上下载自己所需要的包

  5. 如何下载包

    包管理工具:npm包管理工具(Node Package Manager),这个工具随着nodejs的安装包一起被安装到了用户的电脑上

  6. 初次安装包之后多了哪些文件

    1. node_modules:存放所有已安装到项目中的包
    2. package-lock.json:记录node_modules目录下每一个包的下载信息(名字、版本号、下载地址)
  7. 安装指定版本

    默认情况下,是安装最新版本的,如果需要指定版本,使用@

    npm i moment@2.22.2
    
  8. 包的语义化版本规范

    点分十进制的形式定义的,总共有3位数字,如2.22.2

    1. 第一位数字:大版本
    2. 第二位数字:功能版本
    3. 第三位数字:Bug修复版本

    版本号提升规则:只要前面的版本号增长了,后面的版本号归零

  9. 包管理配置文件

    npm规定:在项目根目录中,必须提供一个叫做package.json的包管理配置文件,用来记录与项目有关的一些配置信息,如

    1. 项目名称、版本号、描述等
    2. 项目中用了哪些其他包
    3. 哪些包只在开发期间会用到
    4. 哪些包在开发和部署的时候都会用到

    快速创建package.json

    npm init -y

    注意:上述命令,只能在英文目录中运行,所以项目文件夹的名称一定要使用英文命名,不要使用中文,不要出现空格

    dependencies:用来记录安装过哪些包

    devDependencies:只在项目开发阶段会用到,项目上线之后不会用到

     ```js
     npm i webpack -D
    
     npm install webpack --save--dev
     ```
    
  10. npm下载速度慢

    1. 原因:默认从国外https://registry.npmjs.org服务器进行下载,此时网络数据传输需要经过漫长的海底光缆

    2. 方案:淘宝npm镜像服务器

      淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,然后在国内提供下包的服务,极大的提高了下包的速度

      切换npm的下包镜像源

       ```shell
       # 查看当前下包镜像源
       npm config get registry
      
       # 将下包的镜像源切换为淘宝镜像源
       npm config get registry=https://registry.npm.taobao.org/
       ```
      
    3. nrm

      为了方便切换下包的镜像源,我们可以安装nrm这个小工具,利用nrm提供的终端命令,可以快速查看和切换下包的镜像源

      npm i nrm -g
      nrm ls
      nrm use taobao
      

      可能存在的报错

      /opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.18.0/lib/node_modules/nrm/cli.js:9
      const open = require('open');
                  ^
      
      Error [ERR_REQUIRE_ESM]: require() of ES Module /opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.18.0/lib/node_modules/nrm/node_modules/open/index.js from /opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.18.0/lib/node_modules/nrm/cli.js not supported.
      Instead change the require of index.js in /opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.18.0/lib/node_modules/nrm/cli.js to a dynamic import() which is available in all CommonJS modules.
          at Object.<anonymous> (/opt/homebrew/Cellar/nvm/0.39.1_1/versions/node/v16.18.0/lib/node_modules/nrm/cli.js:9:14) {
      code: 'ERR_REQUIRE_ESM'
      }
      

      原因是缺少相应的open

      // 这里好像要这个版本才行,我安装最新的版本也没有解决
      npm install -g nrm open@8.4.2 --save
      
  11. 规范的包结构

    1. 包必须以单独的目录而存在
    2. 包的顶级目录下必须要包含package.json这个包管理配置文件
    3. package.json中必须包含name,version,main这3个属性,代表名字、版本号、入口
  12. 开发属于自己的包

    1. 初始化package.json

      {
      "name": "lan-tools",
      "version": "1.0.0",
      "description": "feng.lan测试包",
      "main": "index.js",
      "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": ["feng", "lan"],
      "author": "feng.lan",
      "license": "ISC"
      }
      
    2. 在index.js中初始化自己的代码,并暴露对应的成员

      function printInfo() {
          console.log("这里是feng.lan的测试函数");
      }
      
      function sayHello(name) {
          console.log("hello " + name);
      }
      
      module.exports = {
          printInfo,
          sayHello,
      }
      
    3. 编写包的说明文档

      包根目录中的README.md文件

      ### 安装
      
      > npm install lan-tools
      
      ### 导入
      
      > const lanTools = require('lan-tools');
      
      ### 输出信息
      
      > lanTools.printInfo();
      
      ### sayHello
      
      > lanTools.sayHello("feng.lan");
      
    4. 发布包

      1. 注册npm账号

      2. 终端使用npm login命令,依次输入用户名、密码、邮箱、邮箱验证码后,即可登录成功

        存在的问题:在登录的时候需要将包仓库设置为npm官方源,因为登陆是根据你这个设置的,否则比如你的是淘宝源,他会提示你正在登陆淘宝源,那你注册的npm账号就登陆不上去

         ```shell
         npm WARN adduser `adduser` will be split into `login` and `register` in a future version. `adduser` will become an alias of `register`. `login` (currently an alias) will become its own command.
         npm notice Log in on https://registry.npm.taobao.org/
         ```
        
      3. 将终端切换到包的根目录之后,运行npm publish

    5. 删除已经发布的包

      npm unpublish 包名 --force
      

      注意

      1. 只能删除72小时内发布的包
      2. 不能24小时内不允许重复发布

模块加载机制

  1. 优先从缓存中进行加载

    模块在第一次加载之后会被缓存,这也意味着多次调用require()不会导致模块的代码被执行多次

    无论是内置模块、用户自定义模块、还是第三方模块,他们都会优先从缓存中加载,从而提高模块的加载效率

  2. 内置模块的加载机制

    内置模块的加载优先级最高

    即使自定义模块有同名的fs模块,也是加载nodejs官方的fs模块

  3. 自定义模块的加载机制

    必须要以./或者../开头的路径标志符,如果没有用./或者../,则node会把他当作内置模块或者第三方模块进行加载

    如果省略了文件的扩展名,则nodejs会按顺序分别尝试加载一下的文件

    1. 按照确切的文件名进行加载
    2. 补全.js扩展ing进行加载
    3. 补全.json扩展名进行加载
    4. 补全.node扩展名进行加载
    5. 加载失败,终端报错
  4. 第三方模块的加载机制

    nodejs会从当前模块的父目录开始,尝试从/node_modules目录中加载第三方模块

    如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录

    例如,在/user/local/project/foo.js中调用了require('tools'),则nodejs会按以下顺序进行查找

    1. /user/local/project/node_modules/tools
    2. /user/local/node_modules/tools
    3. /user/node_modules/tools
    4. /node_modules/tools
  5. 目录作为模块

    当把目录作为模块标志符,传递给require()进行加载的时候,有3种加载方式

    1. 在被加载的目录下查找package.json,并寻找main属性,作为require()的入口
    2. 如果上一步没有,则寻找目录下的index.js文件
    3. 如果上面两步都失败了,则会在终端报错Error: Cannot find module 'xxx'

express

初始express

  1. 定义

    1. 官方:基于nodejs平台,快速、开放、极简的web开发框架
    2. 白话:和内置的http模块类似,是专门用来创建web服务器的
    3. 本质:就是一个npm上的第三方包,提供了快速创建web服务器的便捷方法

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

    1. 进一步理解

      内置的http内置模块用起来很复杂,开发效率低

      express是基于内置的http模块进一步封装出来的,能够极大的提高开发效率

    2. experss能做什么

      1. web网站服务器:专门对外提供web网页资源的服务器
      2. api接口服务器:专门对外提供api接口的服务器
  2. 安装

    npm i express`

  3. 基本使用

    // 导入express
    const express = require('express');
    // 创建web服务器
    const app = express();
    // 启动web服务器
    app.listen(80, () => {
        console.log('express server running at http://127.0.0.1 and listening on 80')
    })
    
  4. 监听get、post请求,并返回响应内容

    const express = require('express');
    
    const app = express();
    
    app.listen(80, () => {
        console.log('express server running at http://127.0.0.1 and listening on 80')
    })
    
    app.get('/getMethods', (req, res) => {
        console.log('getMethods');
        res.send({
            type: 'get',
            name: 'lan',
            age: 12
        })
    })
    
    app.post('/postMethods', (req, res) => {
        console.log('postMethods');
        res.send({
            type: 'post',
            name: 'lan',
            age: 12
        })
    })
    
  5. 获取url的查询参数

    app.get('/getMethods', (req, res) => {
        const params = req.query;
        res.send(params)
    })
    
  6. 获取url中的动态参数

    通过:匹配动态参数

    例如接口/user/:id用来查询某个用户信息

    app.get('/user/:id', (req, res) => {
        const params = req.params;
        res.send(params);
    })
    
    // 可以多个动态参数拼接
    app.get('/user/:id/:username', (req, res) => {
        const params = req.params;
        res.send(params);
    })
    

托管静态资源

  1. express.static()

    express提供了一个非常好用的函数,叫做express.statis(),通过它,我们可以非常方便的创建一个静态资源服务器,例如,通过如下代码就可以将pulbic目录下的图片、css文件、js文件对外开放访问了

     ```js
     // 目录位置相对于根目录
     app.use(express.static('public'));
    
     // url访问方式 public/demo.js
     // http://127.0.0.1/demo.js
     ```
    
  2. 托管多个静态资源目录,请多次调用即可

    app.use(express.static('public'));
    app.use(express.static('static'));
    
  3. 挂载路径前缀

    如果希望访问静态资源之前,加上路径前缀

     ```js
     app.use('/public', express.static('public'));
     ```
    

    注意:/public/不可少

nodemon

  1. 作用

    在编写调试nodejs项目的时候,如果修改了项目的代码,则需要频繁的手动close掉,然后再重新启动,非常繁琐

    nodemon能够监听项目文件的改动,当代吗被修改后,会自动重新启动项目

  2. 安装

    npm install -g nodemon
    
  3. 使用

    启动命令从node index.js改为nodemon index.js

路由

  1. 概念

    指的是客户端的请求服务器处理函数之间的映射关系

    由3部分组成,请求的类型、请求的url地址、处理函数

    app.METHOD(PATH, HANDLER);
    
    app.get('/', () => console.log('get请求'));
    app.post('/', () => console.log('post请求'));
    
  2. 路由匹配过程

    每当一个请求到达服务器以后,需要先经过路由匹配,只有匹配成功之后,才会调用对应的处理函数

    1. 按照定义的先后顺序进行匹配
    2. 请求类型请求的url需要同时匹配成功,才会调用对应的处理函数
  3. 最简单的路由

    直接挂载在app

    app.get('/', () => console.log('get请求'));
    app.post('/', () => console.log('post请求'));
    
  4. 路由模块化

    为了方便对路由进行模块化的管理,不建议直接将路由挂载到app上,而是推荐将路由抽离为单独的模块

    1. 创建路由模块对应的./js文件
    2. 调用express.Router()函数创建路由对象
    3. 向路由对象上挂载具体的路由
    4. 使用module.exports()向外共享路由对象
    5. 使用app.use()函数注册路由模块
    // router.js
    const express = require('express');
    // 创建路由对象
    const router = express.Router();
    
    // 挂载路由
    router.get('/user/list', (req, res) => {
        res.send('get user list');
    })
    router.post('/user/add', (req, res) => {
        res.send('add new user');
    })
    
    // 向外导出路由对象
    module.exports = router;
    
    const express = require('express');
    const router = require('./router.js');
    
    const app = express();
    
    // 注册路由模块,全剧中间件
    app.use(router);
    
    app.listen(80, () => {
        console.log('express server running at http://127.0.0.1 and listening on 80')
    })
    
  5. 为路由添加前缀

    app.use('/api', router);
    

中间件

  1. 定义

    当一个请求到达express服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理

    本质上是一个处理函数,中间件的格式如下

     ```js
     app.get('/', (req, res, next) => {
         /* some code */
         next();
     })
     ```
    

    注意:中间件函数的形参列表,必须包含next参数,而路由处理参数只包含req和res

  2. next函数的作用

    是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由

  3. 定义中间件函数

    const express = require('express');
    const router = require('./router.js');
    
    const app = express();
    
    const mw = (req, res, next) => {
        console.log('最简单的中间件');
        next();
    }
    
    // 全局生效的中间件
    app.use(mw);
    
    app.use('/api', router);
    
    app.listen(80, () => {
        console.log('express server running at http://127.0.0.1 and listening on 80')
    })
    
  4. 全局生效的中间件

    客户端发起的任何请求,到达服务器之后,都会触发的中间件

    通过app.use(wm),即可定义一个全局生效的中间件

     ```js
     app.use(mw);
     ```
    

    定义多个全局中间件,客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用

     ```js
     app.use((req, res, next) => {
         console.log('调用了第一个中间件');
         next();
     })
    
     app.use((req, res, next) => {
         console.log('调用了第二个中间件');
         next();
     })
     ```
    

    作用

    1. 多个中间件之间,共享同一份res和req
    2. 基于这个特性,我们可以在上游的中间件中,统一为req和res对象添加自定义的属性或方法,供下游的中间件或者路由进行使用
  5. 局部生效的中间件

    不使用app.use()定义的中间件

     ```js
     const mw = (req, res, next) => {
         console.log('局部生效的中间件');
         next();
     }
    
     // 挂载路由
     router.get('/user/list', mw, (req, res) => {
         console.log('startTime', req.startTime)
         res.send({
             txt: 'get user list',
             date: req.startTime
         });
     })
     ```
    

    定义多个局部中间件

     ```js
    
     // 方式一:使用数组
     router.get('/user/list', [mw1, mw2, mw3], (req, res) => {})
     // 方式二:多个参数
     router.get('/user/list', mw1, mw2, mw3, (req, res) => {})
     ```
    
  6. 几个注意事项

    1. 一定要在路由之前注册中间件(错误级别中间件必须在所有路由之后)
    2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
    3. 一定要在处理函数中调用next()
    4. 为了防止代码逻辑混乱,调用next()函数之后不要再写额外的代码
    5. 连续调用多个中间件时,多个中间件之间,共享req和res对象
  7. 中间件的分类

    1. 应用级别的中间件

      通过app.use()app.get()app.post(),绑定到app实例上的中间件

    2. 路由级别的中间件

      绑定到express.Router()实例上的中间件

      用法和应用级别的中间件没有任何区别,只不过应用级别是绑定到app实例上路由级别是绑定到router实例上

    3. 错误级别的中间件

      专门用来捕获项目中发生的异常错误,从而防止项目异常崩溃的问题

      格式:必须有4个形参(err, req, res, next)

       ```js
       const express = require('express');
       const app = express();
      
       const mw = (err, req, res, next) => {
           console.log('发生了错误', err.message);
           res.send({
               code: 500,
               msg: err.message
           })
       }
      
       app.use('/user/list', (req, res) => {
           throw new Error('服务器错误了');
           res.send('data');
       });
      
       app.use(mw);
      
       app.listen(80, () => {
           console.log('express server running at http://127.0.0.1 and listening on 80')
       })
       ```
      

      必须要在所有路由之后

    4. express内置的中间件

      从express4.16.0版本开始,内置了3个常用的中间件

      1. express.static 快速托管静态资源的内置中间件,例如html文件、图片等(完全兼容

      2. express.json 解析json格式的请求提数据(4.16.0+

      3. express.urlencoded 解析URL-encoded格式的请求提数据(4.16.0+

        默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined

        // 配置解析application/json格式数据的内置中间件
        app.use(express.json());
        // 配置解析 application/x-www-form-urlencoded 格式数据的内置中间件
        app.use(express.urlencoded({ extended: false }));
        
    5. 第三方的中间件

      非nodejs官方内置的,而是由第三方开发出来的中间件

      比如body-parser这个中间件

      1. npm install body-parser
      2. require进行导入
      3. app.use()注册并使用

      解析form-data中的数据中间件

      const multipart = require('connect-multiparty');
      const multipartMiddleware = multipart();
      app.use(multipartMiddleware);
      
      app.post('/api/login', (req, res) => {
          const data = req.body;
          res.send(data);
      })
      
  8. 自定义中间件实现

    描述:手动模拟一个类似express.urlencoded,来解析post提交到服务器的表单数据

    实现步骤

    1. 定义中间件

    2. 监听req的data事件

      如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割后,分批发送到服务器,所以data事件可能会触发多次,每一次触发data事件时,只是完整数据的一部分,需要手动对接收到的数据进行拼接

    3. 监听req的end事件

      当请求体数据接收完毕之后,会自动触发req的end事件

    4. 使用querystring模块解析请求体数据

      nodejs内置了一个querystring模块,专门用来处理查询字符串,这个模块的parse()函数,可以轻松把查询字符串解析成对象的格式

    5. 将解析出来的数据对象挂载为req.body

    6. 将自定义的中间件封装为模块

      app.use((req, res, next) => {
          console.log('进入了自定义中间件')
          let str = "";
          req.on('data', (chunk) => {
              console.log('chunk :>> ', chunk);
              str += chunk;
          })
          req.on('end', () => {
              console.log('完整数据', str);
              const data = qs.parse(str);
              console.log('data :>> ', data);
              req.body = data;
              next();
          })
      })
      
  9. 使用express写接口

    1. get接口

      router.get('/user/list', (req, res) => {
          res.send({
              status: 200,
              data: req.query,
              msg: 'GET 请求成功',
          });
      })
      
    2. post接口

      router.post('/user/add', (req, res) => {
          res.send({
              status: 200,
              data: req.body,
              msg: 'POST 请求成功',
          });
      })
      

cors跨域资源共享

  1. 背景
    上面的get和post接口,不支持跨于请求

    解决方案主要由2种

    1. cors(主流的方案,推荐使用)
    2. jsonp(只支持get)

    使用cors中间件

     ```js
     const cors = require('cors');
     app.use(cors());
     ```
    
  2. 限制

    1. 在服务端进行配置,客户端浏览器无需做任何额外的配置,即可请求开启了cors的接口

    2. 在浏览器有兼容性,只有支持XMLHttpRequest Level2的浏览器才能用cors(IE10+,Chrome4+,FireFox3.5+)

    3. Access-Control-Allow-Origin

      // 请求头
      Access-Control-Allow-Origin: <origin> | *
      
      // 实际设置
      res.setHeader('Access-Control-Allow-Origin', '*');
      
    4. Access-Control-Allow-Headers

      默认情况下,cors仅支持客户端向服务器发送如下的9个请求头

      1. Accept
      2. Accept-Language
      3. Content-Language
      4. DPR
      5. Downlink
      6. Save-Data
      7. Viewport-Width
      8. Width
      9. Content-Type(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)

      如果客户端向服务端发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Headers对额外的请求头进行声明,否则这次请求会失败

       ```js
       // 多个请求头之间使用英文的逗号进行分割
       res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header');
       ```
      
    5. Access-Control-Allow-Methods

      默认情况下,cors仅支持客户端发起get、post、head请求

      如果客户端希望通过put、delete等方式请求服务器的资源,则需要在服务端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的http方法

       ```js
       res.setHeader('Access-Control-Allow-Methods', 'GET POST DELETE HEAD');
       res.setHeader('Access-Control-Allow-Headers', '*');
       ```
      
  3. 简单请求

    同时满足下面2个条件的请求,就是简单请求

    1. 请求方式:GET、POST、HEAD之一
    2. http头部信息u超过以下几个字段(无自定义头部字段
      1. Accept
      2. Accept-Language
      3. Content-Language
      4. DPR
      5. Downlink
      6. Save-Data
      7. Viewport-Width
      8. Width
      9. Content-Type(值仅限于text/plain、multipart/form-data、application/x-www-form-urlencoded三者之一)
  4. 预检请求

    只要符合下面任何一个条件的请求,都需要进行预检请求

    1. 请求方式为GET、POST、HEAD之外的请求
    2. 请求头包含自定义头部字段
    3. 向服务器发送了application/json格式的数据

    在浏览器和服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获取服务器是否允许该实际请求,所以这一次的OPTION请求称为预检请求,服务器成功响应预检请求之后,才会发送真正的请求,并且携带真实数据

  5. jsonp

    概念:浏览器端通过

你可能感兴趣的:(express,笔记,node.js)