Module
在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}无法获取内部变量和函数,故无法拓展,只能改源码
无法获取内部变量和函数,故无法拓展,只能改源码
模块化的优点:
- 降低程序之间的耦合性
- 方便代码复用
- 维护方便
CommonJS
一个js文件就是一个模块,模块中的代码是全部包装在函数中的
可以通过类数组arguments对象验证,只有在函数中才能使用arguments对象
- arguments对象和Function是分不开的
- 因为arguments这个对象不能显式创建
- arguments对象只有函数开始时才可用
- arguments.length为函数实参个数,arguments.callee引用函数自身
一个模块是一个同时传递5个(exports,require,module,__filename,__dirname)实参的函数
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只能使用"."的的方式来向外暴露内部变量
exports===module.exports 实质上是 exports中存放的是module.exports的内存地址。exprots.的方式是改变内存地址中的变量即module.exports对象中的变量,而exports直接赋值,改变的是exports中存放的module.exports的内存地址,即exports不再指向module.exports。exports从引用数据类型变为基本数据类型了。所以会找不到预期暴露的对象或函数
- module.exports既可以通过.的形式,也可以直接赋值
通过require()方法,用于加载模块。require()可以传递一个文件的路径作为参数。require()引入模块后返回一个(代表引入的模块)对象引入的模块必须通过exports属性将其暴露出去。
AMD
Asynchronous Module Definition(异步模块加载机制)。描述了模块的定义,依赖关系,引用关系以及加载机制。requireJS
define(id?,dependencies,factory)
-
define函数是全局变量
-
id 指定了被定义模块的id。(可选)
可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名
-
依赖(可选)
字符串数组,AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖必须在真正执 行具体的factory方法前解决
-
factory(必需)
工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值。
requireJs
script标签中的属性
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:"_"
}
}
module
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 命令
// 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 与 import的复合写法
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模块的差异
- CommonJS模块输出的是一个 值 的 拷 贝 \color{#42c60b}值的拷贝 值的拷贝,es6模块输出的是 值 的 引 用 \color{#42c60b}值的引用 值的引用
- CommonJS模块是 运 行 时 加 载 \color{#42c60b}运行时加载 运行时加载,es6模块时 编 译 时 输 出 接 口 \color{#42c60b}编译时输出接口 编译时输出接口
- CommonJS模块的 r e q u i r e ( ) 是 同 步 加 载 模 块 \color{#42c60b}require()是同步加载模块 require()是同步加载模块,es6模块的 i m p o r t 命 令 是 异 步 加 载 \color{#42c60b}import命令是异步加载 import命令是异步加载,有一个独立的模块依赖解析阶段
- CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
ES6 模块不会缓存运行结果,而是
动
态
地
去
被
加
载
的
模
块
取
值
\color{#42c60b}动态地去被加载的模块取值
动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
package.json的main字段
package.json文件有两个字段可以指定模块的入口文件
- main
{
"type": "module",
"main": "./src/index.js"
}
项目的入口脚本为./src/index.js,它的格式为 ES6 模块。如果没有type字段,index.js就会被解释为 CommonJS 模块。然后就可以使用import命令就可以加载这个模块
- exports
优先级高于main
- 字目录别名
exports字段可以指定脚本或字目录的别名
使用别名加载这个模块//./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
- main 的别名
exports字段的别名如果是.,就代表模块的主入口,优先级高于main字段,并且可以直接简写成exports字段的值。
exports字段只有支持ES6的node.js才认识{ "exports": { ".": "./main.js" } } // 等同于 { "exports": "./main.js" }
{ "main": "./main-legacy.cjs", "exports": { ".": "./main-modern.cjs" } }
- 条件加载
利用.这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。目前,这个功能需要在 Node.js 运行的时候,打开–experimental-conditional-exports标志。{ "type": "module", "exports": { ".": { "require": "./main.cjs", "default": "./main.js" } } }
- 字目录别名
CommonJS模块加载ES6模块
CommonJS 的require()命令不能加载 ES6 模块,会报错,只能使用import()这个方法加载。
(async ()=>{
await import('./xxx)
})()
ES6模块加载Commonjs模块
ES6 模块的import命令可以加载 CommonJS 模块,但是 只 能 整 体 加 载 \color{#42c60b}只能整体加载 只能整体加载,不能只加载单一的输出项。
import packageMain from 'commonjs-package';