JavaScript模块化概述

模块化演进

阶段一:文件即模块,使用<script>标签引入
  • 污染全局作用域
  • 命名冲突
  • 无法管理模块依赖关系
阶段二:命名空间方式,文件暴露一个全局对象,所有方法挂载到全局对象上形成命名空间
  • 缓解命名冲突,但无法避免
  • 没有私有属性,从而无法避免出现私有属性值被不小心更改的情况
  • 依赖关系仍然无法管理
阶段三:IIFE立即执行函数表达式,可以给全局对象上挂载属性来暴露接口,也可以返回一个对象来暴露接口。
  • 私有成员得到保证
  • 通过IIFE传递参数,可以在一定意义上管理依赖关系。如
;(function($) {
// 通过在调用时传入jQuery,表示该模块需要依赖jQuery,则jQuery就需要在该模块执行前就存在了。
})(jQuery)

模块规范化

  • 通过代码来加载模块,即动态去加载模块,而不是使用静态的<script>标签

模块标准化和模块加载器

CommonJS规范
  • 一个文件就是一个模块
  • 每个模块都有单独的作用域
  • 通过module.exports导出成员
  • 通过require函数导入模块
  • 同步模式加载模块,node在启动的时候加载模块,执行的过程中使用模块
AMD规范(异步模块定义规范)
Require.js库
  • 实现AMD规范
  • 同时也是模块加载器
  • 定义模块:使用define()函数
  • 加载模块:使用require()函数,动态创建<script>标签,加载模块代码并执行
  • AMD使用相对复杂
  • 模块JS文件的请求频繁
模块化标准规范

目前基本统一为以下两种规范:

  • 浏览器环境:ES Modules
  • node环境:CommonJS

ES Module规范

基本特性
  • 自动采用严格模式
  • 每个模块都有私有作用域
  • ES module通过CORS的方式进行跨域请求模块文件,因此如果使用ES module请求跨域资源,需要服务器支持CORS
  • ES module的<script>标签会延迟执行脚本,相当于添加了defer,从而避免阻塞浏览器渲染。
导入导出规范
  • 静态导入导出
导出:export关键字
  • 成员声明并初始化的时候导出export const name = 'xx';
  • 统一导出成员export { member1, member2 }
  • 包含默认导出的成员export default memberDefault,default关键字后面的是值。
  • 导出时可以重新命名export { member1 as memberAlpha, member2 as memberBeta }
  • 导出的成员是一个只读引用,模块外部不允许修改导出的成员
    注意:
  • 统一导出的语法export {}并不是导出一个字面量对象,而是固定的语法,同理,对应的import {}也不是对象解构语法。
  • 如果使用export default {}这种语法,则导出的是一个对象,因为default后面跟的是值。
导入:import关键字
  • import from后的路径必须是完整的,不能省略.js扩展名(CommonJS中是可以省略.js扩展名的),也不能省略默认入口文件index.js。这些在使用打包工具的时候才能省略。
  • from 相对路径中的./不能省略(一般情况下,相对路径的./可以省略),因为省略的话,认为是在导入安装了的第三方模块
  • from 可以使用绝对路径或者url来导入模块
  • 如果不提取成员,只需要使用import 'path'
  • 导入所有成员并提供命名空间import * as name from 'path'
  • from 后不能用变量来提供路径
  • 动态导入函数import('path'),返回的是一个Promise
import('./module.js').then(function(module) {
// module是导出成员
})
同时导入模块的默认成员与具名成员
// 导出
export { name, age }
export default 'default value'
// 导入, 将默认成员放在统一导入里,但需要用as关键字来命名
import { name, age, default as value } from './module.js'
// 或者,默认成员的导入与统一导入分开,这样就不需要用as来命名
import value, { name, age } from './module.js'
导出直接导入的成员
  • 这种语法通常用于在入口文件,对资源进行统一的导出管理时用到。
// ./components/index.js
// 直接将module.js中的成员导出,这种语法就是把import替换为export
export { name } from './module1.js'
export { age } from './module2.js'
// app.js
import { name, age } from './components/index.js'
浏览器中ES Module的Polyfill

当浏览器对ES Module不支持时,需要使用Polyfill或者Babel等编译工具来支持(生产阶段不这样使用,因为这是运行时编译,导致执行速度大大降低)。

  • browser-es-module-loader
  • 使用polyfill时,可能会造成支持ES Module的浏览器会对module文件执行两次。这个问题可以通过给<sciprt>标签添加一个nomodule属性来解决。
    • nomodule属性表示如果浏览器不支持ES Module,则执行该脚本;如果浏览器支持ES Module,则不执行该脚本。
在node.js中的ES Module

node从8.5以上开始逐渐实验性支持ES Module。

  • ES Module的文件使用.mjs的后缀
  • 使用node --experimental-modules来启动node
  • node内置模块一般都有默认导出和具名导出,而第三方模块一般都是默认导出,没有具名导出。
在node中使用ES Module导入CommonJS模块

CommonJS始终只会导出一个默认成员(对象、函数、变量等),而且import {} from ''不是一个对象解构的语法

// node环境
// index.js
import mod from './commonjs.js'
console.log(mod.foo); // 'commonjs exports foo'
// commonjs.js
module.exports = {
  foo: 'commonjs exports foo'
}
不能在CommonJS模块中通过require()导入ES Module模块
// node环境
// index.js
export const foo = 'es module foo';
// commonjs.js
const foo = require('./index.js'); // 会报错
ES Module与CommonJS在node中的差异

node中提供的与模块相关的全局变量和全局函数只能在CommonJS模块中引用,ES Module模块拿不到。实际上,CommonJS模块代码加载时会最终加载到一个函数中,这个函数提供了下列的全局变量。

  • require()
  • module
  • exports
  • __filename:在ES Module中,可以通过以下方式来访问到
    • 导入url模块的fileURLToPath方法:import { fileURLToPath } from 'url'
    • 传入import.meta.url当前文件的文件URL将其转换为文件路径const __filename = fileURLToPath(import.meta.url)
  • __dirname:在ES Module中,也可以通过上述方式访问
    • 导入path模块的dirname方法:import { dirname } from 'path'
    • 传入上述得到的__filename:const __dirname = dirname(__filename)
node新版本对ES Module的进一步支持
  • package.json中type字段设置为"module",表示该目录下的JS文件都是ES Module,因此,也不需要后缀名.mjs,直接.js即可。
  • 如果需要在目录下使用CommonJS的模块,则将后缀名改为.cjs,即可在文件中使用CommonJS模块规范(require, module, exports, __filename, __dirname)。
使用Babel兼容早期Node版本对ES Module的支持
  • 下载开发依赖包
    • @babel/node
    • @babel/core
    • @babel/preset-env
  • 使用npx babel-node启动
  • 使用babel-node index.js --presets=@babel/preset-env来运行index.js文件。
  • 或者使用配置文件.babelrc,添加presets的配置。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值