node.js学习笔记(第三天)

170 阅读16分钟

3 包管理配置文件

npm 规定,在项目根目录中,必须提供一个叫做 package.json包管理配置文件。用来记录与项目有关的一些配置(package-lock.json记录的是包的具体版本信息)

信息。例如:

  • 项目的名称、版本号、描述等
  • 项目中都用到了哪些包
  • 哪些包只在开发期间会用到
  • 那些包在开发和部署时都需要用到

3.1多人协作问题

第三方包的体积过大,不方便团队成员之间共享项目源代码

解决方案:共享时剔除node_modules

3.2 如何记录项目中安装了哪些包

在项目根目录中,创建一个叫做 package.json 的配置文件,即可用来记录项目中安装了哪些包。从而方便剔除node_modules 目录之后,在团队成员之间共享项目的源代码。

注意:今后在项目开发中,一定要把 node_modules 文件夹,添加到 .gitignore 忽略文件中。

3.3 快速创建 package.json

npm 包管理工具提供了一个快捷命令,可以在执行命令时所处的目录中,快速创建 package.json 这个包管理配置文件:

npm init -y # 作用,在命令执行时的所在文件创建 package.json 文件

注意:

  1. 上述命令只能在英文的目录(文件名) 下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格。
  2. 运行 npm install 命令安装包的时候,npm 包管理工具会自动把包的名称和版本号,记录到 package.json 中

3.4 dependencies 节点

package.json 文件中,有一个 dependencies 节点,专门用来记录您使用 npm install

命令安装了哪些包

安装多个的包的时候,可以用空格隔开(例如 moment 和 art-templ 两个包)

npm i stall moment art-templ

3.5 一次性安装所有的包

当我们拿到一个剔除了 node_modules 的项目之后,需要先把所有的包下载到项目中,才能将项目运行起来,否则会报找不到包的错误

可以运行 npm install 命令(或 npm i)一次性安装所有的依赖包:

npm install

执行命令之后,npm包管理工具会先读取package.json中的dependencies节点,读取到记录的所有依赖包名称和版本之后,一次性把包下载包项目中

3.6 卸载包

可以运行 npm uninstall 命令,来卸载指定的包(要具体的包名)

npm uninstall moment

注意:npm uninstall 命令执行成功后,会把卸载的包,自动从 package.json 的 dependencies 中移除掉

3.7 devDependencies节点

如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到 devDependencies 节点中。

与之对应的,如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中

可以使用如下的命令,将包记录到 devDependencies 节点中:

# 简写
npm i -D moment
# 完整写法
npm install --save-dev

-D、--save-dev 和包名的位置不影响

4.解决包下载速度慢问题

4.1 包下载速度慢原因

在使用 npm 下包的时候,默认从国外的 registry.npmjs.org/ 服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。

4.2 淘宝npm镜像服务器

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

用户从国内的服务器下载包,国内的服务器定时把npm官方的包同步到国内服务器

镜像(mirroring)是一种文件存储形式,一个磁盘上的数据在另外一个磁盘上存在一个完全相同的副本即为镜像

4.3 切换npm下包镜像源

下包镜像源指的是下包的服务器地址

# 查看当前的下包镜像源
npm config get registry
​
# 切换镜像源
npm config set registry=http://registry.npm.taobao.org/
​
# 查看镜像源是否更改成功
npm config get registry

4.4 nrm

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

# 利用npm包管理器,将nrm安装为全局可用的工具
npm i nrm -g
# 查看所有可用的镜像源  带星号的是当前的镜像源
nrm ls
# 切换信号源
nrm use taobao

5.包的分类

使用 npm 包管理工具下载的包,共分为两大类,分别是:

  • 项目包
  • 全局包

5.1 项目包

那些被安装到项目的 node_modules 目录中的包,都是项目包。

项目包又分为两类,分别是:

  • 开发依赖包(被记录到 devDependencies 节点中的包,只在开发期间会用到)
  • 核心依赖包(被记录到 dependencies 节点中的包,在开发期间和项目上线之后都会用到)

5.2 全局包

在执行 npm install 命令时,如果提供了 -g 参数,则会把包安装为全局包。

全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules 目录下

npm install 包名 -g # 全局安装指定的包
npm uninstall 包名 -g # 全局卸载指定的包

注意:

  1. 只有工具性质的包,才有全局安装的必要性。因为它们提供了好用的终端命令。
  2. 判断某个包是否需要全局安装后才能使用,可以参考官方提供(npmjs.com)的使用说明即可。

5.3 i5ting_toc

