前端模块化开发-ESModules


【注】本文为来自CSDN博主「Bal炎魔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接在此

并做了一些细节上的改进~

目录

模块化的演变

1、文件划分

2、命名空间方式

3、函数包裹

模块化规范

1. CommonJS 规范

2. AMD(Asynchronous Module Definition)规范

3. CMD(Sea.js)

4. ES Modules

1、基本特性

2、导入与导出

3、关于导入一些用法

4、ESModules 浏览器兼容问题

5、node中的ESModules

6、ESModules 在node中与CommonJS交互

7、ESModules 在低版本node中使用Babel兼容


模块化的演变

1、文件划分

最早是通过页面的script标签区分,一个标签就是一个模块,通过全局变量来通信;

这种模式的弊端:

  • 污染全局作用域,变量可以在外部访问和修改;
  • 命名容易冲突;
  • 无法管理模块间的依赖关系;
  • 全靠相互约定来实现,项目规模变大后,容易出问题;

2、命名空间方式

  • 每个模块暴露一个全局对象,模块内的方法通过全局对象传递;
  • 只解决了命名冲突问题;

3、函数包裹

  • 利用立即执行函数,包裹内容,将其封装在私有空间内,利用全局对象暴露对外通信方法;
  • 保证了内部变量的私有性,通过闭包的方式访问;
  • 并且利用函数参数,作为模块儿依赖传入,解决模块间依赖的问题;
  • 模块儿之间的引用靠人工来写很容易出问题和误操作,遗留隐患

模块化规范

1. CommonJS 规范

node提出的标准,在node运行,以同步模式加载模块,但不适合浏览器中使用,早期未选择。

  • 一个文件就是一个模块;
  • 每个模块都有单独的作用域;
  • 通过module.exports导出成员;
  • 通过require函数载入模块

2. AMD(Asynchronous Module Definition)规范

Require.js实现了此规范

 大多数第三方库都支持AMD,但是

  • 其使用相对较为复杂,容易和业务代码耦合,
  • 模块JS文件请求频繁;

3. CMD(Sea.js)

淘宝推出的规范,类似CommonJS规范,但是使用像AMD,为了降低开发者的学习成本;Require.js也兼容了。

4. ES Modules

模块化标准,在node中,就是CommonJS。在浏览器中,就是ES Modules(语言层面统一规范)。

1、基本特性

  • 自动采用严格模式,忽略"use strict";
  • 每个ESM模块都是单独的私有作用域;
  • ESM是通过CORS去请求外部JS模块的;
  • ESM的 script 标签自动延迟 执行脚本,即先渲染;
<body>
  <!-- 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了 -->
  <script type="module">
    console.log('this is es module')
  </script>

  <!-- 1. ESM 自动采用严格模式,忽略 'use strict' -->
  <script type="module">
    console.log(this)
  </script>

  <!-- 2. 每个 ES Module 都是运行在单独的私有作用域中 -->
  <script type="module">
    var foo = 100
  </script>
  <script type="module">
    console.log(foo) //undefined
  </script>

  <!-- 3. ESM 是通过 CORS 的方式请求外部 JS 模块的 有跨域问题 -->
  <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>

  <!-- 4. ESM 的 script 标签会延迟执行脚本 -->
  <script defer src="demo.js"></script>
  <p>需要显示的内容</p>
</body>

2、导入与导出

导入与导出有几种不同的方式

  • 导出
// ------开头用export声明----------
export const name = 'Lili'
export function hello () {
    console.log('hello')
}

// ------最后统一用export导出----------
const name = 'Lili'
function hello () {
    console.log('hello')
}
export { name, hello } 

// -----导出时利用as重命名-----------
export { 
    name as fooname, // 对应导入时应该用fooname
    hello as foohello,
    xxx as default // 默认导出
}

// ------设置默认导出---------------
export default name 
// 对应导入应该主动重命名
// 或者导入时直接import变量名,去接受这个默认导出的成员(见下)
  • 导入
import { name, hello } from './module.js';
// -----------------------------
import xxx, { fooname, foohello } from './module.js';
import { fooname, foohello, xxx as foox} from './module.js'; // 或者这种形式(反正要重命名)

// --导入时直接import变量名-------
import name from './module.js';
  • 注意:
1、export { name, hello } 并不是导出一个对象,而是一种固定的写法结构,
因此 import { name, hello } from './module.js';
也不是导出了对对象的解构,也是一种固定的写法结构

