模块化概述
模块化是一种主流的组织方式,它通过把我们的复杂代码按照功能的不同划分为不同的模块,单独的维护这种方式,提高我们的开发效率,降低维护成本。
模块化只是思想,不包含具体的实现。
模块化演变过程
- Stage1-文件划分方式:基于文件划分的方式
- 污染全局作用域
- 命名冲突问题
- 无法管理模块依赖关系
- 早期模块化完全依靠约定
- Stage2-命名空间方式:每个模块只暴露一个全局对象,所以模块成员都挂载到这个对象中
- 没有私有空间,模块成员可以被外部修改
- 模块之间的依赖关系没有得到解决
- Stage3-IIFE:采用立即执行函数表达式为模块提供私有空间
- 私有成员的概念:需要暴露给外部的成员通过挂载到全局对象上去实现
- 利用自执行函数的参数作为依赖声明去使用,每个模块之间的依赖关系变得明显,利于后期维护
模块化规范的出现
- CommenJS规范(NodeJS中提出的一套标准,NodeJS所有的模块代码必须要遵循CommenJS规范)
- 一个文件就是一个模块
- 每个模块都有单独的作用域
- 通过module.exports导出成员
- 通过require函数载入模块
- CommonJS是以同步模式加载模块
- AMD(异步的模块定义规范)
- Require.js实现了AMD规范,另外它本身也是一个强大的模块加载器
- 目前绝大多数第三方库都支持AMD规范
- AMD使用起来相对复杂
- 模块JS文件请求频繁
- CMD(通用的模块定义规范)
- Sea.js+CMD
模块化标准规范
-
CommonJS in Node.js
- Node内置的模块系统,没有任何的环境问题
-
Es Modules in Browers
- 最主流的前端模块化规范
- 绝大数的浏览器都支持Es Modules这个特性(原生支持意味着可以直接使用这个特性)
ES Modules特性
- 通过给script添加type=module的熟悉,就可以以ES Module的标准执行其中的JS代码
- 自动采用严格模式,忽略use strict。
- 每个ES Module都是运行在单独的私有作用域中
- ESM是通过CORS的方式请求外部JS模块
- ESM的Script标签会延迟执行脚本,不会阻碍页面元素的显示
ES Modules导入导出的注意事项
- 导出的成员并不是一个字面量对象,语法和字面量对象很像。导入的语法很ES6的结构很像,但是它不是一个结构。
- ES Modules模块的导出的不是成员的值,而是这个值存放的地址,在外部拿到的这个成员会受当前内部模块修改的影响
- 在外部导入一个模块的成员只是一个只读的成员,并不能去修改它们
ES Modules导入用法和注意事项
- 不能省略.js的扩展名,和CommonJS是有区别的
- 不能像CommonJS那样载入目录的方式载入index.js,需要提供完整的路径
- 后期可以使用打包工具打包我们的模块时可以省略扩展名和省略index.js等操作
- 相对路径下的./在ESModule是不能省略的,不然会认为是在加载第三方的模块,和Common相同。
- 可以使用完整的URL加载模块
- 可以使用*号方式把所有的成员全部提取出来,可以使用as的方式把所有导出的成员放入到一个对象当中
- 不能嵌套在if和函数当中
- import函数可以动态导入模块,这个函数返回的是一个Promise,当这个模块加载完成之后会自动执行then当中所指定的回调函数,模块的对象可以通过参数去拿到
ES Modules浏览器环境Polyfill
- IE不兼容ES Modules,可以借助编译工具将ES6的代码编译成ES5的方式才能正常工作
- Polyfile可以让浏览器直接去支持ES Modules当中绝大多少的特性
- npm的模块可以使用unpkg.com这个网站提供的cdn服务去拿到它下面所有的js文件。
- unpkg.com/browser-es-…
- 工作原理:通过es-module-loader读取出来交给babel去转换,从而让我们的代码可以正常工作
- promise-polyfill可以让浏览器支持Promise(浏览器支持可以忽略)
- 支持ESM的浏览器用了polyfill会被执行两次,元素是浏览器本身支持被执行了一次,然后es-modules的polyfill也会执行一次,可以借助script标签的新属性nomodule去解决
- nomodule标签只会在不支持esmodules的浏览器环境中工作
- 总结:
- 这种兼容ES Modules的方式只适合我们本地测试或者开发阶段
- 生成阶段千万不要去用,它的原理是在运行阶段动态的去解析脚本,效率过低
- 生成阶段应该预先去把这些代码编译出来,让它们在浏览器中直接运行工作
ES Modules in Node.js:支持情况
- Node8.5版本过后,可以原生的使用ESM去编写我们的代码,还处于实验阶段
- CommonJS规范与ESM规范差距较大,目前还是处于过渡状态
- 可以使用node --experimental-modules xxx.mjs去启用这个试验特性执行文件
- 内置模块兼容了ESM的提取成员方式
- 第三方模块都是导出默认成员
ES Modules in Node.js:与CommonJS模块交互
- CommonJS模块始终只会导出一个默认成员,ESM不能直接提取成员,注意import不是解构导出对象
- Node环境当中不能在CommonJS模块中通过require载入ES Modules
- 总结:
- ES Modules中可以导入CommonJS模块
- CommonJS中不能导入ES Modules模块
- CommonJS始终只会导出一个默认成员
- 注意import不是解构导出对象,它只是一个固定的用法,去提取导出模块当中的命名成员
ES Modules in Node.js:与CommonJS之间的差异
-
CommonJS
- require:加载模块函数
- module:模块对象
- exports:导出对象别名
- __filename:当前文件的绝对路径
- __dirname: 当前文件所在的目录
-
ES Modules
- import: 加载模块函数
- export: 导出对象别名
- import.meta.url :当前所工作文件的文件URL地址
- 通过url模块中的fileURLToPath方法可以得到__filename当前文件的绝对路径
- 通过path模块中的dirname方法得到__dirname当前文件所在的目录
-
ESM中没有CommonJS中的那些模块全局变量
ES Modules in Node.js:新版本进一步支持ESM
- 可以在package.json下添加type字段,将type字段设置为module,这样我们项目下所有的js文件都会以ESM规范去工作,就不用将扩展名改成mjs了。
- 将type设置成module之后,就无法直接使用CommonJS规范了,这时候想要使用CommonJS的话需要将文件的扩展名改成.cjs
ES Modules in Node.js:Babel兼容方案
- babel是一款主流的JavaScript编译器,它可以用来将我们使用了一些新特性的代码编译成当前环境所支持的代码