i5ting_toc 是一个可以把 md 文档转为 html 页面的小工具,使用步骤如下:

# 安装全局包
npm install -g i5ting_toc
# -f 指定文件路径  -o 能够自动打开默认浏览器
i5ting_toc -f md格式文件路径 -o

6.包的规范化结构

在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构一个规范的包,它的组成结构,必须符合以下 3 点要求:

  • 包必须以单独的目录(文件)而存在
  • 包的顶级目录下要必须包含 package.json 这个包管理配置文件
  • package.json 中必须包含 name,version,main 这三个属性,分别代表包的名字、版本号、包的入口。

require 的时候导入的是 main 指向的文件

以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:yarnpkg.com/zh-Hans/doc…

7.开发属于自己的包

7.1 需要实现的功能

  • 格式化日期
  • 转义 HTML 中的特殊字符
  • 还原 HTML 中的特殊字符

7.2 初始化包的基本结构

  1. 新建文件夹,作为包的根目录
  2. 在文件夹中,新建如下三个文件:
  • package.json (包管理配置文件)
  • index.js (包的入口文件)
  • README.md (包的说明文档)

7.3 初始化package.json

{
    "name": "chenchuyuan-tools",
    "version": "1.0.0",
    "main": "index.js",
    "description": "提供了格式化时间 转义html功能",
    "keywords": ["chenchuyuan","escape","dateFormat"],
    "license": "ISC"
}

name:包名。是真正的名字,不是文件夹名,文件夹名可以随便改

version:版本号

main:入口文件

description:简略描述

keywords:用于关键词搜索

license:协议,ISC是建议的默认协议

关于更多 license 许可协议相关的内容,可参考www.jianshu.com/p/86251523e…

7.4 在 index.js 中定义格式化时间的方法

// 格式化时间
function dateFormat(dateStr){
    const dt = new Date(dateStr);
    
    const year = dt.getFullYear();
    const month = padZero(dt.getMonth() + 1);
    const day = padZero(dt.getDay());
​
    const hh = padZero(dt.getHours());
    const mm = padZero(dt.getMinutes());
    const ss = padZero(dt.getSeconds());
​
    return `${year}-${month}-${day} ${hh}:${mm}:${ss}`;
}
​
// 补零
function padZero(n){
    return n > 9 ? n : '0' + n;
}
​
// 暴露方法
module.exports = {
    dateFormat
}

7.5 在index定义转义html的方法

// 转义html
function htmlEscape(htmlStr){
    // g 表示对全部内容进行匹配
    // match 表示匹配成功的内容(单项)
    return htmlStr.replace(/<|>|"|&/g,(match)=>{
        switch(match){
            case '<':
                return '&lt;';
            case '>':
                return '&gt;';
            case '"':
                return '&quot;';
            case '&':
                return '&amp;';
        }
    })
}

7.6 在 index.js 中定义还原 HTML 的方法

// 还原html
function htmlUnEscape(htmlStr){
    // g 表示对全部内容进行匹配
    // match 表示匹配成功的内容(单项)
    return htmlStr.replace(/&lt;|&gt;|&quot;|&amp;/g,(match)=>{
        switch(match){
            case '&lt;':
                return '<';
            case '&gt;':
                return '>';
            case '&quot;':
                return '"';
            case '&amp;':
                return '&';
        }
    })
}

7.7 将不同功能进行模块化拆分

  1. 将格式化时间的功能,拆分到 src -> dateFormat.js 中
  2. 将处理 HTML 字符串的功能,拆分到 src -> htmlEscape.js 中
  3. 在 index.js 中,导入两个模块,得到需要向外共享的方法
  4. 在 index.js 中,使用 module.exports 把对应的方法共享出去

dateFormat 代码如下:

// 格式化时间
function dateFormat(dateStr){
    const dt = new Date(dateStr);
    
    const year = dt.getFullYear();
    const month = padZero(dt.getMonth() + 1);
    const day = padZero(dt.getDay());
​
    const hh = padZero(dt.getHours());
    const mm = padZero(dt.getMinutes());
    const ss = padZero(dt.getSeconds());
​
    return `${year}-${month}-${day} ${hh}:${mm}:${ss}`;
}
​
// 补零
function padZero(n){
    return n > 9 ? n : '0' + n;
}
​
module.exports = {
    dateFormat
}

htmlEscape 代码如下:

