在es6之前,JavaScript没有模块系统。
社区自己定制了一些模块加载方案,如CommonJS(服务器)、AMD(浏览器)等。
1.原始写法
函数分文件存放
function A(){}
function B(){}
缺点: 多 人 开 发 容 易 造 成 全 局 变 量 污 染 \color{#fa4}多人开发容易造成全局变量污染 多人开发容易造成全局变量污染
2.对象写法
var moduleA = {
count:10,
A:function () {
this.count +=10;
console.log(this.count)
},
B:function () {
this.count *=10;
console.log(this.count)
}
};
var moduleB = {
count:20,
A:function () {
this.count -=10;
console.log(this.count)
},
B:function () {
this.count /=10;
console.log(this.count)
}
};
moduleA.A()
moduleA.B()
moduleB.A()
moduleB.B()
在函数调用之前改变moduleA的count值,关于moduleA的所有运算都会改变
...
moduleA.count = "haha"
moduleA.A()
moduleA.B()
moduleB.A()
moduleB.B()
缺点: 函 数 和 变 量 都 是 对 外 暴 露 的 \color{#fa4}函数和变量都是对外暴露的 函数和变量都是对外暴露的
3.闭包(立即执行函数)
var moduleA = (function () {
var count = 10;
function A() {
count +=10;
console.log(count)
}
function B() {
count *=10;
console.log(count)
}
return {
showA:A,
showB:B
}
})()
moduleA.showA();
moduleA.showB();
外部访问不到count
变量和A
访问,只能通过return返回的对象
访问moudleA.showA
...
moduleA.showA();
moduleA.showB();
console.log(moduleA.count)
moduleA.A()
闭包中变量为私有变量,函数为私有函数,避免了全局污染
缺点: 无 法 获 取 内 部 变 量 和 函 数 , 故 无 法 拓 展 , 只 能 改 源 码 \color{#fa4}无法获取内部变量和函数,故无法拓展,只能改源码 无法获取内部变量和函数,故无法拓展,只能改源码
模块化的优点:
一个js文件就是一个模块,模块中的代码是全部包装在函数中的
可以通过类数组arguments对象验证,只有在函数中才能使用arguments对象
exports 该对象用来将变量或函数暴露到外部
require 函数,用来引入外部函数
module 代表当前模块本身,exports就是module的属性,使用module.exports导出与exports导出 实质上是一样的
__filename 当前文件的完整路径
__dirname 当前模块所在文件夹的路径
e x p o r t s 和 m o u d l e . e x p o r t s 区 别 \color{#f4a}exports 和moudle.exports区别 exports和moudle.exports区别
exports===module.exports 实质上是 exports中存放的是module.exports的内存地址。exprots.的方式是改变内存地址中的变量即module.exports对象中的变量,而exports直接赋值,改变的是exports中存放的module.exports的内存地址,即exports不再指向module.exports。exports从引用数据类型变为基本数据类型了。所以会找不到预期暴露的对象或函数
通过require()方法,用于加载模块。require()可以传递一个文件的路径作为参数。require()引入模块后返回一个(代表引入的模块)对象引入的模块必须通过exports属性将其暴露出去。
Asynchronous Module Definition(异步模块加载机制)。描述了模块的定义,依赖关系,引用关系以及加载机制。requireJS
define函数是全局变量
id 指定了被定义模块的id。(可选)
可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名
依赖(可选)
字符串数组,AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖必须在真正执 行具体的factory方法前解决
factory(必需)
工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。
defer
【ie】 async = 'true'
引入的所有.js文件都是异步执行的
异步加载,渲染引擎遇到异步加载的文件,就会开始下载外部脚本,但不会等他下载和执行,而是直接执行后面的命令
defer要等到整个页面在内存中正常渲染结束,才会执行
async 文件一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的
data-main=' '
设置入口文件
每一个.html文件都有有一个入口文件,管理当前.html页面使用的所有的.js代码
后续引入的所有.js,后缀都可以省略
创建模块
define(function () {
function add(x,y) {
return x + y
}
function show() {
console.log("hello world")
}
return {
add:add,
show:show
}
})
引入模块
require.config({
paths:{
add:'demo/add',
}
})
require(['add'],function (add) {
var res = add.add(1,2)
console.log(res)
add.show()
})
如果引入的模块由依赖关系比如jquery-cookie依赖与jquery,在require.config 中添加如下配置
shim:{
//设置依赖关系 先引入jquery.js 然后在引入jquery-cookie
"jquery-cookie":["jquery"],
//声明当前模块不遵从AMD
"XXX":{
exports:"_"
}
}
es6的模块都默认是严格模式
export命令
对外暴露模块接口
export var firstName = "xxx"
export var lastName = 'Jackson';
export var year = 1958;
var firstName = "xxx"
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year }
export function add(x,y){ return x + y }
重命名
function v1(){}
function v2(){}
export {
v1 as streamV1
v2 as streamV2
};
import命令
引入模块功能
使用export定义的模块对外接口后,可以使用import加载这个模
import { firstName, lastName, year} from './xxx'
重命名
import { firstName as surname } from './xxx'
i m p o r t 命 令 输 入 的 变 量 都 是 只 读 的 \color{#fa4}import命令输入的变量都是只读的 import命令输入的变量都是只读的
i m p o r t 命 令 具 有 提 升 效 果 , 会 提 升 到 整 个 模 块 的 头 部 , 首 先 执 行 \color{#fa4}import命令具有提升效果,会提升到整个模块的头部,首先执行 import命令具有提升效果,会提升到整个模块的头部,首先执行
i m p o r t 是 静 态 执 行 , 所 以 不 能 使 用 表 达 式 和 变 量 \color{#fa4}import是静态执行,所以不能使用表达式和变量 import是静态执行,所以不能使用表达式和变量
i m p o r t 语 句 会 执 行 所 加 载 的 模 块 \color{#fa4}import语句会执行所加载的模块 import语句会执行所加载的模块
模块整体加载
即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。
import * as circle from './circle';
// export-default.js
export default function () {
console.log('foo');
}
其他模块加载该模块时,import命令可以为该匿名函数指定任意名字
// import-default.js
import customName from './export-default';
customName(); // 'foo'
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
使 用 e x p o r t d e f a u l ‘ 时 , 对 应 的 i m p o r t 语 句 不 需 要 使 用 大 括 号 \color{#fa4}使用export defaul`时,对应的import语句不需要使用大括号 使用exportdefaul‘时,对应的import语句不需要使用大括号
e x p o r t d e f a u l t 命 令 其 实 只 是 输 出 一 个 叫 做 d e f a u l t 的 变 量 , 所 以 它 后 面 不 能 跟 变 量 声 明 语 句 \color{#fa4}export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句 exportdefault命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句
var a = 1;
export default a;
e x p o r t d e f a u l t a 的 含 义 是 将 变 量 a 的 值 赋 给 变 量 d e f a u l t \color{#fa4}export default a的含义是将变量a的值赋给变量default exportdefaulta的含义是将变量a的值赋给变量default
export default命令的本质是将后面的值,赋给default变量
export default function (obj) {
// ···
}
export function each(obj, iterator, context) {
// ···
}
export { each as forEach };
同时输入默认方法和其他接口
import _, { each, forEach } from 'lodash';
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。
具名接口改为默认接口
export { es6 as default } from './someModule';
// 等同于
import { es6 } from './someModule';
export default es6;
默认接口也可以改名为具名接口
export { default as es6 } from './someModule';
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
ES6 模块不会缓存运行结果,而是 动 态 地 去 被 加 载 的 模 块 取 值 \color{#42c60b}动态地去被加载的模块取值 动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
package.json文件有两个字段可以指定模块的入口文件
{
"type": "module",
"main": "./src/index.js"
}
项目的入口脚本为./src/index.js,它的格式为 ES6 模块。如果没有type字段,index.js就会被解释为 CommonJS 模块。然后就可以使用import命令就可以加载这个模块
优先级高于main
//./node_modules/es-module-package/package.json
{
"exports": {
"./submodule": "./src/submodule.js"
}
}
使用别名加载这个模块import submodule from 'es-module-package/submodule';
//./node_modules/es-module-package/src/submodule.js
如果没有指定别名,就不能用“模块+脚本名”这种形式加载脚本。{
"exports": {
".": "./main.js"
}
}
// 等同于
{
"exports": "./main.js"
}
exports字段只有支持ES6的node.js才认识{
"main": "./main-legacy.cjs",
"exports": {
".": "./main-modern.cjs"
}
}
{
"type": "module",
"exports": {
".": {
"require": "./main.cjs",
"default": "./main.js"
}
}
}
CommonJS 的require()命令不能加载 ES6 模块,会报错,只能使用import()这个方法加载。
(async ()=>{
await import('./xxx)
})()
ES6 模块的import命令可以加载 CommonJS 模块,但是 只 能 整 体 加 载 \color{#42c60b}只能整体加载 只能整体加载,不能只加载单一的输出项。
import packageMain from 'commonjs-package';