Nodejs 简介
NodeJs 是一个基于 ChromeV8 引擎的 JavaScript 运行环境
NodeJs 安装
nvm文档手册 - nvm是一个nodejs的版本管理工具 (uihtm.com)
模块化
模块化基本概念
编程领域中的模块化,就是遵守固定的规则,把一个大文件拆分成独立的相互依赖的多个小模块
模块的概念
根据模块的来源,可以分为
- 内置模块(由 nodejs 官方提供,如 fs、path、http)
- 自定义模块(用户创建的 js 文件)
- 第三方模块(第三方提供的模块,需要下载)
加载模块
使用require()可以加载内置、自定义、第三方模块
模块作用域
在模块中定义的变量,方法等,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域,模块的作用域防止了全局变量污染问题
向外共享模块作用域中的成员
在每个模块中,都存在一个module对象,通过该对象可以将模块内的成员导出,如下代码
// 将 sayHello 函数绑定到 exports 对象上
module.exports.sayHello = () => {
console.log('hello')
}
module.exports.username = 'zhang san'
const model = require('./a') // 导入模块 a
model.sayHello() // 如果 a 中的 module 绑定了成员,那么在当前模块中这些成员可以被共享
console.log(model.username)
除了module.exports,还可以直接使用exports对象来简化代码,二者指向的是同一个地址
console.log(exports === module.exports) // true
需要注意的是,require() 一个模块时,得到的永远是
module.exports对象,如果在代码中同时对exports和module.exports的引用指向做了更改,最终导入的内容,都以module.exorts为准
为了更好的理解,有如下时间格式化的例子,首先定义时间格式化的模块
function formatDate(date) {
try {
if (date instanceof Date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hours = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return `${year}-${repairZero(month)}-${repairZero(day)} ${hours}:${repairZero(minute)}:${repairZero(second)}`
} else {
throw new TypeError('Type error, expects a parameter of type Date')
}
} catch (e) {
console.error(e)
}
}
/**
* 补 0
*/
function repairZero(dateNum) {
return dateNum > 10 ? dateNum : '0' + dateNum
}
// 导出时间格式化函数
module.exports = {
formatDate
}
引入时间格式化模块
const dateFormat = require('./dateFormat') // 引入模块
console.log(dateFormat.formatDate(new Date())); // 调用格式化函数
NPM & 包
NodeJs 中的第三方模块又叫做包,这些包由第三方的团队或者个人开源到 npm (npmjs.com) 上供开发人员使用,可以使用 npm 包管理工具来下载和管理这些包,npm 包管理工具在安装 nodejs 时已经被自动安装了,使用npm -v可查看其版本
npm 包安装
如果需要安装指定名称的包,使用如下命令(该命令会将包相关文件存放在当前项目下)
npm install 完整包名称
简写形式如下
npm i 包完整名称
以moment为例,首先是安装包,如下
npm i moment
安装包后就可以直接进行导入,导入的名称为安装包时的名称,如下
const moment = require('moment')
导入包之后,需要参照官方文档使用,在搜索引擎中搜索moment即可找到对应的官网Moment.js 中文网 (momentjs.cn),之后即可参照官网使用其提供的 api,如下
const moment = require('moment') // 导入包
console.log(moment().format('YYYY-MM-DD HH:mm:ss')); // 格式化时间
首次安装包完成在项目文件夹下创建node_module文件夹和package-lock.josn配置文件
其中node_module由于存放下载的第三方包,使用require()导入包时,也是从该文件夹下查找和加载相关资源
package-lock.json则用于记录node_module目录下每一个包的相关信息,如包名,版本号,下载地址等
node_module文件夹和package-lock.json由npm自动维护,开发人员不建议去修改
安装指定版本的包
npm install会自动安装最新的包,如果需要指定版本号,则需要通过@指定具体的版本,如下
npm i moment@2.22.2
如果已经存在对应的包,那么重新执行npm install时会替换对应的包
包的语义化版本规范
包的版本以点分十进制的形式定义,总共三位数字,如2.22.4,其中每一位代表的含义如下
- 第一位数字:大版本(例如发生底层重构时,则大版本号发生改变)
- 第二位数字:功能版本(功能上的变动,则功能版本发生改变)
- 第三位数字:bug 修复版本(修复bug后,bug 修复版本发生改变)
只要前面的版本号增长了,则后面的版本号归0,例如2.22.4的大版本发生改变,则更改后的版本为3.0.0,以此类推
包管理配置文件
npm 规定,在项目的根目录中,必须提供一个package.json的包管理配置文件,用于记录一些项目相关的配置,如
- 项目名称,版本号,描述等
- 项目中用到了那些包
- 那些包会在开发期间用到
- 那些包会在开发和部署时都需要用到
通过package.json文件最主要的是能够提供对项目的包依赖管理,其他用户在拿到项目时,只需要通过package.json文件就可以自行下载当前项目中的所有依赖包,npm提供了为当前项目快速生成package.json的指令,如下
npm init -y # 该命令只能在英文目录下成功运行,不要出现中文和空格
当项目中存在package.json文件时,之后执行npm install命令时,都会将对应的包名称和版本号记录到package.json文件中
dependencies 节点
首次创建package.json时,如果项目中没有依赖,那么dependencies节点不存在,该节点专门用于记录npm install的包名和版本号,如下
{
"name": "nodejs-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"moment": "^2.22.2" // 包名为 moment,版本号为 2.22.2
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
一次性安装所有的包
当项目中存在package.json文件时,直接运行npm install或npm i即可一次性安装所有的包依赖
卸载包
使用npm uninstall 包名命令来卸载指定的包,同时对应的package.json中的对应包也会被删除
devDependencies 节点
如果某些包只会在开发阶段用上,那么可以将这些包挂载到devDependencies节点中,否则还是应该挂载到dependencies 节点中,想要挂载到devDependencies中,需要执行如下包安装命令
npm i 包名 -D
包的分类
npm 安装的包主要分为两类,包括项目包、全局包,项目包主要在当前项目中使用,而如果在执行npm install命令时,提供了-g参数,如下
npm install 包名 -g
那么该包可以称之为全局包,如果要卸载全局包,则使用如下命令
npm uninstall 包名 -g
注意,只有工具性质的包才需要全局安装
规范的包结构
- 包必须以单独的目录存在
- 包的顶级目录下必须包含
package.json文件 - package.json 中必须包含
nameversinomain三个属性,分别包含包名版本号包的主入口,其中main中规定的主入口会在require()导入包时被加载
根据以上规范,有如下自定义模块的例子
首先新建目录my-tools,在该目录下初始化一个package.json文件,如下
npm init -y
根据package.json文件中的main属性,在根目录下新建index.js文件
根目录下再新建src目录,该目录用于存放项目代码的 js 文件,如下
/**
* 时间格式化
* @param date
* @return {string}
*/
function formatDate(date) {
try {
if (date instanceof Date) {
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hours = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return `${year}-${repairZero(month)}-${repairZero(day)} ${hours}:${repairZero(minute)}:${repairZero(second)}`
} else {
throw new TypeError('Type error, expects a parameter of type Date')
}
} catch (e) {
console.error(e)
}
}
/**
* 补 0
*/
function repairZero(dateNum) {
return dateNum > 10 ? dateNum : '0' + dateNum
}
module.exports = {
formatDate
}
/**
* html 标签替换为转义字符
* @param htmlStr
* @return {*}
*/
function htmlEscape(htmlStr) {
return htmlStr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
/**
* 还原被替换后的 html
* @param htmlEscapeStr
* @return {*}
*/
function unEscape(htmlEscapeStr) {
return htmlEscapeStr.replace(/<|>|"|&/g, (match) => {
switch (match) {
case '<':
return '<'
case '>':
return '>'
case '"':
return '"'
case '&':
return '&'
}
})
}
module.exports = {
htmlEscape,
unEscape
}
在index.js文件中,引入src目录下的相关文件,因为package.json中的main属性规定了index.js作为包的入口,那么在入口中,就可以将需要对外暴露的接口使用module对象导出,代码如下
const dateFormat = require('./src/dateFormat') // 引入自定义的时间格式化js文件
const htmlEscape = require('./src/htmlEscape') // 引入自定义的 html 转移js文件
module.exports = {
// 导出相关函数
...dateFormat, ...htmlEscape
}
发布包
以上完成了一个基本的包结构搭建,现在可以将该包发布到npm,首先需要注册一个npm账号;之后在本地的命令行中执行npm login命令完成登录,注意,登录之前需要将 npm 源设置为官方源,如下
# 设置为官方源
npm config set registry https://registry.npmjs.org/
# 设置为淘宝源
npm config set registry http://registry.npm.taobao.org/
命令行登录时,还需要到邮箱验证一次验证码,登录成功后,会出现Logged in as dhj4612 on https://registry.npmjs.org/.字样
切换到需要发布包项目的根目录下,执行如下命令发布包
npm publish
注意:在这之前,需要检查npm上是否有同名的包,如果有包名冲突的情况,会有如下错误提示
Package name too similar to existing package my_tools; try renaming your package to '@dhj4612/my-tools' and publishing with 'npm publish --access=public' instead
删除以发布的包
如果想要删除已发布的包,可以执行如下命令从(只能删除 72 小时内发布的包且删除的包不能在 24 小时内重新发布)
npm unpublish 包名 --force
模块的加载机制
优先从缓存中加载,模块在第一次加载后,会被缓存,这也意味着多次调用require()不会导致代码被重复执行多次,不论是内置模块,用户模块或则是第三方模块,都会优先从缓存中加载
内置模块加载的优先级最高,如果非内置模块中同样存在fs模块,那么在加载时,优先加载内置模块中的fs
使用require()加载自定义模块时,必须指定以./或../等开头的路径标识符,否则的话,NodeJs 会将其当作内置模块进行加载,例如require('自定义模块')就会被当作内置模块加载
在导入自定义模块时,如果省略了文件的扩展名,NodeJs会按照顺序尝试加载以下文件
- 按照确切文件名进行加载
- 补全 .js 的扩展名进行加载
- 补全 .json 的扩展名进行加载
- 补全 .node 扩展名进行加载
- 最终加载失败,终端报错
如果传递给require()的模块标识符没有以./或../开头,也不是一个内置模块,那么 NodeJs 会从当前模块的父目录开始,尝试从 /node_module文件夹进行加载,如果依然没有,那么继续移动到上一级目录,直至到达当前项目的根目录
当require()中传入的模块标识符为一个目录时,有如下加载方式
- 在目录中查找
package.json文件并寻找main属性作为require()加载的入口 - 如果没有
package.json或者main属性指向的入口文件不存在,则 NodeJs 会试图加载根目录下的index.js文件 - 以上都失败,则在终端打印错误信息:
Error Cannot find module xxx