一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情
02_模块化与npm
核心目标
- 理解模块化的定义
- 了解模块化的发展
- 能自定义nodejs中的模块
- 能下载和使用第三方模块
模块化-理解模块化
提问:在浏览器中,我们写代码时,
根目录
├── index.html # 主页的页面
├── index.js # 主页需要用到的js代码
│ └── getData() # 是index.js中定义的函数
└── tool.js # 为整个项目提供公共方法
└── doSomething(){ } # 是tool.js中定义的函数
对于如上的代码结构,如何让index.js中的getData去使用tool.js中的doSomething()函数?
- 上面的代码写法,会有什么问题?
- 为什么不能直接让index.js来直接使用tool.js的函数,而要通过index.html来统一管理一下呢?
原因很简单:es5中不支持模块化(不能直接在一个js文件中去引用另一个js文件的方法,必须要通过第三个文件.html来引入)
啥是模块化?
一个js文件可以引入其他的js文件,能使用引入的js文件的中的变量、数据,这种特性就称为模块化。
使用模块化开发可以很好的解决变量、函数名冲突问题,也能灵活的解决文件依赖问题。
模块化的发展
- 以前
es5不支持模块化,让前端人员很为难。为了让支持模块化,我们一般会借用第三方库来实现:
-
- sea.js. www.zhangxinxu.com/sp/seajs/
- require.js. requirejs.org/
- 现在
-
- es6原生语法也支持模块化(并不表示浏览器也直接支持模块化 --- 需要单独设置一下)
- Nodejs内部也支持模块化(与es6的模块化有些不同之处),具体的语法在后面来介绍。
常见的模块化规范有
- CommonJS 规范(nodejs 默认支持的)
- ES6 模块化规范
- CMD 和 AMD 模块化规范(不再推荐使用)
模块化-体验模块化
演示性的学习,让大家了解几种模块化的写法
- nodejs的模块化
- es6的模块化
- sea.js
- require.js
nodejs中的模块分类-复习
每个模块都是一个独立的js文件。每个模块都可以完成特定的功能,我们需要时就去引入它们,并调用。不需要时也不需要管它。(理解于浏览器的js中的Math对象)
nodejs模块的分类
- 核心模块
-
- 就是nodejs自带的模块,在安装完nodejs之后,就可以随意使用啦。相当于学习js时使用的Array对象。
- 例:fs, http, querystring, path
- 全部模块的源代码 github.com/nodejs/node…
- 自定义模块
-
- 程序员自己写的模块。就相当于我们在学习js时的自定义函数。
- 第三方模块
-
- 其他程序员写好的模块。nodejs生态提供了一个专门的工具npm来管理第三方模块,后面我们会专门讲到。
- 相当于别人写好的函数或者库。例如我们前面学习的JQuery库,arttemplate等。
自定义模块-基本介绍
目标
掌握自定义模块的使用背景和步骤
背景
我们对代码的封装是以模块(一个独立的.js文件)为单位进行的。一般的做法是实现好某一个功能之后,封装成一个模块,然后在其它文件中使用这个模块。
类比于js自定义函数,自定义模块的使用场景是:
- 代码需要在项目重用
- 代码需要提供给他人使用
- 代码虽然不需要重用,但封装成模块有利于优化代码结构,方便后期维护与扩展
步骤
一共有两步:
- 定义模块。就是创建一个js文件, 对外导出我们希望导出的内容。
- 使用模块。在需要使用的地方去导入模块文件。
Node.js 中的 CommonJS 的模块化规范
CommonJS 规范
CommonJS 规范中主要规定了以下 3 项内容:
- 导入其它模块时,统一使用 require() 函数。
- 每个 .js 文件,都是一个独立的模块,模块内的成员都是私有的。
- 在每个 JS 模块中,使用 module.exports 向外共享成员。
图示
根目录
├── user.js # 定义模块
└── test.js # 引入user.js模块
注意
- module.exports 是固定写法,一般放在文件的最末尾,也只用一次。
- module.exports表示当前模块要暴露给其它模块的功能。
-
- 它可以导出对象,数组,函数等等类型。为了方便组织代码,导出对象的情况比较多。
- 不必要导出所有函数,对象,数组等。那些没有导出的部分就相当于这个模块的内部变量了。在下图中变量1,函数1,数组就是模块内部的数据,在外部无法别访问到。
小结
所谓定义模块,就是新建一个js文件。文件取名时,要注意一下:
- 一般会用模块名给它命名。类比于核心模块,例如,你的模块叫myModule,则这个js文件最好叫myModule.js
- 不要与核心模块的名字重复了。就像我们定义变量不要与核心关键字重名,你自己定义的模块也不要叫fs.js,因为nodejs有一个核心模块就叫fs.js。
- 要记得导出模块
自定义模块-实操
假设在工作中我们自己定义了一些工具方法, 而这些工具方法是可以在其他项目中使用的。此时我们就可以采用自定义模块的方式来处理。
素材
以下是一个对时间进行格式化的函数
const formatDate = (dateTime) => {
const date = new Date(dateTime) // 转换成Data();
const y = date.getFullYear()
const m = date.getMonth() + 1
m = m < 10 ? '0' + m : m
const d = date.getDate()
d = d < 10 ? ('0' + d) : d
return [y,m,d].join('-')
}
这个函数如何才能被复用呢?
我们以此为基础,开始做自定义模块的动作
思路
根目录
├── tool.js # 把我们要用到的公共方法 封装在这个文件中
└── test.js # 测试tool.js中封装的方法
操作
定义模块
// 方法定义在这里
const formatDate = (dateTime) => {
const date = new Date(dateTime) // 转换成Data();
const y = date.getFullYear()
const m = date.getMonth() + 1
m = m < 10 ? '0' + m : m
const d = date.getDate()
d = d < 10 ? ('0' + d) : d
return [y,m,d].join('-')
}
// 通过module.exports来导出模块
module.exports = {
formatDate
};
记得要导出模块: 在文件尾部,使用module.exports来导出模块。
导入模块
完成了模块定义之后,我们就可以在另一个文件中使用这个模块了。
基本步骤是:
-
导入模块;格式:const 模块名 = require('./模块路径')
-
先打出来看看;
当一个模块被成功引入之后,就可以类比使用核心模块的过程一样去使用它们了。
下面是示例代码:
// test.js
// 1. 导入模块
// 注意这里使用的是相对路径。可省略.js.
const tool = require('./tool.js');
// 在使用之前请先打印出来看看
console.log(tool);
// 2. 使用模块中的方法
注意:
- 使用require语句引入你定义好的模块
- 这里必须使用
相对路径的格式去引入自定义模块。"./" 也不能省略。
练习
将如下的方法补充到上面封装的tool.js中,并导出使用
//tools.js
const relativeTime = (oldTime) => {
const t = new Date(oldTime)
// Date.now():现在的时间戳(毫秒)
// t.getTime():旧时间的时间戳(毫秒)
const diff = Date.now() - t.getTime() // 相隔多少毫秒
// Math.floor 向下取整: 1.7年 ---> 1年前
const year = Math.floor(diff / (1000 * 3600 * 24 * 365))
if (year) {
return `${year}年前`
}
const month = Math.floor(diff / (1000 * 3600 * 24 * 30))
if (month) {
return `${month}月前`
}
const day = Math.floor(diff / (1000 * 3600 * 24))
if (day) {
return `${day}天前`
}
const hour = Math.floor(diff / (1000 * 3600))
if (hour) {
return `${hour}小时前`
}
const minute = Math.floor(diff / (1000 * 60))
if (minute) {
return `${minute}分钟前`
} else {
return '刚刚'
}
}
导出模块的两种方式
目标
了解两种导出的方式(难点,不是重点)
导出模块有两种方式
- exports
- module.exports
// 定义方法,常量
const myPI = 3.14
const add = (a,b) => a + b;
// 导出,两种方法任意都可以
// 方法一:
exports.myPI = myPI
exports.add = add
// 方法二:
module.exports.myPI = myPI
module.exports.add = add
// 方法二(变形)
module.exports = {
myPI,
add
}
在阅读其它人的代码时,可能会遇到这两种不同的写法。所以我们还是有必要了解一下的。
cookie模块,body-parser模块,arry-flatten模块中的导出均采用不同的方式。
两个对象的关系
- 初始exports和module.exports是指向同一块内存区域,其内容都是一个空对象。(exports是module.exports的别名)即:
exports === module.exports // 输出是 true
所以下面两种写法的效果是一样的:
// 写法1 mymodule.js
exports.f = function(){ }
exports.pi = 3.1415926
// 写法2 mymodule.js
module.exports.f = function(){ }
module.exports.pi = 3.1415926
- 在定义模块时:
如果直接给exports对象赋值(例如:exports={a:1,b:2}),此时,exports就不会再指向module.exports,而转而指向这个新对象,此时,exports与module.exports不是同一个对象。
在引入某模块时:以该模块代码中module.exports指向的内容为准。
图示
结论
在导出模块过程中,建议只用一种方式(建议直接使用module.exports)
了解npm和包
npm
npm全称Node Package Manager(node 包管理器),它的诞生是为了解决 Node 中第三方包共享的问题。npm不需要单独安装。在安装Node的时候,会连带自动安装npm。npm -v检查安装的情况。- 官网
当我们谈到npm时,我们在说两个东西:
- 命令行工具。这个工具在安装node时,已经自动安装过了,不需要额外安装。
- npm网站。这是一个第三方模块的"不花钱的模块超市",我们可以自由地下载,上传模块。
不花钱的模块超市
npm网站收集了前端的各种工具. 之前学习过:
jQuery, bootStrap, flexible.js, arttemplate.js, layui.js, echarts.js........
你是如何下载的?官网下载?
有没有一个想法:在一个地方下载所有的库(模块.....)
包(package)与模块关系
npm网站上去下载我们的需要的代码时,它们是以"包"这种结构放在npm网站上的。先来了解下包和模块的关系。
- nodejs中一个模块就是一个单独的js文件
- Node.js 中的第三方模块,又叫做包、第三方包、依赖包
- 包是多个模块的集合。一个模块的功能比较单一,所以一个包一般会包含多个模块。
- npm 管理的单位是包。类似于网站和网页的区别:一个网站一般会包含多个网页。
找自己需要的包
npm 公司提供了一个非常著名的搜包网站:https://www/npmjs.com/。它是全球最大的第三方包共享平台。
小结
-
npm中的第三方模块是以______的格式放在的npm网站上的,供程序员______下载
-
一个包内部可能会有______个模块
npm下载使用包
目标
掌握下载包的使用方式,了解dayjs的基本使用
步骤
分成三步:
- 初始化项目。npm init 如果之前已经初始化,则可以省略。
- 安装包。 npm install 包名。[注意:保持联网的状态哈]
- 引入包,使用
第一步:初始化项目
这里提到的项目并不是某个具体的功能,只是要创建一个空文件夹即可(注意,不要起中文名字哈)。
进入到项目所在的根目录下,启动小黑窗(建议:按下shift键,点击右键,在弹出的菜单中选择 “在此处打开命令行”)
输入如下命令:
npm init --yes
// --与yes之间没有空格, -- 与init之间有空格
// 或者是 npm init -y
init命令用来在根目录下生成一个package.json文件,这个文件中记录了我们当前项目的基本信息,它是一切工作的开始。
第二步:安装包
npm 这个超市中有现成的写好的代码,我们想下载来用,这个过程就是安装包,或者叫下包、装包
安装命令: npm i 包名
根据我们遇到的实际问题,我们来引入相应的包来解决它们。例如,我们在开发一个项目,其中涉及一些对日期时间的处理可以安装dayjs包。
安装day.js包
day.js是一个专门用来处理日期时间的一个包
主页地址:dayjs.fenxianglu.cn/
安装命令:npm install dayjs
第三步:使用包
当我们已经下载好一个包之后,就可以像使用核心模块一样去使用它。
格式是:const 常量名 = require('包名') 这个格式与引入核心模块的格式是一样的。
// 从npm下载 别人写的好代码,在本地引入,并使用
const dayjs = require('dayjs')
console.log( dayjs()
.startOf('month')
.add(1, 'day')
.set('year', 2018)
.format('YYYY-MM-DD HH:mm:ss') );
console.log(dayjs);
以上代码的整体目录结构
project01
├── node_modules # 统一放置下载的包
│ └── dayjs # 某个包
├── xx.js # 业务代码, 引入dayjs来使用
├── package.json # 记录本项目的信息
└── package-lock.json # 下载包的详细信息
小结
下载使用包的步骤有三步,分别是:________ (只需要做一次), ________, _______
下载使用包的细节1-package.json
npm init 命令
在某个目录下开启小黑窗,输入如下命令:npm init
它会启动一个交互式的程序,让你填入一些关于本项目的信息,最后生成一个package.json文件。
如果你希望直接采用默认信息,可以使用:
npm init --yes
// 或者是 npm init -y
说明:
- 这个命令只需要运行一次,它的目的仅仅是生成一个package.json文件。
- 如果项目根目录下已经有了package.json文件,就不需要再去运行这个命令了。
- 这个package.json文件后期是可以手动修改的。
package.json文件
它一般是由npm init命令创建出来的(也可以正常被CV),它的整体内容是一个json字符串,用来对当前项目进行整体描述。
其中最外层可以看作是一个js的对象(每一个属性名都加了"",这就是一个典型的json标记)。
这个文件中有非常多的内容,我们目前学习如下几个:
- name: 表示这个项目的名字。如是它是一个第三方包的话,它就决定了我们在require()时应该要写什么内容。
- version:版本号
- keywords:关键字
- author: 作者
- descrption: 描述
其它可参考
1.javascript.ruanyifeng.com/nodejs/pack…
下载使用包的细节2-node_modules文件夹
作用
这个文件夹中保存着我们从npm中下载来的第三方包。在使用npm install 命令时,会从npm网站下载对应的包到这个文件夹中。
执行逻辑
当键入npm install XXX之后(这里假设这个XXX包是存在的,也没有出现任何的网络错误):
- 如果有package.json
(1) 修改package.json文件。根据开发依赖和生产依赖的不同,决定把这句记录在加在devDependencies或者是dependencies列表中。
(2) 修改node_modules文件夹
-
- 如果有node_modules文件夹,则直接在下面新建名为XXX的文件夹,并从npm中下来这个包。如果这个包还有其它的依赖,则也会下载下来。
- 如果没有node_modules,则先创建这个文件夹,再去下载相应的包
- 如果没有package.json。会给一个警告信息。
小结
- 建议先用init命令创建package.json之后,再去install
- 在分享代码时,我们一般不需要把node_modules也给别人(就像你不需要把day.js给别人,因为他们可以自己去下载)。对方拿到我们的代码之后,先运行
npm install(后面不接任何的包名),自己去安装这些个依赖包。
下载使用包整体回顾
下载包-修改镜像源
下包速度慢的原因:默认情况下,npm 从一个名为 registry.npmjs.org/ 的服务器上下包。这个服务器在国外,因此下包速度会非常慢。
解决方案:把 npm的下包地址,从国外的服务器切换为国内的服务器。
检查当前的下包地址:
npm config get registry
把下包的地址切换为国内的淘宝服务器
npm config set registry=https://registry.npm.taobao.org/
全局安装包和本地安装包
我们通过npm install 命令来安装包,简单说就是把包从npm的官网(或者是指定的镜像源)下载到我们自己的电脑中。那具体这个包下载到哪里了,还是有一点讲究的。
分成两类:
- 全局安装: 包被安装到了系统目录(一般在系统盘的node_modules中)。
-
- 命令:
npm install -g 包名或者npm install 包名 -g - 辅助提示:
- 命令:
npm root -g // 查看全局包的安装目录
npm list -g --depth 0 //查看全局安装过的包
- 局部安装(或者叫本地安装),包安装在当前项目的根目录下(与package.json同级)的node_modules中。
-
- 命令:
npm install 包名
- 命令:
全局包与本地包的区别
- 全局安装的包一般可提供直接执行的命令。我们通过对一些工具类的包采用这种方式安装,如:
gulp, nodemon, live-server,nrm等。 - 本地安装的包是与具体的项目有关的, 我们需要在开发过程中使用这些具体的功能。
一个经验法则:
- 要用到该包的命令执行任务的就需要全局安装
- 要通过require引入使用的就需要本地安装
一个判断标准:看这个包的说明文档
小结
- 在下包的时候,______ 参数的作用是:把包安装到全局的用户目录之下。
- 要用到该包的命令执行任务的就需要 ______安装
- 要通过require引入使用的就需要 _____ 安装