1. 为什么要模块化
- 把具有不同功能的版块封装为不同的模块,比如把涉及到网络请求的函数全都写在一个network.js文件里,把一些工具类的函数写在tool.js文件里,框架里的每一个组件都占据一个.vue文件,避免所有代码都写到一起,难以管理,同时还可以提高代码的复用率
- 通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数和类。
2. 模块化规范
目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统
1. CommonJs
- NodeJS采用CommonJS规范实现了模块系统
- CommonJS规范
- CommonJS规范规定了如何定义一个模块, 如何暴露(导出)模块中的变量函数, 以及如何使用定义好的模块
- 在CommonJS规范中一个文件就是一个模块
- 在CommonJS规范中每个文件中的变量函数都是私有的,对其他文件不可见的
- 在CommonJS规范中每个文件中的变量函数必须通过exports暴露(导出)之后其它文件才可以使用
- 在CommonJS规范中想要使用其它文件暴露的变量函数必须通过require()导入模块才可以使用
2. Node模块中暴露数据的几种方法
- exports xxx =(暴露出去一个对象)
a.js
每一个node执行一个文件时,会给这个文件内生成一个exports和module对象
//在模块里面编写私有的变量和函数,通过exports暴露出去,记住,暴露出去的是一个对象
let name = 'mike'
function sum() {
console.log(3);
}
exports.name = name
exports.sum = sum
b.js
let moduleA = require('./s')
console.log(moduleA);
- module.exports.xxx
a.js
let name = 'mike'
function sum() {
console.log(3);
}
module.exports.name = name
module.exports.sum = sum
b.js
let moduleA = require('./a')
console.log(moduleA);
所以第一种和第二种方法到底有什么区别????
区别就是第二种方法可以不通过对象的属性赋值,可以直接赋值
let name = 'mike'
function sum() {
console.log(3);
}
module.exports = name //mike,可以直接赋值
exports = name //{}:不可以直接赋值
//此时如果再打印moduleA的话
//输出的就是上述注释的结果
- 全局暴露(不推荐,不符合commonJs规范,私有+暴露+导入)
global.str = name;
global.sum = sum;
3. require的引入规则
- require导入模块时可以不添加导入模块的类型(模块的后缀名可以不写)
如果没有指定导入模块的类型, 那么会按照查找顺序依次查找.js .json .node文件
无论是三种类型中的哪一种, 导入之后都会转换成JS对象返回给我们 - 导入自定义模块时必须指定路径
require除了可以导入"自定义模块(文件模块)“、还可以导入"系统模块(核心模块–nodejs自带的)”、“第三方模块(别人写的,可以直接使用)”
导入"自定义模块"模块时前面必须加上路径(就是前面的./之类的)
导入"系统模块"和"第三方模块"是不用添加路径 - 查找
如果是"系统模块"直接到环境变量配置的路径中查找
如果是"第三方模块"会按照module.paths数组中的路径依次查找
require(X)查找顺序:
- 判断X是不是内置模块,比如http,是内置模块,返回该模块即可
- 判断有没有路径开头
- 有
- 把X当做一个文件,根据X所在的父模块确定绝对路径,加入文件没有后缀的话,依次查找js,json,node文件,找到就返回
- 把X当做一个目录,一次查找下面的文件,只要其中有一个存在,就返回该文件
- 没有
- 根据X所在的父模块,确定X可能的安装目录,一次在每一个目录里,把X当成文件名或者目录名加载
比如,在/home/ry/projects/foo.js 执行了 require(‘bar’),那么可能的路径有
在这些路径里先把bar当成文件,一次找bar,bar.js/json/node,找不到再把bar当成目录,依次找pakage.json,index.js,index.json,index.node…
- 根据X所在的父模块,确定X可能的安装目录,一次在每一个目录里,把X当成文件名或者目录名加载
- 都没找到,就返回not found
3 es6模块化
在ES6出现之前,JS不像其他语言拥有“模块化”这一概念,比如python的import,就连css也有@import,于是为了支持JS模块化,我们使用类、立即执行函数或者第三方插件(RequireJS、seaJS)来实现模块化,在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。es6成为浏览器和服务器通用的模块解决方案。
一个文件就是一个模块, 模块中的数据都是私有的,ES6模块化模块和NodeJS中一样, 可以通过对应的关键字暴露模块中的数据, 可以通过对应的关键字导入模块, 使用模块中暴露的数据
-
ES6 模块不是对象,而是通过
export
命令显式指定输出的代码,再通过import
命令输入。 -
特点:静态加载,传统的commenJS属于是运行时加载(无法在编译时做静态优化),而es6模块化成为编译时加载,所以效率要比commonJS模块加载的效率高,这也就导致了没法引用es6模块本省,因为它不是对象;es5模块自动采取严格模式
eg:
let { stat, exists, readfile } = require('fs'); //只有在运行时才会生成一个对象,然后加载这个对象中的三个方法, import { stat, exists, readFile } from 'fs';
1.export命令
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
-
一个模块就是一个独立的文件,该文件内部的所有变量外部无法获取,如果你希望外部能够读取模块内的一个变量,必须使用export关键字输出该变量,函数和类也是同理
eg:
//profile.js export var firstName='Michael' export var lastName = 'Jackson'; export var year = 1958;
或者:
var firstName='Michael' var lastName = 'Jackson'; var year = 1958; export {firstName,lastName,year as YEARS} //可以使用as重命名
只有这两种写法,其他的都是错误的,比如什么var m=1 export m也错了,没有加{}是错误的
2.import命令
-
使用
export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块。// main.js import { firstName, lastName, year } from './profile.js'; function setName(element) { element.textContent = firstName + ' ' + lastName; }
export命令接受一对大括号,里面指定要从其他模块导入的变量名,大括号的变量名,必须与被导入模块对外接口的名称相同
如果想为输入的变量重新取一个名字,可以使用as
eg:import { firstName as name } from ‘./profile.js’;
建议对import导入的变量都采用只读形式,不要修改,如果是对象是可以修改的,因为避免难以排查的错误
from后的路径
可以是相对路径也可以是绝对路径,如果不带路径,就必须有配置文件,告诉 JavaScript 引擎该模块的位置。
如果多次重复执行同一句import
语句,那么只会执行一次
3.整体加载
import * as circle form ‘./circle’
eg:
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
import * as circle from './circle'; //cricle对象不允许修改
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
4.export default
从上面的例子可以看出,使用import命令的时候,用户需要知道所有加载的变量名称或者函数名称才能去使用,否则无法加载,但是,希望快速加载的用户未必愿意去阅读api文档知道有哪些属性和方法
export default 用于输出一个叫做default的变量或者方法。所以它后面不能跟变量声明语句。并且只能export输出一个,所以import的时候可以随意指定名称,也可以不加{}
5.复合写法
混合使用
eg:在a.js文件里面
在b.js里面
4. es6和cmj的总结
-
导入导出:
require: node 和 es6 都支持的引入
export / import : 只有es6 支持的导出引入
module.exports / exports: 只有 node 支持的导出