JS模块化

214 阅读4分钟

一、JS模块化概念

  • 什么是模块?
    将一个复杂的程序依据一定的规范封装程几个块(文件),并进行组合在一起。块的内部数据/实现是私有的,只是向外部暴露一些接口(方法)与外部其他模块通讯。

  • 为什么要模块化?

    • 代码功能间节解藕,降低代码复杂度
    • 代码功能方便服用
    • 方便部署
  • 模块化的好处和问题

    • 好处:避免命名冲突(减少命名空间污染),更好的分离, 按需加载,更高复用性,高可维护性
    • 问题:页面引入加载script,产生的问题:请求过多,依赖模糊,难以维护
  • JS模块化发展历史

    • 全局function模式:

      • 将不同的功能封装成不同的全局函数
      <script type='text/javascript' src='module1.js'></script>
      <script type='text/javascript' src='module2.js'></script>
      // module1.js
      function getName () {}
      // module2.js
      function getName () {}
      // 导致问题:每个模块定义的方法都在同一个全局作用域环境中,后面模块的方法/属性会覆盖前面模块的同名方法和属性
      
      • 问题: Global被污染了, 很容易引起命名冲突
    • namespace模式

      • 将不同模块的方法/属性放在一个指定的对象下面,简单对象封装,减少了全局变量
      // module1.js
      let myModule1 = {
        name: 'myModule1',
        foo() {
          console.log(`foo() ${this.name}`)
        }
      }
      // module2.js
      let myModule2 = {
        name: 'myModule2',
        foo() {
          console.log(`foo() ${this.name}`)
        }
      }
      
      • 问题: 本质就是对象,不安全(数据不是私有的, 外部可以直接修改)
      myModule1.name = 'other name' //能直接修改模块内部的数据
      
    • IIFE模式: 匿名函数自调用(闭包)

      • IIFE : immediately-invoked function expression(立即调用函数表达式)
      • 作用: 数据是私有的, 外部只能通过暴露的方法操作
      • 问题: 如果当前这个模块依赖另一个模块怎么办
      (function (window) {
        let name = 'myModuleName' // 模块内部数据
        function bar() { //用于暴露有函数
          console.log(`bar() ${name}`)//操作数据的函数
          otherFun() //内部调用
        }
        function otherFun() { //内部私有的函数
          console.log('otherFun()')
        }
        //暴露行为
        window.myModule = { bar }
      })(window)
      
    • IIFE模式增强 : 引入依赖

      • 现代模块实现的基石
      (function (window, $) {
        let name = 'myModuleName' // 数据
        function bar() {  // 用于暴露有函数
          console.log(`bar() ${name}`)
          $('body').css('background', 'red')
          otherFun() // 内部调用
        }
        function otherFun() { //内部私有的函数
          console.log('otherFun()')
        }
        //暴露行为
        window.myModule = {foo, bar}
      })(window, jQuery) // 注入依赖
      

二、JS模块化规范

异步模块定义(AMD)是Asynchronous Module Definition的缩写,是 RequireJS 在推广过程中对模块定义的规范化产出。

通用模块定义(CMD)是Common Module Definition的缩写,是SeaJS 在推广过程中对模块定义的规范化产出。

RequireJS 和 SeaJS 都是模块化框架的代表,AMD和CMD,是他们各自定义模块化的方式,大同小异,主要是代码风格和API不同

1. CommonJs

  • Node.js就是基于CommonJs模块化规范实现的
  • 规范说明
    • 每个文件都可以当做一个模块
    • 在服务器端:模块的加载是运行时同步加载的
    • 在浏览器端:浏览器默认不支持模块规范的功能,模块需要提前编译打包处理
  • 规范使用
    • 暴露模块: 本质就是导出了一个名为 exports 的对象
      module.exports = {
          xxx: value
      }
      exports.xxx = value
      
    • 引入
      // 第三方模块:xxx为模块名
      // 自定义模块:xxx为模块文件路径
      require(xxx)
      
  • 规范实现
    • 服务器端实现
      • Node.js
    • 浏览器端实现
      • Browserify
      • 也称为commonJs的浏览器端打包工具

2. AMD

  • 规范说明
    • Asynchronous Module Definition(异步模块定义)
    • 专门用于浏览器端,模块的加载时异步的
  • 基本用法
    • 暴露模块:
      // 使用没有依赖的模块
      define(function() {
          return 模块
      })
      // 定义有依赖的模块
      define(['module1', 'module2'], function(m1, m2) {
          使用m1/m2实现一个功能的模块
          return 模块
      })
      
    • 引入使用模块
      // 使用有依赖的模块
      require(['module1', 'module2'], function(m1, m2) {
          使用m1/m2
      })
      
  • 规范实现(浏览器端)
    // mian.js
    (function () {
        requirejs.config({
            baseUrl: 'js/',
            // 显示声明指定的模块
            paths: {
                dataService: './modules/dataService',
                alerter: './modules/alerter',
                jquery: './libs/jquery-1.10.1',
                // 库不一样,暴路的方式不一样,有的库支持AMD格式,有的不支持。angular就不支持
                // angular: './libs/angular', 
            },
            shim: {
                angular: {
                    exports: 'angular'
                }
            }
        })
    
        // 使用
        requirejs(['alerter'], function (alerter){
            // ...
        })
    })()
    
    <!-- 引入require.js并指定js主文件的入口 -->
    <script data-main="js/mian.js" src="js/require.js"></script>
    

3. CMD

  • 规范说明
    • Common Module Definition(通用模块定义)
    • 专门用于浏览器端,模块的加载时异步的
    • 模块在使用的时候才会加载
  • 基本用法
    • 暴露模块:
      // 定义没有依赖的模块 ./module1.js
      define(function(require, exports, module) {
          exports.xxx = value
          module.exports = {xxx: value}
      })
      // 定义有依赖的模块 ./module4.js
      define(function(require, exports, module) {
          // 同步引入依赖模块
          var module2 = require('./module2')
          // 异步引入依赖模块
          var module3 = require.async('./module3', function(m3){
              // m3 就是异步引入的模块module3
          })
          // 暴露模块
          exports.xxx = value
      })
      
    • 引入使用模块
      // 使用有依赖的模块 ./mian.js
      define(function(require, exports, module) {
          var m1 = require('./module1')
          var m4 = require('./module4')
          m1.show()
          m4.show()
      })
      
  • 规范实现(浏览器端)
    <!-- 引入require.js并指定js主文件的入口 -->
    <script src="js/libs/sea.js"></script>
    <script>
        seajs.use("./js/modules/mian.js")
    </script>
    

4. ESModule

  • 规范说明
    • 依赖模块需要编译打包处理
  • 语法
    • 导出模块
      • export.xxx = value
      • export default {xxx: value}
    • 引入使用模块
      • import {xxx} from './nodule.js'
      • import module from './nodule.js'
  • 规范实现(浏览器端)
    • 使用Babel将ES6编译为ES5代码
    • 使用Browserify编译打包js