js模块化和前端工程化

99 阅读3分钟

一、为什么要做js模块化

命名重复:前端项目逐渐复杂,需要多人开发多种功能,命名污染全局作用域,不利于开发维护

文件分离:多种不同 功能的文件需要被分到不同文件中实现后再被引用

二、js模块化的方式和发展历程

-iife

const iifeModule = (function(dependencyModule1,dependencyModule2) {
    let count = 0;
    const increase = () => {
        if(count > dependencyModule1.num) return dependencyModule1.num; 
        return ++count;
    };
    const decrease = () => --count;
    const reset = () => {this.count = 0}
    return {
        increase,decrease,reset 
    }
})({num:8},dependencyModule2);
iifeModule.increase();
iifeModule.increase();
iifeModule.increase();
iifeModule.increase();
iifeModule.increase();

面试题 了解早期jquery是如何处理依赖以及模块化的吗?/了解iife是如何解决多方依赖的问题的? 答:iife加传参

-Common.js

-使用module.exports={}或者exports.increase=()=>{}去暴露一个模块

//main.js
const dependencyModule1 = require('./dependencyModule1');
let count = 0;
const increase = () => {
    if(count > dependencyModule1.num) return dependencyModule1.num; 
    return ++count;
};
const decrease = () => --count;
const reset = () => {this.count = 0}

exports.increase = increase;
exports.decrease = decrease;
exports.reset = reset;
// 或者
module.exports = {
    increase,decrease,reset
}

-使用require('/...')引入模块

const {increase,decrease,reset} = require(./main.js);
increase();
decrease();
reset();

-common.js的优缺点:

优点: Common.js率先在服务端实现了模块化,从框架层面解决依赖、全局变量污染的问题
缺点: 主要针对服务端,对客户端和浏览器的异步拉取依赖整合不是那么友好

-AMD(Require.js):允许异步加载依赖,允许指定回调函数

使用define('moduleName',[dependencyModules], callback)定义模块

define('countModule',['dependencyModule1'],dependencyModule1 => {
    let count = 0;
    const increase = () => {
        if(count > dependencyModule1.num) return dependencyModule1.num; 
        return ++count;
    };
    const decrease = () => --count;
    const reset = () => {this.count = 0}
    return {
        increase,decrease,reset
    }
})

使用require([dependencyModules],callback)

require(['countModule'],countModule => {
    countModule.increase();
})

面试题: 如果在countModule中想兼容已有代码,怎么办

define('countModule',[],require => {
    const dependencyModule1 = require('./dependencyModule1');
    let count = 0;
    const increase = () => {
        if(count > dependencyModule1.num) return dependencyModule1.num; 
        return ++count;
    };
    const decrease = () => --count;
    const reset = () => {this.count = 0}
    return {
        increase,decrease,reset
    }
})

面试题:AMD中使用揭露模式

define('countModule',[],(require,export,module) => {
    const dependencyModule1 = require('./dependencyModule1');
    let count = 0;
    const increase = () => {
        if(count > dependencyModule1.num) return dependencyModule1.num; 
        return ++count;
    };
    const decrease = () => --count;
    const reset = () => {this.count = 0}
    export.increase = increase;
    export.decrease = decrease;
    export.reset = reset;
})

-UDP

面试题:兼容AMD&CJS/如何判断CJS和AMD? 答: UDP就是兼容CJS和AMD,使用AMD作为基础,并兼容CJS

const CJSModule = function(require,export,module){
    const dependencyModule1 = require('./dependencyModule1');
    let count = 0;
    const increase = () => {
        if(count > dependencyModule1.num) return dependencyModule1.num; 
        return ++count;
    };
    const decrease = () => --count;
    const reset = () => {this.count = 0}

    exports.increase = increase;
    exports.decrease = decrease;
    exports.reset = reset;
}
const udpModule = (function(factory) {
    if(typeof module === "object"&& module.exports&& typeof define !== "function") {
        CJSModule(require,export,module);
    }else {
        define((require,export,module) => {
            CJSModule(require,export,module);
        })
    }
}))(CJSModule)
udpModule() 

-AMD的优缺点:

优点: 适合在浏览器中加载异步模块,可以并行加载多个模块
缺点:会有引入成本,不能按需加载

-CMD-按需加载,主要应用与sea.js

 define('module', (require, exports, module) => {
    let $ = require('jquery');
    // jquery相关逻辑

    let dependencyModule1 = require('./dependecyModule1');
    // dependencyModule1相关逻辑
  })

-CMD的优缺点:

优点: 按需加载,依赖就近
缺点:依赖于打包,加载逻辑存在于每个模块中,扩大模块体积

ES6模块化

-引入关键字 —— import

import antd from 'antd';
import React from 'react';

#### 导出关键字 —— export
```js
export default PageHeader;
export {PageHeader}
export function() {}
export const defaultEnum = {}

模板引入的地方

  <script type="module" src="esModule.js"></script>

node中:

  import { increase, reset } from './esModule.mjs';
  increase();
  reset();

  import esModule from './esModule.mjs';
  esModule.increase();
  esModule.reset();

面试题:动态模块**

考察:export promise

ES11原生解决方案:

  import('./esModule.js').then(dynamicEsModule => {
    dynamicEsModule.increase();
  })

ESModule优缺点

优点(重要性):通过一种最统一的形态整合了js的模块化
缺点(局限性):本质上还是运行时的依赖分析

三、前端工程化(完全体 webpack为核心的工程化 + mvvm框架组件化 + 设计模式)

  <!doctype html>
    <script src="main.js"></script>
    <script>
      // 给构建工具一个标识位
      require.config(__FRAME_CONFIG__);
    </script>
    <script>
      require(['a', 'e'], () => {
        // 业务处理
      })
    </script>
  </html>
  define('a', () => {
    let b = require('b');
    let c = require('c');

    export.run = () {
      // run
    }
  })
工程化实现

step1: 扫描依赖关系表:

  {
    a: ['b', 'c'],
    b: ['d'],
    e: []
  }

step2: 重新生成依赖数据模板

  <!doctype html>
    <script src="main.js"></script>
    <script>
      // 构建工具生成数据
      require.config({
        "deps": {
          a: ['b', 'c'],
          b: ['d'],
          e: []
        }
      })
    </script>
    <script>
      require(['a', 'e'], () => {
        // 业务处理
      })
    </script>
  </html>

step3: 执行工具,采用模块化方案解决模块化处理依赖

  define('a', ['b', 'c'], () => {
    // 执行代码
    export.run = () => {}
  })

优点:

  1. 构建时生成配置,运行时执行
  2. 最终转化成执行处理依赖
  3. 可以拓展