4. 模块系统
4.1 Common.JS 模块规范
在 Node 中的 JavaScript 还有一个很重要的概念,模块系统
- 模块作用域
- 使用 require 方法来加载模块
- 使用 exports 接口对象来导出模块中的成员
4.1.1 加载 require
语法:
const 自定义变量名称 = require('模块')
两个作用:
1.执行被加载模块中的代码
2.得到被加载模块中的exports
,导出接口对像
4.1.2导出 exports
-
Node.js 中是模块作用域,默认文件中所有的成员都挂载到
exports
接口对象中就可以了
1.导出多个成员*exports.a = 123; exports.b = 'hello'; exports.c = function () { console.log('ccc') }; exports.d = { foo:'bar' };
2.(1)导出单个成员
module.exports = 'hello';
(2)以下这种情况会覆盖
module.exports = 'hello'; //以这个为准,后者会覆盖前者 module.exports = function (x,y) { return x + y; }
(3)也可以这样来导出多个成员
module.exports = { add: function (x,y) { return x + y } str: 'hello' }
//main.js文件
//默认的到的是对象
//使用对象中的成员时必须.点儿出来
//有时候,对于一个模块,我们仅仅就是希望导出其中的某一个成员
const fooExports = require('./foo');
console.log(fooExports)
//foo.js文件
const foo = 'bar';
function add (x,y) {
return x + y;
}
//如果一个模块需要直接导出某个成员,而非一个对象
//那这个时候必须使用下面的这种方式
module.exports = add
//exports = add; 这种方法是不对的
//exports 是一个对象
//我们可以通过多次为这个对象添加成员实现对外导出多个
//只能得到我想要给你的成员
//这样做的目的是为了解决命名冲突的问题
//exports.add = add;
//你可以认为在每个模块的最后 return 了这个exports
4.1.3 exports
和 module-exports
的区别
原理解析
exports
和 module.exports
是一个引用
console.log(exports === module.exports)' //ture
exports.foo = 'bar';
//等价于
module.exports.foo = 'bar';
详细解析
- node默认的一些东西
//在 node 中,每个模块内部都有一个自己的 module 对象
//该 module 对象中有一个成员叫: exports(也是一个对象)
//也就是说如果你需要对外导出成员。只需要把导出的成员挂载到 module.exports 中
const module = {
exports: {
}
}
//默认在代码最后有一句: return module.exports
return module.exports
//谁来 require 我,谁就得到 module.exports
//我们发现每次导出接口成员的时候都通过 module.exports.xxx = xxx 的方式很麻烦,点的太多
//所以为了简化操作专门提供了一个变量 module.exports = exports
//也就是说在模块中还有这样一句代码: const exports= module.exports
console.log(module.exports === exports) //ture
module.exports.foo = 'bar';
module.export.add = function (x,y) {
return x + y
}
//const module = {
// exports: {
// foo : 'bar',
// add = function (x,y) {
// return x + y
// }
// }
//}
//return module.exports
- 一些注意事项
//当给一个模块需要导出单个成员的时候,直接给exports 赋值是不管用的
exports = 'hello' //不可行
//一定要注意,最后 return 的是 module.exports,不是 exports ,所以给exports 重新赋值就丢失了两者之间的引用关系。
exports = {};
exports.foo = 'bar';
//这时所加载此文件的文件不会得到foo的值
一个小栗子
exports.a = 'hello'; //有用
exports = {}; //重新赋值,失去引用
exports.foo = 'bar';
module.exports.b = 'world'
//可以得到的有a 的值和 b 的值
重新建立引用
//给 exports 重新赋值会断开和 module.exports 之间的引用
//同理,给 module.exports 重新赋值同样会断开
//这里导致 exports !== module.exports
module.exports = {
foo: 'bar'
}
//但是这里又重新建立两者的引用关系
exports = module.exports;
exports.foo = 'hello'
//最后得到的是{foo:hello}
来个练习题
//{foo:bar}
exports.foo = 'bar';
//{foo:bar,a:123}
module.exports.a = 123;
//exports !== module.exports
//最终 return 的是 module.export
//所以无论你 exports 中的成员是什么都没用
export = {
a:456
};
//{foo:hahaha,a:123}
module.exports.foo = 'hahaha';
//没关系,混淆你的
exports.c = 456;
//重新建立引用关系
exports = module.exports;
//因为前面重新建立了关系,所以这里是有用的
//{foo: 'hahaha', a:789}
export.a = 789;
//前面再nb这里也重新赋值
//最终得到的是 Function
module.exports = function () {
console.log('hello')
}
//真正去使用的时候
// 导出多个成员exports.xxx = xxx;
// 导出多个成员也可以: module.exports = {};
// 导出单个成员: module.exports
//当然你可以只使用 module.exports ,避免混淆
4.1.4 require方法加载规则
1. 优先从缓存加载
//main.js 文件
require('./a');
//优先从缓存加载
//由于在 a 中已经加载过 b ,所以这里不会重复加载
//可以拿到其中的接口对象
//这样做的目的是为了避免重复加载,提高模块加载效率
const fn = require('./b');
console.log(fn)
//最后结果是
//a.js被加载了
//b.js被加载了
//[Function]
//[Function]
//a.js文件
console.log('文件被加载了');
const fn = require('./b');
console.log(fn);
//b.js文件
console.log('b被加载了');
module.exports = function () {
console.log('hello bbb');
}
require
* 如果是非路径形式的模块标识
路径形式模块:
./ 当前目录,不可省略
…/ 上一级目录,不可省略
/xxx 几乎不用
D;/a/faa.js 几乎不用
首位的 / 在这里表示的是当前文件模块所属磁盘根路径
.js后缀名可以省略
require('./foo.js')
2. 核心模块的本质也是文件
核心模块文件已经被编译到了二进制文件中了,我们只需要按照名字来加载就可以了
require('fs')
require('http')
3. 第三方模块
-
凡是第三方模块都必须通过 npm 来下载
使用的时候就可以通过 require(‘包名’) 的方式进行加载才可使用
不可能有任何一个第三方包和核心模块的名字是一样的
既不是核心模块也不是路径形式的模块 -
先找到当前文件所处目录中的 node_modules 目录
node_modules/art-template
node_modules/art-template/package.json 文件
node_modules/art-template/package.json 文件中的 main 属性
main 属性中就记录了 art-template 的入口模块
然后加载使用这个第三方包,实际上最终加载的还是文件 -
如果 package.json 文件不存在或者 main 指定的入口模块也是没有,则 node 会自动 找该目录下的 index.js,也就是说 index,js 会作为一个默认备选项
-
如果以上所有任何一个条件都不成立,则会进入上一级目录中的 node_modules 目录查找,如果上一级还没有,则继续往上上一级查找,如果直到当前磁盘根目录还找不到,最后报错
-
注意:我们一个项目中有且只有一个 node_modules 放在项目根目录中
总结
- 优先从缓存加载
- 核心模块
- 路径形式的文件模块
- 第三方模块
node_modules/art-template/
node_modules/art-template/package.json
node_modules/art-template/package.json main
index.js 备选项
进入上一级目录找 node_modules
按照这个规则依次往上找,直到磁盘目录还是找不到,最后报错
5. npm
5.1 npm 网站
npmjs.com
5.2 npm 命令行工具
npm 的第二成含义就是一个命令行工具,只要你安装了node 就已经安装了 npm
npm 也有版本号这个概念
可以通过在命令行中输入
npm --version
升级 npm (自己升级自己)
npm install --global npm
5.3 npm 常用命令
- npm init ( npm init -y 可以跳过向导,快速生成)
- npm install (一次性把 dependencies 选项中依赖项全部安装)
- npm install 包名 (只下载)
- npm install --save 包名(下载并且保存依赖项(package.json 文件中的 dependencies 选项)
- npm uninstall 包名 (只删除,如果有依赖项会依然保存) ,简写为 npm un 包名
- npm uninstall --save 包名 (删除的同时也会把)依赖信息也去除 ,简写 npm un -S 包名
- npm help (查看使用帮助)
- npm 命令 --help (查看指定命令的使用帮助)
例如忘记了 install 命令的简写了,这个时候可以输入npm uninstall --help
来查看使用帮助
5.4 解决 npm 被墙问题
npm 存储包文件的服务器在国外,有时候会被墙,也就是说速度很慢,所以我们需要解决这个问题。
淘宝镜像:淘宝的开发团队把 npm
在国内做了一个备份。
- 安装淘宝的
cnpm
接下来你安装包的时候把之前的# 在任意目录执行都可以 # --global 表示安装到全局,而非当前目录 # --global 不能省略,否则不管用 npm install --global cnpm
npm
替换成cnpm
举个栗子:# 这里还是走国外的 npm 服务器,速度比较慢 npm install jquery # 使用 cnpm 就会通过淘宝的服务器来下载 jquery cnpm install jquery
- 如果不想安装
cnpm
又想使用淘宝的服务器来下载,可以使用下面的这个方法
但是每次这样手动添加参数很麻烦,所以我们可以把这个选项加入配置文件中:npm install jquery --registry=https://registry.npm.taobao.org
只要经过了上面命令的配置,则你以后所有的npm config set registry http://registry.npm.taobao.org # 查看 npm 配置信息 npm config list
npm install
都会默认通过淘宝的服务器来下载。