JS模块化详解

113 阅读3分钟

1、js历史

  • 发展历程:无模块化 => IIFE(立即执行函数)=> CommonJs => AMD => CMD => ES6 Module
无模块化

1、在不同页面中引入不同js文件 2、多js文件是为了维护和可读性 3、不同js文件在同一页面中被引用

<script src="js/jquery.js"></script>
<script src="vue.js"></script>
<script src="swiper.js"></script>

4、问题:a、命名冲突 b、全局变量污染 c、代码可读性差 d、维护性差

IIFE(立即执行函数)
作用域
  • let const 块级作用域
   let a = 1;
   // 常量不能修改,但是可以修改属性
   const b = 2; 
   const obj = {name: 'zhangsan'};
   obj.name = 'lisi';
   // 模块
   const c = (() => {
      let d = 3;
      return {
         inadd: () => ++d,
         reset: () => d = 0,
      }
   })();
   c.inadd();
   c.reset();
  • 依赖其他模块
   const c = ((module1,module2) => {
      let d = 3;
      return {
         inadd: () => ++d,
         reset: () => d = 0,
      }
   })(module1,module2);
  • 面试题:了解早期jQuery的依赖处理以及模块加载方案吗? IIFE 加上 传参调配
// jquery等框架实际应用会涉及到revealing的写法, jquery 更强调 所有API一局部变量的形式定义在函数中,而仅仅对外暴露出可被调用的接口
   const c = (() => {
      let d = 3;
      const inadd = () => ++d;
      const reset = () => d = 0;
      return {
         inadd,
         reset
      }
   })();
CommonJs
  • 模块化规范:CommonJs --- CJS node.js制定特征:
    • 通过module + exports对外暴露接口
    • 通过require引入模块 模块组织方式
       // commonjsModule.js
       const module1 = require('./module1');
       const module2 = require('./module2');
       let a = 0;
       const inadd = () => ++a;
       const reset = () => a = 0;
       // 暴露方式1
       exports.inadd = inadd;
       exports.reset = reset;
       // 暴露方式2
       module.exports = {
          inadd,
          reset
       }
       // 在 main.js 中引入
       const { inadd, reset } = require('./commonjsModule');
       inadd(); // 1
       const c = require('./commonjsModule');
       c.inadd(); // 2
    
    • 实际执行处理
       (function(exports, require, module, __filename, __dirname) {
          const module1 = require('./module1');
          const module2 = require('./module2');
    
          let a = 0;
          const inadd = () => ++a;
          const reset = () => a = 0;
          module.exports = {
                inadd,
                reset
          };
    
          return module.exports;
       }).call(thisValue, exports, require, module, filename, dirname);
    
       (function (exports, require, module, __filename, __dirname) {
          const commonJSCounterModule = require('./commonJSCounterModule')
          commonJSCounterModule.inadd();
       }).call(thisValue, exports, require, module, filename, dirname);
       优点: CommonJS规范在服务器端率先完成了JavaScript的模块化,解决了依赖、全局变量污染的问题,这也是js运行在服务端运行的必要条件 
       缺点: 由于服务端以及commonjs是同步加载模块的 新的问题:异步
    
AMD规范
  • 非同步加载模块,允许制定回调函数 经典的实现框架:require.js
    • 新增了定义的方式:
       // 通过define来定义一个模块,然后require加载
       define(id, [depends], callback)
       require([module], callback)
    
    • 模块的定义方式:
       define(
          'amdCounterModule', 
          ['module1', 'module2'], 
          (module1, module2) => {
             let a = 0;
             const inadd = () => ++a;
             const reset = () => a = 0;
             return {
                   inadd,
                   reset
             }
          }
       )
    
       require(
          ['amdCounterModule'],
          amdCounterModule => {
             amdCounterModule.inadd();
             amdCounterModule.reset();
          }
       )
    
    • ** 面试题2: 如果想在AMD中使用require方式加载同步模块可以么? AMD支持向前兼容的,以提供回调的形式来做require方法动态加载模块
          define( require => {
             const module1 = require('./module1');
             const module2 = require('./module2');
             let a = 0;
             const inadd = () => ++a;
             const reset = () => a = 0;
             return {
                   inadd,
                   reset
             }
             // revealing 
             expports.inadd = inadd;
             exports.reset = reset;
          })
      
    • ** 面试题3: 有没有什么方式可以统一兼容AMD和common UMD的出现
      (define => define((require, exports, module) => {
        const module1 = require('./module1');
        const module2 = require('./module2');
        let a = 0;
        const inadd = () => ++a;
        const reset = () => a = 0;
        module.exports = {
             inadd,
             reset
        }
      }))(
          // 判断区分AMD和commonjs
          typeof module === 'object' 
          && module.exports 
          && typeof define !== 'function' 
          ? //CJS
          factory => module.exports = factory(require, exports, module) 
          : //AMD
          define
      )
       // 优点:适合在浏览器环境中异步加载模块,同时又已采用common模块
       // 缺点:提高了开发成本,并且不能按需加载,必须提前加载所有依赖
    
CMD规范
  • 应用于可优化方案中,代表:sea.js 特征:支持按需加载
   define(function(require, exports, module) {
      var $ = require('jquery');
      var dependencyModule1 = require('./dependencyModule1');

      // ……
   })
  • ** 面试题:CMD 和 AMD 区别
 // AMD
   define([
      './module1',
      './module2'
   ], function(dependencyModule1, dependencyModule2) {
      module1.inadd();
      module2.reset();
   })

   // CMD - 依赖就近
   define(
      function(require, exports, module) {
         let module1 = require('./module1');
         module1.inadd();
      }
   )
ES6模块化 - ESM
  • 新增定义方式: 引入:import 导出:export
   import module1 from './module1';
   import module2 from './module2';
   let a = 0;
   const inadd = () => ++a;
   const reset = () => a = 0;
   module.exports = {
      inadd,
      reset
   }
  • ** 面试题:动态模块的加载
   import ('./esModule').then(({ inadd, reset }) => {
        inadd();
        reset();
    });

    import ('./esModule').then((dynamicESModule) => {
        dynamicESModule.inadd();
        dynamicESModule.reset();
    });