1: 模块化演变过程
目录
1: 模块化演变过程
2: 模块化规范总结
3: ES Module
1: ES Module 基本特性
2: ES Module 导入,导出
3: ES Module导入导出注意事项
4: ES Module导入方式 和 导出方式;
5: ES Module 浏览器环境 Polyfill
第一阶段:(基于文件的划分模块方式)
概念:将每个功能 和 相关数据状态 分别放在单独的文件里
约定每个文件就是一个单独的模块,
使用每个模块【这个模块引入到页面中】,然后直接调用模块的成员【方法/ 变量】。
缺点: 所有成员都可以在模块外部被修改和访问【所有模块都是直接在全局工作的,没有私有空间】
模块一旦多了之后,命名会发生冲突,
无法管理各个模块之间的依赖关系。
var name = 'module-a'
function method1 () {cosnole.log(name + '#method1')
}
function method2 () {cosnole.log(name + '#method2')
}
第二阶段:【命名空间】
概念:每个模块只暴露一个全局对象,所有模块成员都挂载到这个对象上
做法:在第一阶段基础上,通过将 每个模块 【包裹成一个全局对象】的形式实现
类似于为 每个模块的成员添加 【命名空间】的感觉
优点: 减少,命名冲突的发生
缺点: 所有成员都可以在模块外部被修改和访问【所有模块都是直接在全局工作的,没有私有空间】。
无法管理各个模块之间的依赖关系。
var moduleA = {name: 'module-a',method1: function () {console.log(this.name + '#method1')}, method2: function () {console.log(this.name + '#method2')}
}
第三阶段:
概念:使用立即执行函数表达式(IIFE)为模块提供私有空间
做法:将每个模块成员都放在一个函数提供的私有作用域中,
对于需要向外暴露的成员,通过挂载到全局对象上的方式实现
优点: 模块有了私有空间
缺点:无法管理各个模块之间的依赖关系。
有了私有成员,私有成员只能在模块成员内通过闭包的形式访问
// (function(){})() : 自加载执行该方法
(function () {var name = 'module-a'function method1 () {cosnole.log(name + '#method1')}function method2 () {cosnole.log(name + '#method2')}// 将需要向外暴露的成员,挂载到全局对象上window.moduleA = {method1: method1,method2: method2 }
})()
第四阶段:
概念:使用(IIFE)参数作为依赖声明使用
做法:在第三阶段的基础上,利用立即执行函数的参数传递模块依赖项
优点: 使得模块之间的关系变得更加明显
(function ($) {var name = 'module-a'function method1 () {cosnole.log(name + '#method1')$('body').animate({ margin: '200px'})}function method2 () {cosnole.log(name + '#method2')}// 将需要向外暴露的成员,挂载到全局对象上window.moduleA = {method1: method1,method2: method2 }
})(jQuery)
2: 模块化规范总结
1: CommonJS模块规范【以同步模式加载模块】
它是运行在node.js环境下的模块化规范,node的机制是在启动时,加载模块,执行时直接使用模块
一个文件就是一个模块 每个模块都有单独的作用域
通过module.exports 导出模块成员
通过require函数载入模块
该模块规范不适合浏览器原因??
答:浏览器在加载页面时,如果需要同步加载所有模块,必然导致性能低下,所有早期的浏览器没有使用CommonJS规范
2: AMD模块规范【异步模式加载模块】根据浏览器特性制造的规范
通过define函数去定义模块;
通过require 来加载模块(内部会自动创建一个script标签来加载模块) require.js是一个自动加载模块器 ,它提供了"AMD"模块化规范
3: ES Module 支持大部分浏览器,现阶段比较流行的模块化规范
3: ES Module
1: ES Module 基本特性
<script type="module">console.log(this) // 因为是module类型 所以是undefinedvar foo = 100console.log(foo) // 100
</script>
<script type="module">console.log(foo) // 报错 foo没有定义,因为每个 ES Module 都是运行在单独的私有作用域中
</script>
1: 自动采用严格模式, 忽略'use strict', 严格模式的标志就是不能直接使用this
2: 每个ESM 模块都是单独的私有作用域
3: ESM 是通过 CORS 去请求外部 JS 模块的
4: ESM 的 script 标签回延迟执行脚本
2: ES Module 导入,导出
通过 export 导出模块成员
通过 import 导入模块
var name = 'foo module'
// 默认导出name
export default name// 接受默认导出, 将默认导出 重命名为 fooName
import fooName from './module'
console.log(fooName)
3: ES Module导入导出注意事项
1: ES Module中的 { }是固定语法,
import { name, age} = require('./module.js')
CommonJS中是先将模块整体导入为一个对象,再从对象中解构出需要的成员
export obj = {name, age}
const { name, age } = require('./module.js')
2: 导入成员并不是复制一个副本,而是直接导入模块成员的引用地址
也就是说 export导出的变量 和 import 得到的变量,在内存中是同一个块空间
所有一旦模块成员修改了,这里也会同步修改
3: 导入的模块成员是只读的,不可以修改
但是如果导入的是一个对象,对象的属性读写不受影响
name = 'tom' // 报错
obj.name = 'tom' // 正常
4: ES Module导入方式 和 导出方式;
// import { name, age } from 'module.js' // 不可以
// import { name, age} = require('./module') // 不可以
import { name, age } from './module.js'
import { name, age } from '/04.1-import/module.js'
import { name, age } from 'http://localhost:3002/04.1-import/module.js'console.log(name)
1: import导入模块路径必须是完整路径,路径的 ./ 不能省略,路径可以是 绝对路径 和 url
2: 只加载模块,不提取成员【在导出一些不受外界影响的子功能模块中很有作用】
import {} from './module.js'
import './module.js'
3: 用 * 导出模块所有成员,并将其放入一个对象中,从对象中提取需要的成员
import * as mod from './module.js' console.log(mod)
4: 如何动态去加载模块, import() 返回一个Promise对象,等待异步模块加载完成后,会自动加载 then 所指定的回调函数 模块对象可以通过回调函数的参数获取
import('./module.js').then(function (module) { console.log(module)
})
5: 同时导入 其他成员 和 默认成员
import { name, age, default as title } from './module.js'
import { name, age, title } from './module.js'
import title, { name, age } from './module.js' // 最新写法
6: 导出方式,可在导入时直接导出
export { default as Button } from './button.js'
export { Avatar } from './avatar.js'
5: ES Module 浏览器环境 Polyfill
es-module-loader 将浏览器中不识别的ES Module代码读取出来,交给babel来转化为浏览器可识别的代码
在IE 浏览器执行会报错,因为IE浏览器不支持es6的新特性Promise,
所以需要给IE浏览器引入一个promise-polyfill让其支持Promise
备注:
如果支持promise-polyfill的浏览器会执行2次,
1: 浏览器本身执行一次;2: ES Module loader 的polyfill会再执行一次
解决办法??
可以使用 script 的新属性 nomodule 来设置, 不支持 polyfill的加载,支持的不需要加载
注意!:
在本地测试可以使用 nomodule,它的原理是:运行阶段动态的去解析脚本,效率非常低下
在生成环境不要用: 生产环境应该提前将这些代码编译出来,让它可以直接在浏览器工作
<body><script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script><script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script><script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script><script type="module">import { foo } from './module.js'console.log(foo)</script>
</body>