Node.js-day02

模块化开发介绍

模块化基本概念

模块化:攒一台电脑(CPU、主板、内存、硬盘、显卡。。。) 活动板房:模块化;铺设铁道 编程中是否需要借助模块化的好处: 1、明显提高开发效率 2、方便后期的维护扩展(需要考虑成本) 模块化特点:高内聚,低耦合

模块化规范

早期,js中没有模块化的开发规范: 模块化要解决的问题:模块之间需要有隔离(解决命名冲突问题),并且模块之间需要有交互(解决模块之间的依赖问题)

  • 模块化介绍
    • 浏览器端模块化规范
      • AMD —> requireJS
      • CMD —> Sea.js
    • 服务器端模块化规范
      • CommonJS —> Node.js
    • ES6语音本身提供了模块化 ESM
      • Vue/React项目中会大量使用

总结:

  1. 规范是一套标准说明
  2. 按照规范实现的库才可以用于开发
  3. 前面所学的核心模块fs/path/http都遵循CommonJS的规则

Node.js模块化

Node.js中,一个js文件就是一个模块,模块之间存在隔离,并且模块之间可以交互

  • CommonJS模块化规则
    • 导出模块成员
      • module.exports = {}
    • 导入模块内部成员
      • require(参数),参数表示核心模块的名称或者自定义模块路径
  1. // common.js模块
  2. function showInfo () {
  3. console.log('nihao')
  4. }
  5. const PI = 3.14
  6. // 如何导入模块的内部成员
  7. module.exports = {
  8. showInfo: showInfo,
  9. PI: PI
  10. }
  1. // test.js 模块
  2. /*
  3. 测试模块的导入
  4. */
  5. // 导入核心模块
  6. const path = require('path')
  7. // 导入自定义模块(导入后,模块的代码会运行)
  8. const obj = require(path.join(__dirname, 'common.js'))
  9. obj.showInfo()
  10. console.log(obj.PI)

总结:

  1. 导出的目的是让别的模块使用当前模块的功能
  2. 导入的目的是为了使用别的已经做好的模块
  • 模块成员导出方式
    • 最终导出以module.exports 为准
    • exports是module.exports的别名
  1. // 关于导出的具体细节
  2. const msg = 'hello'
  3. // module.exports = {
  4. // msg: msg
  5. // }
  6. // -----------------------------
  7. // module.exports的默认值是module.exports = {}
  8. // module.exports.msg = msg
  9. // module.exports.info = 'nihao'
  10. // -----------------------------
  11. // 如下的写法仅仅会导出info,因为msg被覆盖掉了
  12. // module.exports.msg = msg
  13. // module.exports = {
  14. // info: 'nihao'
  15. // }
  16. // -----------------------------
  17. // 如下的写法会导出两个属性
  18. // module.exports = {}
  19. // module.exports = {
  20. // info: 'nihao'
  21. // }
  22. // module.exports.msg = msg
  23. // 向对象里面添加属性时,取决于此时变量指向那个对象
  24. // 不建议上述两种方式同时使用
  25. // ====================================
  26. // exports 是 module.exports 的别名,但是最终导出以 module.exports 为准
  27. // module.exports = exports = {}
  28. // exports.msg = 'hello'
  29. // exports.info = 'nihao'
  30. // ------------------------------------
  31. // 如下的写法仅仅会导出一个属性info
  32. exports.msg = 'hello'
  33. // 下面的对象会覆盖默认的对象
  34. module.exports = {
  35. info: 'nihao'
  36. }
  37. // 上述两种方式不建议一块使用

总结:如果感觉上述导出规则很繁琐,那么可以仅仅记住如下一种即可 module.exports = { info: ‘nihao’ }

  • 模块类型
    • 内置核心模块:模块名称都是固定的 fs 、path、http
    • .js模块:自定义模块
    • .json模块:自定义模块,主要用来提供数据
    • .node模块:自定义模块,这是二进制格式的,基于C/C++开发的模块,编译后形成该模块

总结:

  1. 为什么要采用模块化方式?提高开发效率(方便重用);方便后期维护和扩展
  2. 模块化是否需要标准规范?需要
  3. 都有哪些规范?AMD/CMD/CommonJS(Node.js)/ESM
  4. CommonJS的主要规则:模块的导出和导入规则
    1. 导出 module.exports/exports
    2. 导入 require(参数):参数要么是核心模块名称,要么是自定义模块路径
    3. 对于导出有一些细节需要注意:导出的多种写法
  5. 熟悉模块的类型

一个js文件是一个模块,该模块功能有限,一般一个模块只做一件事情

  • 什么是包?
    • 包可以看作是模块代码其它资源组合而成的一个更大的模块(第三方模块)
    • 包可以实现更加复杂、完善的功能模块,方便代码的复用,提高开发效率。
  • 如何复用包?
  • 如何创建一个包?
  • 包和模块之间是什么关系?包中可以包含更多模块

