• 前端的导入导出:「CommonJS」「ES Module」模块化规范


    前言

    模块化开发有助于我们将代码进行拆分,便于开发和维护,但如果不清楚模块化规范,就会在开发时不知道该用 require 还是 import,导出时该用 export 还是 module.exports,所以我们必须搞清除它们的区别和事情的来龙去脉。

    本篇主要内容是 CommonJS 和 ES Module 规范。其它还有 AMD、CMD、UMD规范,感兴趣的小伙伴可以自行了解一下。

    什么是前端模块化

    随着前端项目越做越大,功能越来越多,我们不能把所有代码写在一个 js 中,而是把代码按照不同的功能进行划分,但是代码越来越多,代码之间的引用嵌套越来越深,我们又不得不花费大量时间去管理和维护,如何提高代码的管理效率?就是通过模块化。

    模块化不但是一种代码组织形式,也是一种思想,我们根据代码的不同功能,来划分不同模块,目的是方便管理代码,从而提升开发效率。

    模块化的演进过程

    模块化规范不是一夜之间突然出现,而是像时代一样,有着演进过程:

    1. 石器时代:我们通过 script 标签引入 js 文件,并且约定,一个文件代表一个模块,这种方式很好理解,但存在很多问题
      1. 缺少私有空间,也就是模块内部成员可以在外部被修改
      2. 所有模块作用在全局,容易发生命名冲突,变量污染
      3. 无法管理模块的依赖关系,如果引用顺序出错,程序将难以运行
    2. 青铜器时代:使用命名空间模式,就是给每一个模块暴露出一个对象,把模块内的所有成员挂载到这个对象下,这就有点模块化内味儿了,但还是无法解决私有空间的问题,模块成员在外部依然可以被修改
    3. 蒸汽机时代:使用 IIFE(立即调用函数表达式)提供私有作用域,当函数变成立即执行的函数表达式时,表达式中的变量不能从外部访问,也就是形成了闭包,然后再将对象暴露出去,挂载到全局
    // 蒸汽机时代:使用 IIFE 提供私有作用域的方式
    ;(function(){
      // 通过闭包,避免私有成员被外部修改
      var msg = 'hello world' 
      function method(){ 
          console.log(msg)
      }
      // 挂载到全局
      window.module = {
          method:method
      }
    })()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    通过 IIFE 我们解决了私有作用域的问题,却无法解决 script 标签引入的问题,当 index.html 中引入了十几个 script 标签,还要维护他们的引入顺序时,那是相当痛苦的。

    1. 开源时代,百家争鸣。百花齐放,JavaScript 社区孕育出了 CommonJS 规范。

    回顾CommonJS和ES Module使用方式

    先回顾下 CommonJSES Module 常用的方式,加深下印象:

    • CommonJS导入导出
      • require
      • module.exports
      • exports
    • ES Module导入导出
      • import
      • export
      • export default
        把他们归类一下,就好区分了,其中 import 还有几种特殊用法,接着往下看。

    CommonJS 规范

    CommonJS 首先帮我们解决了 script 标签引入的问题,只需要提供一个 script 标签作为入口文件,模块之间的引用可以交给 CommonJS

    我们来快速了解一下 CommonJS 规范:

    • CommonJS 源自社区
    • CommonJS 的出现早于 ES Module 规范
    • CommonJS 被大量使用在 node.js
    • 使用 module.exports 导出模块,使用 require 导入模块
    • exports 也可以导出模块,它的本质还是引用了 module.exports
    • CommonJS 是同步加载模块,这点与 ES Module 不同

    CommonJS 导出

    可以导出任意类型

    // module.js
    module.exports = {
        name:'banana',
        age:18,
        eat:function(){
            console.log('I like eating bananas')
        }
    }
    module.exports.userName = 'admin'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    CommonJS 导入

    // app.js
    const obj = require('./module.js')
    console.log(obj) // { name: 'banana', age: 18, eat: [Function: eat], userName: 'admin' }
    
    // 如果只想导入某个属性,可以使用解构赋值
    const { name } = require('./module')
    console.log(name) // 'banana'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    CommonJS 不适用于浏览器

    因为 CommonJS 是同步加载模块,而加载模块就是去服务端获取模块,加载速度会受网络影响,假如一个模块加载很慢,后面的程序就无法执行,页面就会假死。而服务端能够使用 CommonJS 的原因是代码本身就存储于服务器,加载模块就是读取磁盘文件,这个过程会快很多,不用担心阻塞的问题。
    所以浏览器加载模块只能使用异步加载,这就是 AMD 规范的诞生背景。

    ES Module 规范

    CommonJS 虽然很好,但是不适用于浏览器,于是 ES Module 应运而生。
    再来了解一下 ES Module

    • ES ModuleES6 之后新增的模块化规范,它从 Javascript 本身的语言层面,实现了模块化
    • ES Module 想要完成浏览器端、服务端的模块化大一统,成为通用解决方案
    • 使用 export 导出模块,使用 import 导入模块
    • 通过 as 关键词,对导出对象重命名,也可以通过 as 对导入对象重命名

    ES Module 导出

    可以导出任意类型

    // module.js
    const obj = {
        name:'banana',
        age:18,
        eat:()=>{
            console.log('I like eating bananas')
        }
    }
    const userName = 'admin'
    
    export { obj,userName }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ES Module 导入

    // app.js
    import { obj,userName } from './module.js'
    
    • 1
    • 2

    通过 as 重命名导出

    // module.js
    const userName = 'admin'
    const passWorld = '密码是我生日'
    
    export { 
        userName as name,
        passWorld as pass
    }
    
    // app.js
    import { name,pass } from './module.js'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    default 默认导出

    默认导出一个成员

    // module.js
    const name = 'banana'
    export default name
    
    // app.js
    import newName from './module.js' // 此时可以用新的变量名接收
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    默认导出多个成员

    // module.js
    export default {
        name:'banana',
        age:18,
        eat:()=>{
            console.log('I like eating bananas')
        }
    }
    
    // app.js
    import handle from './module.js'
    console.log(handle.name) // banana
    handle.eat() // I like eating bananas
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ES Module 注意点

    • 当我们只想运行模块,而不是获取其中变量时,可以这么写 import './module.js'
    • 需要导出大量成员时,可以用一个变量来接收
    export { name,age,address,tel,gender,...... } // 导出了很多的成员
    import * as obj from './module.js' // 使用 obj 来接收
    
    • 1
    • 2
    • 同时导出命名成员和默认成员
    const name='banana',age=18;
    export { name,age }
    export default 'default value'
    
    import { name, age, default as title } from './module.js' // 此时默认成员需要用 default as 来接收
    import title, { name, age } from './module.js' // 简写的方式,将默认成员放在最前面
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用 ES Module 执行 JS 代码

    • 通过给 script 标签添加 type=“module” 属性,可以用 ES Module 的标准来执行 JS 代码
    • 使用 ES Module 的 JS,会延迟执行,有点类似于 defer 属性
    
    <script type="module">
      console.log(this) //undefined
    script>
    
    
    <script type="module">
      const name = 'banana'
    script>
    <script type="module">
      console.log(name) //undefined
    script>
    
    
    <script type="module" src="http://www.baidu.com" /> // 报错
    
    <!-- ESM 的 script 标签会延迟执行,当 html 加载完毕后,再执行 script,相当于添加了 defer 属性 -->
    <script>
      // 阻塞下面的 p 标签显示
      alert('hello')
    script>
    <p>内容1p>
    <script type="module">
      // 不会阻塞下面的 p 标签显示
      alert('hello')
    script>
    <p>内容2p>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    node 对 ES Module 的支持

    node@8.0 之前的版本还不支持 ES Module,不过可以通过 babel 来解决

    // 安装 babel 插件
    yarn add @babel/node @babel/core @babel/preset-env -D
    // 运行babel
    yarn babel-node
    // 运行文件和插件
    yarn babel-node index.js --persets=@babel/preset-env
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    Mac使用快捷指令启动jupyter
    功能点估算方法,如何让估算偏差更小?
    企业架构LNMP学习笔记27
    如何抑制告警风暴?
    纳瓦尔宝典读书笔记
    动动手指自己“造”芯片,Google 推出芯片设计网站
    STM8S系列基于STVD开发,自定义printf函数+TIM5精确延时函数模块化工程示例
    prism.js使用图文教程
    解决vim与外界的复制粘贴(不用安装插件)
    985测试工程师被吊打,学历和经验到底谁更重要?
  • 原文地址:https://blog.csdn.net/qq_45225759/article/details/127855574