不可写为 export { name: age , hello } //报错

而 export default { name: age }是导出一个对象,可以直接写和使用;


2、export 是导出变量的引用关系(无论是否对象),不是复制一份,改变原值这里也是改变;
并且,通过 import 导入的成员是只读的,不可修改;


3、关于导入一些用法

原生的ESModules import导入,后面不能省略,后缀名、不能省略index.js,必须以“./“或者”/“开始,相对和绝对路径,也可以直接使用连接完成的url。

import { name } from './module.js' //不能省略后缀名
import { lowercase } from './utils/index.js' //不能省略index.js
import { name } from '/04-import/module.js' //不能省略“./“(不然会被当成第三方模块)或者”/“(根目录开始)
import { name } from 'http://localhost:3000/04-import/module.js' //可以直接使用完整的url

// --------------
import {} from './module.js'
import './module.js'
// 只加载模块导入模块,不导入具体变量、成员

// ---------------
import * as mod from './module.js'
console.log(mod)
// 导入全部变量,并且作为 mod 内的属性

// ---------------
var modulePath = './module.js'
import(modulePath).then(function (module) {
  console.log(module)
})
// 当时导入路径为变量时,可通过全局函数 import 导入,
// 返回一个异步函数 promise,在回调函数中输出导入的值

// ----------------
import { name, age, default as title } from './module.js'
import title, { name, age } from './module.js'
// title为导入的默认值
console.log(name, age, title)
// 同时导入 其他值和默认值时,可以这样写

补充:

// 直接导出 导入的成员,一般可使用在 index.js 中

index.js 作为桥梁,组织起散落在各个模块中的 export,那么别的模块只需import index就行了

同样的,模块中的 default 也需要额外处理,重命名。

export { foo, bar } from './module.js'
export { default as foo1, bar } from './module.js'

// 直接导出 导入的成员,一般可使用在index.js中


4、ESModules 浏览器兼容问题

利用,Polyfill 引入脚本,但是实际工作中不推荐使用,效率较差

<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 nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>

5、node中的ESModules

目前是实验阶段,生成中不推荐使用
node 8.5.0 以上的版本已经支持,使用时要改扩展名为 mjs

并且加”–experimental-modules“ 参数

// node –experimental-modules index.mjs

// 第一,将文件的扩展名由 .js 改为 .mjs;
// 第二,启动时需要额外添加 `--experimental-modules` 参数;

// 我们也可以通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')


// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式(而第三方没有,只提供导出默认成员的方式)
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')

// 不支持,因为第三方模块都是导出默认成员
import { camelCase } from 'lodash'
console.log(camelCase('ES Module'))


// 对于第三方的 NPM 模块也可以通过 esm 加载
import _ from 'lodash'
_.camelCase('ES Module')

6、ESModules 在node中与CommonJS交互

  • ESModules中可以导入 CommonJS模块
  • 注意 import 不是解构导出对象
  • CommonJS始终只会导出一个默认成员
  • 而 CommonJS 中不可以导入 ESModulesmok
// CommonJS 模块始终只会导出一个默认成员
// module.exports = {
//   foo: 'commonjs exports value'
// }
exports.foo = 'commonjs exports value'

两者的一些差异
CommonJS中的一些全局成员

// 加载模块函数
console.log(require)
// 模块对象
console.log(module)
// 导出对象别名
console.log(exports)
// 当前文件的绝对路径
console.log(__filename)
// 当前文件所在目录
console.log(__dirname)

其在ESModules中都不能使用,相对应的提供了这些来替换

// 加载模块函数                ESModules
console.log(require)   ->     import
// 模块对象
console.log(module)    ->     export
// 导出对象别名
console.log(exports)   ->     export
// 当前文件的绝对路径
console.log(__filename)->     import.meta.url
// 当前文件所在目录
console.log(__dirname) ->     import.meta.url
// ---------------------------------------
// 通过 url 模块的 fileURLToPath 方法转换为路径
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)

7、ESModules 在低版本node中使用Babel兼容


安装,@babel/node、@babel/core、@babel/preset-env
.babelrc babel的配置文件
{
“presets”: ["@babel/preset-env"]
}
编译兼容最新特性
{
“plugins”: [
“@babel/plugin-transform-modules-commonjs”
]
}
单独插件的使用

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值