npm

npm介绍

npm: npm是一个网站,用于托管所有的包,方便大家发布包和搜索获取包 npm: 包管理工具的名字叫做Node.js Package Manager(简称 npm 包管理工具),这个包管理工具随着Node.js 的安装包一起被安装到了用户的电脑上。可以使用npm工具提供的命令管理包:包的下载安装、更新、卸载,发布…… 大家可以在终端中执行npm -v 命令,来查看自己电脑上所安装的npm 包管理工具的版本号:

总结:npm既是一个网站,也是一个工具

npm基本操作

npm包管理工具:安装、更新、卸载…… 命令参考文档

  • 安装包
  1. # npm install 包名
  2. # npm i 包名
  3. # npm add 包名
  4. npm install moment
  5. # npm install 包名@版本号
  6. npm i moment@2.25.3
  • 卸载包
  1. # npm uninstall 包名
  2. # npm remove 包名
  3. # npm un 包名
  4. npm uninstall moment
  5. npm remove moment
  • 更新包
    • 前提条件:项目的跟路径必须有一个package.json文件
    • 默认更新到最新版本
  1. # npm update 包名
  2. # npm up 包名
  3. npm update moment

总结:掌握包的基本安装、更新和卸载操作,熟悉相关命令的别名用法

创建一个包项目

  • 创建项目目录 mypack
  • 创建相关文件
    • js模块 包的功能实现
    • readme.md 包的使用说明
    • package.json 在包的跟目录需要有这个文件,作用是描述包
  • 进入目录执行如下命令,生成一个package.json文件
  1. npm init -y

总结:至少需要有package.json文件 这个json的作用:可以记录当前项目所依赖的第三方包。

  • 实现一个格式化日期的功能(基于moment包实现)
  1. // 实现包的功能
  2. const moment = require('moment')
  3. // 封装一个格式化日期的方法供别人使用
  4. // 函数的参数一表示需要格式化的时间(需要是日期对象)
  5. // 函数的参数二表示需要的格式(yyyy-MM-dd hh:mm:ss)
  6. function formatDate (date, style) {
  7. return moment(date).format(style)
  8. }
  9. module.exports = {
  10. formatDate: formatDate
  11. }
  • 测试代码
  1. // 测试index.js文件的方法
  2. const m = require('./index.js')
  3. const d = m.formatDate(new Date(), 'yyyy-MM-DD HH:mm:ss')
  4. console.log(d)
  5. // -----------------------------
  6. // 如果,我们的把发布到了npm网站上,别人可以按照如下方式使用
  7. // 先安装包
  8. // npm i mypack
  9. // 再使用包提供的方法实现功能
  10. // const m = require('mypack')
  11. // const d = m.formatDate(new Date, 'yyyy-MM-DD')

总结:

  1. 实现包基本结构:至少要包含package.json文件
  2. 实现一个功能并导出

包管理配置文件

包管理配置文件介绍

npm 规定,在项目的根目录下,必须有一个包管理配置文件,名字叫做 package.json,用来记录项目中安装了哪些包。 注意:运行 npm install 命令安装包的时候,npm 包管理工具会自动把包的名称和版本号,记录到 package.json 中。

  • 执行如下命令可以快速生成package.json配置文件
  1. npm init -y
  • 如果项目代码有依赖,但是没有node_modules目录是无法运行,需要先安装依赖,再运行
    • 一次性安装所有的包
  1. # 安装所有的依赖包
  2. npm install
  3. # 或者
  4. npm i

总结:如果我们得到了别人的代码,一般代码中是不包含node_modules目录的,此时无法运行,需要通过npm i 命令安装所有的依赖后,才可以继续运行项目。

配置文件常用属性

  • name 包的名称,全平台唯一
  • version 包的版本号
  • description 包的功能描述
  • main 属性作用:包默认执行的js文件(包的入口)
  • keywords 包被检索的关键字
  • author 包的作者
  • license 包遵循的开源协议
  • dependencies (生产依赖 production)
    • 如果某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中。
  • devDependencies (开发依赖 development )
    • 如果某些包只在项目开发阶段会用到,在项目上线之后不会用到,则建议把这些包记录到devDependencies 节点中。(开发依赖 development)
  1. npm i 包名 -D
  2. # 或者
  3. npm install 包名 --save-dev
  • 如何仅仅安装生产依赖
    • 默认npm install 会安装所有依赖(开发和生产依赖都安装)
    • 通过—production 选项可以仅仅安装生产依赖
  1. npm install --production
  • scripts 属性的作用:定义一些脚本命令,可以起一个别名,方便运行
    • 如果脚本的名称是start,那么运行时,可以简化为 npm start
  1. "scripts": { "dev": "node 06-module-test-1.js"},
  1. npm run dev
  • package-lock.json文件的作用
    • 记录包的依赖关系(记录所有的依赖顺序和版本等信息),防止版本更新导致的API变化的问题。

