一、什么是模块化
-
模块化就是将程序分成一个个的小模块来进行开发,然后再将其组合起来。
-
开发模块时,每个模块都拥有独立的作用域,避免了全局变量污染,不会影响到其他模块的开发
-
同时,也可以通过暴露出自己的某些变量、方法和对象,来让其他模块引入使用
-
也可以通过引入其他模块的暴露部分,来开发自己的模块
-
通过暴露和引入,可以实现对模块的组装从而开发出程序
二、CommonJs模块化规范
Module类源码
module.id //带有绝对路径的模块文件名
module.filename //模块的文件名,带有绝对路径
module.loaded //表示模块是否已经完成加载
module.parent //返回一个对象,表示调用该模块的模块。
module.children //返回一个数组,表示该模块要用到的其他模块。
module.exports //模块对外输出的值。需要打破模块封装性曝露的方法和属性,都要挂载到module.exports上。其它文件加载该模块,实际上就是读取module.exports属性
// 在 /Users/computer/Desktop/ccc/lib.js 文件中 console.log(module);
Module {
id: '.',
path: '/Users/computer/Desktop/ccc',
exports: { name: 'test' },
parent: null,
filename: '/Users/computer/Desktop/ccc/main.js',
loaded: false,``
children: [
Module {...}
],
paths: [ //查找路径
'/Users/computer/Desktop/ccc/node_modules',
'/Users/computer/Desktop/node_modules',
'/Users/computer/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
模块化开发概要图
2.1 module.exports 和 exports
测试代码:
index.js
var a = 3
var bb = function(a){
console.log(a)
}
//exports实际上是对module.exports的引用,相当于是exports=module.exports,此时exports module.exports指向同一个对象空间
exports.a = a
exports.bb = bb
// 验证:用myexports=exports对exports进行引用,此时myexports exports module.exports 实际上指向同一个对象空间
var myexports = exports
myexports.c = 5
//但最终这个模块导出的是 module.exports 所指向的对象空间
//若修改 module.exports 则会创建另外一个对象空间,也就表示之前所有的引用都会失效,因为他们指向的是之前的对象空间
// module.exports = {
// a: 4,
// bb: function(a){
// console.log(exports.a)
// }
// }
//test.js
//实际上也是对对象的引用
const index = require("./index.js")
index.bb(index.a)
index.a = 5
console.log (require("./index.js"))
测试结果:
2.2 require
require是一个函数,可以帮助我们引入一个文件(模块)中导入的对象。
而通过require来引入在node中的模块分成三种情形,
第一种是node自带的核心模块,引入该模块的方式是直接使用require(X),X为模块名称,
第二种是自定义模块以及本地文件,前提是X是一个路径,如"./"或者“../”等。
第一步:将X当做一个文件在对应的目录下查找;
- 如果有后缀名,按照后缀名的格式查找对应的文件
- 如果没有后缀名,会按照如下顺序:
- 直接查找文件X
- 查找X.js文件:当做JavaScript脚本文件解析
- 查找X.json文件:以JSON格式解析。
- 查找X.node文件:以编译后的二进制文件解析。.node文件通常是c/c++写的一些扩展模块
第二步:没有找到对应的文件,将X作为一个目录。查找目录下面的index文件
- 查找X/index.js文件
- 查找X/index.json文件
- 查找X/index.node文件
第三种是第三方模块,他首先得通过node i X,将X安装在node_module文件夹下,然后通过require(X),将其引入
查找X路径的流程是通过module对象中的paths数组依次查找
2.3 注意点
因为CommonJs导出的是一个对象(module.exports),所以其他模块引用的都是同一个对象,如果修改其中一个引用的值,所有的值都会改变。
三、ES6模块化规范
3.1 ES6 模块与 CommonJS 模块的差异
-
CommonJs输出的是一个值的浅拷贝,ES6模块输出的是值的引用s
-
CommonJs模块是运行时加载,ES6模块是编译时输出接口
第一个差异是指当CommonJs输出值后,内部模块值的改变不会影响到已经输出的值。而由于ES6模块是对值的引用,因此他可以实时获取到模块内的已经输出的值的变化,是一种动态取值。
CommonJs模块下:
//index.js //在模块内部修改已经输出的值,已经输出的值不会在改变 var z =3 exports.z =z var dd = function(z){ z++; } exports.dd = dd //test.js const index = require("./index.js") console.log("第一次获取的z:"+index.z) //获取已输出对象值 index.dd //调用修改值的方法 console.log("第二次获取的z:"+index.z) //再次获取输出值输出结果:
ES6模块下:
// index.js export let counter = 3; export function incCounter() { counter++; } // test.js import { counter, incCounter } from './index'; console.log(counter); // 3 incCounter(); console.log(counter); // 4由此可看出CommonJs在第一次js脚本运行时对输出的内容进行了缓存,而ES6只是提供一个获取值的接口,去动态取值而不是缓存。
第二个差异是因为 CommonJS 加载的是一个对象(即
module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。因此我们在ES6模块下可以只导入模块中的某些方法或者变量,但是不需要全部导入,而CommonJs则会将整个模块脚本给运行完,会将对象内容全部缓存。
3.2 export 和 export default
export导出方式
export+语句声明
export const name = 'coderwhy';
export const age = 18;
export let message = "my name is why";
export function sayHello(name) {
console.log("Hello " + name);
}
export {}
const name = 'coderwhy';
const age = 18;
let message = "my name is why";
function sayHello(name) {
console.log("Hello " + name);
}
export {
name,
age,
message,
sayHello
}
export as 别名
export {
name as fName,
age as fAge,
message as fMessage,
sayHello as fSayHello
}
export default用法
- 默认导出export时可以不需要指定名字;
- 在导入时不需要使用
{},并且可以自己来指定名字; - 一个模块只能有一个export default
3.3 import
import引入方式
import ‘ ’
import 'lodash';
会执行引入模块,但是不引入值。
import {<> as <> } from ' '
import { name as wName, age as wAge, sayHello as wSayHello } from './modules/foo.js';
导入时给标识符起别名。
import * as <> from ' '
import * as foo from './modules/foo.js';
console.log(foo.name);
console.log(foo.age);
foo.sayHello("Kobe");
整体导入,并将导入模块功能放在自己定义的模块功能对象
export import 结合
从一个模块中导入的内容如果想直接导出去,那么可以用下面的语句
// foo.js 导入,但是只是做一个中转
export { sum } from './bar.js';
// 接口改名
export { sum as barSum } from './bar.js'; // 甚至在foo.js中导出时,我们可以变化它的名字
// 整体导入和导出
export * from './bar.js';
// 相当于实现了模块之间的继承。注意,`export *`命令会忽略后面模块的`default`接口。