// 转义html
function htmlEscape(htmlStr){
    // g 表示对全部内容进行匹配
    // match 表示匹配成功的内容(单项)
    return htmlStr.replace(/<|>|"|&/g,(match)=>{
        switch(match){
            case '<':
                return '&lt;';
            case '>':
                return '&gt;';
            case '"':
                return '&quot;';
            case '&':
                return '&amp;';
        }
    })
}
​
// 还原html
function htmlUnEscape(htmlStr){
    // g 表示对全部内容进行匹配
    // match 表示匹配成功的内容(单项)
    return htmlStr.replace(/&lt;|&gt;|&quot;|&amp;/g,(match)=>{
        switch(match){
            case '&lt;':
                return '<';
            case '&gt;':
                return '>';
            case '&quot;':
                return '"';
            case '&amp;':
                return '&';
        }
    })
}
​
module.exports = {
    htmlEscape,
    htmlUnEscape
}

index 代码如下:

const dateFormat = require('./src/dateFormat');
const htmlEscape = require('./src/htmlEscape')
​
​
​
// 暴露方法
module.exports = {
    // 使用 es6的解构语法,这里相当于把导入的模块方法一个个写入
    ...dateFormat,
    ...htmlEscape
}

7.8 编写包的说明文档

包根目录中的 README.md 文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的格式写出来,方便用户参考。

README 文件中具体写什么内容,没有强制性的要求;只要能够清晰地把包的作用、用法、注意事项等描述清楚即可。

我们所创建的这个包的 README.md 文档中,会包含以下 6 项内容:

安装方式、导入方式、格式化时间、转义 HTML 中的特殊字符、还原 HTML 中的特殊字符、开源协议

8.发布包

8.1 注册账号

  1. 访问 www.npmjs.com/ 网站,点击 sign up 按钮,进入注册用户界面
  2. 填写账号相关的信息:Full Name、Public Email、Username、Password
  3. 点击 Create an Account 按钮,注册账号
  4. 登录邮箱,点击验证链接,进行账号的验证

8.2 登录npm账号

npm 账号注册完成后,可以在终端中执行 npm login 命令,依次输入用户名、密码、邮箱后,即可登录成功

在运行 npm login 命令之前,必须先把下包的服务器地址切换为 npm 的官方服务器。否则会导致发布包失败!

8.3 把包发布到npm上

将终端切换到包的根目录之后,运行 npm publish 命令,即可将包发布到 npm 上

注意:包名不能雷同

8.4 删除已发布的包

运行 npm unpublish 包名 --force 命令,即可从 npm 删除已发布的包

  • npm unpublish 命令只能删除 72 小时以内发布的包
  • npm unpublish 删除的包,在 24 小时内不允许重复发布
  • 发布包的时候要慎重,尽量不要往 npm 上发布没有意义的包

模块的加载机制

1.优先从缓存中加载

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

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

2.内置模块的加载机制

内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高

例如,require('fs') 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs。

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

使用 require() 加载自定义模块时,必须指定以 ./ 或 ../ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 ../ 这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载

同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:

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

4.第三方模块的加载机制

如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘../’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块

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

例如,假设在 'C:\Users\itheima\project\foo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:

  1. C:\Users\itheima\project\node_modules\tools
  2. C:\Users\itheima\node_modules\tools
  3. C:\Users\node_modules\tools
  4. C:\node_modules\tools

foo.js 是当前模块

5.目录作为模块

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

  1. 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require() 加载的入口
  2. 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
  3. 如果以上两步都失败了,则 Node.js 会在终端打印错误消息,报告模块的缺失:Error: Cannot find module 'xxx‘

初识express

1.express简介

1.1 什么是express

官方给出的概念:Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架

通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的

Express 的本质:就是一个 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法

Express 的中文官网: www.expressjs.com.cn/

1.2 进一步理解express

  1. 思考:不使用 Express 能否创建 Web 服务器?

    答案:能,使用 Node.js 提供的原生 http 模块即可。

  2. 思考:既生瑜何生亮(有了 http 内置模块,为什么还有用 Express)?

    答案:http 内置模块用起来很复杂,开发效率低;Express 是基于内置的 http 模块进一步封装出来的,能够极大的提高开发效率。

  3. 思考:http 内置模块与 Express 是什么关系?

    答案:类似于浏览器中 Web API 和 jQuery 的关系。后者是基于前者进一步封装出来的

1.3 express能做什么

对于前端程序员来说,最常见的两种服务器,分别是:

  • Web 网站服务器:专门对外提供 Web 网页资源的服务器(就是防止网页资源的地方)
  • API 接口服务器:专门对外提供 API 接口的服务器(就是提供网页里边数据接口的地方)

使用 Express,我们可以方便、快速的创建 Web 网站的服务器或 API 接口的服务器

