@
目录JavaScript在使用时存在两大问题,文件依赖
和命名冲突
。
比如,js中文件的依赖是不确定的,如下
graph TD A[a.js] B[b.js] C[c.js] D[d.js] D --> C C --> B B --> A上述流程中,b.js 依赖 a.js,可 c.js 却不一定依赖 b.js ; 所以在项目开发中,我们引入一个js功能脚本,脚本功能却不起作用,我们第一时间能想到该脚本缺少依赖,可我们却不能很明显的发现这个依赖。
又比如,js中的变量覆盖问题,如下
graph TD A[a.js num = 10] B[b.js] C[c.js num = 10] D[d.js] D --> C C --> B B --> A上述流程中,在文件依赖不确定的情况下,我们也能知道 c.js 中的num会覆盖 a.js 中的 num,导致程序的不确定性
模块化 是指解决一个 复杂问题 时,自顶向下逐层 把系统划分成若干模块的过程 。对于整个系统来说,模块是可组合、分解和更换的单元。
一个功能就是一个模块,多个模块可以组成完整应用,抽离一个模块不会影响其他功能的运行。
比如:
graph TD A[app.js] --> B[user.js] A --> C[post.js] A --> D[goods.js] B --> E[addUser.js] B --> F[deleteUser.js] D --> G[findGoods.js] D --> H[addGoods.js]上述流程中,主模块 app 下分为三个子模块分分别伪 user 用户管理功能、post文章管理功能、goods商品管理功能,user下又分为addUser添加用户功能、deleteUser删除用户功能;像这样多个单一模块形成一个功能强大的整体,有序而不乱,各自相互不影响,这样就是软件的模块化开发。
总之,编程领域中的模块化,就是遵守固定的规则,把一个大文件拆成独立并互相依赖的多个小模块。
把代码进行模块化拆分的好处:
模块化规范 就是对代码进行模块化的拆分与组合时,需要遵守的那些规则。
例如:
⚫ 使用什么样的语法格式来引用模块
⚫ 在模块中使用什么样的语法格式向外暴露成员
模块化规范的好处:大家都遵守同样的模块化规范写代码,降低了沟通的成本,极大方便了各个模块之间的相互调用,利人利己。
Node.js 模块系统 | 菜鸟教程 (runoob.com)
Node.js 中根据模块来源的不同,将模块分为了 3 大类,分别是:
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,外部模块无法访问到,这种模块级别的访问限制,叫做 模块作用域。
防止了全局变量污染的问题
在每个 自定义模块中都有一个 module 对象,它里面存储了和当前模块有关的信息,打印如下:
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。
外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。
//01.custom.js const username = '张三'; function sayHello(){ console.log('你好,我是: '+ username); } module.exports.username = username; module.exports.sayHello = sayHello; //02.test.js const custom = require('./01.custom'); console.log(module); console.log(custom);
Node 提供了 exports 对象。默认情况下,exports
是module.exports
的别名(地址引用关系),默认下它们指向同一个空间地址。
//01.custom.js const username = '张三'; function sayHello(){ console.log('你好,我是: '+ username); } const sex = '男'; exports.username = username; exports.sayHello = sayHello; module.exports.sex = sex; //02.test.js const custom = require('./01.custom'); console.log(module); console.log(custom);
使用require() **可以加载需要的 内置模块、用户自定义模块、第三方模块 进行使用
// 1. 加载内置 fs模块 const fs = require('fs'); // 2. 加载自定义模块 const custom = require('./custom.js') // 3. 加载第三方模块 const moment = require('monment');
require() 模块时,得到的最终结果是 module.exports
指向的对象
exports.username = 'zs' module.exports = { gender: '男', age: '22' }
module.exports.username = 'zs' exports = { gender: '男', age: '22' }
exports.username = 'zs', module.exports.gender = '男'
exports = { username: 'zs', gender: '男' } module.exports = exports module.exports.age = '22'
Node.js 中的 第三方模块 又叫做 包。
不同于 Node.js 中的内置模块与自定义模块,包是由第三方个人或团队开发出来的,免费供所有人使用。
注意:Node.js 中的包都是免费且开源的,不需要付费即可免费下载使用。
由于 Node.js 的内置模块仅提供了一些底层的 API,导致在基于内置模块进行项目开发的时,效率很低。
包是基于内置模块封装出来的,提供了更高级、更方便的 API,极大的提高了开发效率。
包和内置模块之间的关系,类似于 jQuery 和 浏览器内置 API 之间的关系。
包的下载网址 : https://www.npmjs.com/
, 该网址由国外公司npm, lnc.所有, 该网址为: 全球最大的包共享平台。
同时供了一个地址为 https://registry.npmjs.org/
的服务器,来对外共享所有的包,可以从这个服务器上下载自己所需要的包
想要下载使用包,需要使用由 npm,lnc 提供的包管理工具(Node Package Manager,简称 → npm包管理工具) ,该工具是直接从 https://registry.npmjs.org/
服务器 把需要的包下载到本地
npm包管理工具不用单独下载,它集成在Node.js中。
终端中执行 npm -v
命令,来查看自己电脑上所安装的 npm 包管理工具的版本号:
包也会更新迭代,所以包会有对应的版本号,例如:2.24.0
;
如上,包的版本号是以“点分十进制”形式进行定义的,总共有三位数字;
其中每一位数字所代表的的含义如下:
版本号提升的规则:只要前面的版本号增长了,则后面的版本号归零。
那些被安装到项目的 node_modules 目录中的包,都是项目包。
项目包又分为两类,分别是:
在执行 npm install 命令时,如果提供了-g
参数,则会把包安装为全局包。
全局包会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules
目录下。
修改全局安装默认目录过程
修改全局安装目录
方式一:通过命令行 npm config
配置
npm config set prefix “D:\...\nodejs\node_modules\node_global_modules”
npm config set cache “D:\...\nodejs\node_modules\node_cache”
方式二:通过配置文件.npmrc
配置
在nodejs的安装目录中找到node_modules\npm\.npmrc
文件
修改如下即可:
prefix = D:\...\nodejs\node_modules\node_global cache = D:\...\nodejs\node_modules\node_cache
配置环境变量,通知电脑系统第三方下载的全局模块路径改变
首先在“系统变量”中新建,新建一个名为NODE_PATH的变量,变量值为:之前新建的用于存放全局模块的文件夹中的node_modules文件夹的绝对路径D:\...\nodejs\node_modules\node_global_modules\node_modules
在系统变量下的path
变量添加第三方全局模块安装目录D:\...\nodejs\node_modules\node_global_modules
❕注意:
一个规范的包,它的组成结构,必须符合以下 3 点要求:
注意:以上 3 点要求是一个规范的包结构必须遵守的格式,关于更多的约束,可以参考如下网址:https://yarnpkg.com/zh-Hans/docs/package-json
如果想在项目中安装指定名称的包,需要运行如下的命令
npm install 包的完整名称
可简写命令 npm install → npm i
默认情况下,使用 npm install
命令安装包的时候,会自动安装最新版本的包。
如果需要安装指定版本的包,可以在包名之后,通过@
符号指定具体的版本,例如:
npm i moment@2.22.2
初次装包完成后,在项目根目录自动下新增了 node_modules
的文件夹和 package-lock.json
的配置文件。
在项目的开发阶段和线上运营阶段,都需要依赖的第三方包,称为项目依赖,默认这些依赖都下载到项目根目录下 node_modules 文件夹下。
node_modules
一般加载了很多依赖,里面的文件夹以及文件过多过碎,当我们将项目整体拷贝给对方的时候,,传输速度会很慢很慢。用来记录 node_modules 目录下的每一个包的下载信息,例如包的名字、版本号、下载地址等。
作用:
//package-lock.json { "name": "description", "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": {...} //此字段记录了模块依赖关系 }
注意:开发者无需手动修改 node_modules文件夹 和 package-lock.json文件,它们由 npm包管理工具 自动管理维护。
npm 规定,在 项目根目录 中,必须提供一个叫做package.json
包管理配置文件。用来记录与项目有关的一些配置信息。例如:
{ //项目名称 "name": "blog", //项目版本 "version": "1.0.0", //项目描述 "description": "", //项目主入口文件 "main": "index.js", //该对象存储 命令 的别名 "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, //项目描述关键字 "keywords": [], //项目作者 "author": "", //项目遵循协议, ISC:开放源代码协议 "license": "ISC", //此字段存储项目开发依赖 "devDependencies": { "gulp": "^3.9.1", }, //此字段存储项目的开发与部署阶段需要用到的包 "dependencies": { "express": "^4.16.4", "express-art-template": "^1.0.1", } }
npm 包管理工具提供了一个快捷命令npm init
,可以在执行命令时所处的目录中创建 package.json 这个包管理配置文件,除了包的名称和版本号,其他文件内部参数值需要单独指定,
也可以通过命令npm init -y
快速创建 package.json 这个包管理配置文件,文件内部参数值都为默认值
❕注意:
上述命令只能在 英文 的目录下成功运行!所以,项目文件夹的名称一定要使用英文命名,不要使用中文,不能出现空格。
项目依赖缺失
项目中的node_modules
第三方包的体积过大!
从而导致,不方便团队成员之间共享项目源代码。
共享时剔除node_modules,可这会导致一个问题,当他人获取到你所共享源代码时会发现,项目缺少依赖无法运行!,比如:
此时若项目中没有package.json
文件,就只能去源码中寻找所需要的依赖包,这会相当麻烦
通过项目的package.json
文件能重新找回依赖。
在项目缺失依赖,而缺失的依赖相关信息已被记录到项目的 package.json 中的情况下,根据项目情况:
使用npm install
命令将项目缺失的核心依赖包重新下载。
执行 npm install 命令时, npm 包管理工具会先读取 package.json 中的 dependencies 节点, 读取到记录的所有依赖包名之后,npm 会把这些包一次性下载到项目中
使用npm install --production
命令重新下载项目开发包
开发依赖包:某些包只在项目开发阶段会用到,在项目上线之后不会用到,
建议通过命令npm i 包名 --save-dev
把包安装到项目依赖文件夹下,并把包记录到package.json
配置文件的 devDependencies
节点中。
核心依赖包:某些包在开发和项目上线之后都需要用到,则建议把这些包记录到 dependencies 节点中,npm i 包名
安装的包就被记录到该节点中。
//package.json { //其他信息省略 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", // "别名": "被替代的命令" "build": "nodemon app.js" }, }
//app.js console.log('app.js文件被执行了');
npm run 别名
执行被替代命令D:\...\test\gulp_item\description>npm run build > description@1.0.0 build D:\...\test\gulp_item\description > nodemon app.js [nodemon] 2.0.12 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node app.js` app.js文件被执行了 [nodemon] clean exit - waiting for changes before restart
可以运行 npm uninstall
命令,来卸载指定的包:
在使用 npm 下包的时候,默认从国外的 https://registry.npmjs.org/ 服务器进行下载,此时,网络数据的传输需要经过漫长的海底光缆,因此下包速度会很慢。
淘宝在国内搭建了一个服务器,专门把国外官方服务器上的包同步到国内的服务器,然后在国内提供下包的服务。从而极大的提高了下包的速度。
镜像(Mirroring):是一种文件存储形式,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像
下包的镜像源,指的就是 下包的服务器地址。
https://blog.csdn.net/weixin_45941499/article/details/120837237#nrm_49
举例实现如下三种功能
// 导入自定义包 const utils = require('gh-utils'); // 功能1:实现日期格式化 const dt = utils.dateFormat(new Date()); console.log(dt);// 输出 2021-01-20 10:09:45 //功能2: 转义 html 中的特殊字符 const htmlStr = "<h1 style='color: red;'>你好!©<span>小黄!</span></h1>" const str = utils.htmlEscape(htmlStr); // &It;h1 style="color: red;">你好! &copy;&It;span>小黄!>/span>&It;/h1> console.log(str); //功能3: 还原 html 中的特殊字符 const rawHTML = utils.htmlUnEscape(str); // 输出 <h1 style='color: red;'>你好!©<span>小黄!</span></h1> console.log(rawHTML);
{ "name": "gh-utils", "version": "1.0.0", "main": "index.js", "description": "该包提供了若干功能", "keywords": [], "license": "ISC" }
关于更多 license 许可协议相关的内容,可参考 https://www.jianshu.com/p/86251523e898
function dateFormat (dateStr){/* 省略 */} function htmlEscape(htmlStr) {/* 省略 */} function htmlUnEscape(str) {/* 省略 */} module.exports = { dateFormat }
包根目录中的README.md
文件,是包的使用说明文档。通过它,我们可以事先把包的使用说明,以 markdown 的格式写出来,方便用户参考。
README 文件中具体写什么内容,没有强制性的要求;只要能够清晰地把包的作用、用法、注意事项等描述清楚即可。
我们所创建的这个包的 README.md 文档中,会包含以下 6 项内容:
npm login
命令,依次输入用户名、密码、邮箱后,即可登录成功。npm publish
命令,即可将包发布到 npm 上(注意:包名不能雷同)。npm unpublish 包名 --force
命令,即可从 npm 删除已发布的包。模块在第一次加载后会被缓存。 这也意味着多次调用 require()
不会导致模块的代码被执行多次。
注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。
内置模块是由 Node.js 官方提供的模块,内置模块的加载优先级最高。
例如,require('fs') 始终返回内置的 fs 模块,即使在 node_modules 目录下有名字相同的包也叫做 fs。
使用 require() 加载自定义模块时,必须指定以 ./ 或 ../ 开头的路径标识符。在加载自定义模块时,如果没有指定 ./ 或 ../
这样的路径标识符,则 node 会把它当作内置模块或第三方模块进行加载。
同时,在使用 require() 导入自定义模块时,如果省略了文件的扩展名,则 Node.js 会按顺序分别尝试加载以下的文件:
① 按照确切的文件名进行加载
② 补全 .js 扩展名进行加载
③ 补全 .json 扩展名进行加载
④ 补全 .node 扩展名进行加载
⑤ 加载失败,终端报错
如果传递给 require() 的模块标识符不是一个内置模块,也没有以 ‘./’ 或 ‘../’ 开头,则 Node.js 会从当前模块的父目录开始,尝试从 /node_modules 文件夹中加载第三方模块。
如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在 'C:\Users\test\project\foo.js' 文件里调用了 require('tools'),则 Node.js 会按以下顺序查找:
①C:\Users\test\project\node_modules\tools
②C:\Users\test\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'
require('./find.js');
require('./find');
require('find');