包的安装方式

  • 本地安装:把包安装到当前项目的node_modules目录中,这种包主要用于提供开发相关API。
  • 全局安装:把包安装到Node.js的安装目录中,这种包主要的作用是作为命令行的指令来用。
    • 全局安装方式就是在安装时添加 -g 即可
    • 卸载全局包时,也需要添加 -g
  1. # 全局安装npm i i5ting_toc -g# 安装完成后使用命令i5ting_toc -f outline-day03-模块化开发.md -o

解决包下载慢的问题

  • 为什么下载慢

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

  • 解决办法:使用国内镜像(副本)

淘宝在国内搭建了一个的服务器,专门把国外服务器上的包同步到国内的服务器,然后在国内提供下包的服务。从而极大的提高了下包的速度。 淘宝提供的下包服务叫做淘宝NPM 镜像,官网地址是:https://npm.taobao.org/,您可以参考官网提供的步骤,把下包的服务器从国外切换为国内的淘宝NPM 镜像。

  • 切换淘宝镜像
  1. # 安装包的时候指定一个镜像,每次都需要指定
  2. npm i moment --registry=https://registry.npm.taobao.org/
  1. # 查看当前下载包的镜像
  2. npm config get registry
  3. # 将下载包的镜像切换到淘宝镜像
  4. npm config set registry=https://registry.npm.taobao.org/
  • 使用nrm切换镜像
    • 如果需要管理各种镜像,可以使用nrm命令行工具
    • 使用nrm命令管理镜像
      • nrm ls
        • 查询所有镜像
      • nrm use 镜像名称
        • 切换镜像
  1. npm i nrm -g

开发自己的包

规范的包结构

在清楚了包的概念、以及如何下载和使用包之后,接下来,我们深入了解一下包的内部结构。

  • 一个规范的包结构,需要符合以下3 点要求:
    • 包必须以单独的目录而存在
    • 包的顶级目录下要必须包含package.json 这个包管理配置文件
    • package.json 中必须包含 name,version,main这三个属性,分别代表包的名字、版本号、包的入口。
  • 注意:以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:

开发自己的包

  • 需要实现的功能
    • 格式化日期
  • 项目初始化
    • package.json
    • index.js
    • module.js
    • README.md
  • 实现功能
  • 文档尽量写得完善

发布自己的包

  • 注册 npm 账号
  1. 访问 https://www.npmjs.com/网站,点击 sign up 按钮,进入注册用户界面
  2. 填写账号相关的信息
  3. 点击 Create an Account 按钮,注册账号(邮箱会收到一封验证邮件,需要点击收到的链接,进行身份验证)
  • 登录 npm 账号 (必须先切换到npm镜像)
    • npm 账号注册完成后,可以在终端中执行npm login 命令,依次输入用户名、密码、邮箱后,即可登录成功。
  • 把包发布到 npm 上
    • 将终端切换到包的根目录之后,运行npm publish 命令,即可将包发布到 npm 上(注意:包名不能雷同)。
  • 删除已发布的包
    • 运行 npm unpublish 包名 —force 命令,即可从 npm 删除已发布的包。

发布包注意事项

① npm unpublish 命令只能删除 72 小时以内发布的包

② npm unpublish 删除的包,在 24 小时内不允许重复发布

③ 发布包的时候要慎重,尽量不要往npm 上发布没有意义的包!

模块与包加载机制

  • 普通模块加载机制

模块在第一次加载后会被缓存。这也意味着多次调用require() 不会导致模块的代码被执行多次。 注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。

  • 内置模块的加载机制

内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。 例如,require(‘fs’) 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做fs。

  • 自定义模块的加载机制

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

如果按确切的文件名没有找到模块,则Node.js 会尝试带上 .js、 .json 或 .node 拓展名再加载。

① .js 文件会被解析为JavaScript 文本文件
② .json 文件会被解析为 JSON 文本文件
③ .node 文件会被解析为通过 process.dlopen() 加载的编译后的插件模块

  • 第三方模块(包)的加载机制

如果传递给 require() 的模块标识符不是一个内置模块,也没有以’./‘ 或 ‘../‘ 开头,则 Node.js 会从当前模块的父目录开始,尝试从它的/node_modules 目录里加载模块。如果没有找到,则移动到再上一层父目录,直到文件系统的根目录。

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

① C:\Users\itheima\project\node_modules\tools
② C:\Users\itheima\node_modules\tools
③ C:\Users\node_modules\tools
④ C:\node_modules\tools

  • 目录作为模块

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

① 在被加载的目录中查找package.json 文件,并指定一个 main 属性,作为 require() 加载的入口
② 如果目录里没有 package.json 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
③ 如果这些尝试失败,则 Node.js 将会使用默认错误报告整个模块的缺失:Error: Cannot find module ‘xxx’

W