2.express的基本使用

2.1 安装

项目所处的目录中,运行如下的终端命令,即可将 express 安装到项目中使用,@用于指定版本

npm install express@4.17.1

2.2 创建基本的web服务器

// 1.导入express
const express = require('express');
​
// 2.创建web服务器
const app = express();
​
// 3.使用listen启动web服务器,其参数为端口号和启动成功的回调函数
app.listen(8083,()=>{
    console.log('服务器启动成功 http://127.0.0.1:8083')
})

2.3 监听get请求

通过 app.get() 方法,可以监听客户端的 GET 请求,具体的语法格式如下

// 4.监听get方法
app.get('/',(req,res)=>{
    
})

2.4 监听post请求

通过 app.post() 方法,可以监听客户端的 POST 请求,具体的语法格式如下:

// 5.监听post方法
app.post('/',(req,res)=>{
  
})
  • 第一个参数为请求的url地址
  • 第二个参数为请求对应的处理函数 req:请求对象(包含了与请求相关的属性和方法) res:响应对象(包含了与响应相关的属性和方法)

2.5 把内容响应给客户端

通过 res.send() 方法,可以把处理好的内容,发送给客户端:

// 4.监听get方法
app.get('/',(req,res)=>{
    // 服务器通过send将内容响应给客户端
    res.send({name:"ccy",age:18})
})
​
// 5.监听post方法
app.post('/',(req,res)=>{
    res.send('监听post方法成功!')
})

send相比于原生的end功能更强大,send能发送json数据也能发送字符串

2.6 获取url中携带的查询参数

通过 req.query 对象,可以访问到客户端通过查询字符串的形式,发送到服务器的参数

// 6.通过req.query获取查询参数
app.get('/name',(req,res)=>{
    // 打印
    console.log(req.query)
    // 响应给服务器
    res.send(req.query)
})
  • req.query 默认是一个空对象
  • 客户端通过 name/ ?name=haha&age=18 这种查询字符串的形式发送到服务器的方式,服务器能够通过req.query对象访问到,比如req.query.name

2.7 获取url中的动态参数

通过 req.params 对象,可以访问到 URL 中,通过 : 匹配到的动态参数

// 7.通过req.params获取url中的动态参数
// url中可以通过:参数名的形式,匹配动态参数值
app.get('/user/:id/:name',(req,res)=>{
    console.log(req.params);// user/1/2 所匹配的为 { id: '1', name: '2'}
    res.send(req.params)
})
  • req.params 默认是一个空对象
  • 对象里面存放着:动态匹配到的参数值
  • 动态参数可以是单个或者多个

3.托管静态资源

3.1 express.static

express 提供了一个非常好用的函数,叫做 express.static(),通过它,我们可以非常方便地创建一个静态资源服务器

例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:

const express = require('express');
​
const app = express();
​
app.use(express.static('./clock'))
​
app.listen(8083,()=>{
    console.log('启动服务器成功 http://127.0.0.1:8083');
})

通过上方的代码,我们就能够访问到clock目录下的文件了。例如:

http://127.0.0.1:8083/index.html

http://127.0.0.1:8083/index.css

http://127.0.0.1:8083/index.js

Express 在指定的静态目录中查找文件,并对外提供资源的访问路径。

因此,存放静态文件的目录名不会出现在 URL 中

所以上方的url不是http://127.0.0.1:8083/clock/index.html

3.2 托管多个静态资源

如果要托管多个静态资源目录,请多次调用 express.static() 函数:

app.use(express.static('./clock'))
app.use(express.static('./files'))

访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件

例如当两个目录都有index.js文件的时候,客户端访问到的是第一个添加目录里边的文件(这里就是访问clock里面的index.js)

3.3 挂在路径前缀

如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:

app.use('/clock',express.static('./clock'))
app.use(express.static('./files'))

你就可以通过带有 /clock 前缀地址来访问 clock 目录中的文件了:

http://127.0.0.1:8083/clock/index.html

http://127.0.0.1:8083/clock/index.css

http://127.0.0.1:8083/clock/index.js

如果不加这个前缀的话,访问的index.js会是files目录下的index.js

4.nodemon

4.1 为什么要使用nodemon

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

现在,我们可以使用 nodemon(www.npmjs.com/package/nod…) 这个工具,它能够监听项目文件的变动,当代码被修改后,nodemon 会自动帮我们重启项目,极大方便了开发和调试。

4.2 安装 nodemon

npm install -g nodemon

4.3 使用nodemon

node app.js
# 用下方的命令来代替上方的命令来启动项目
nodemon app.js