1

前端模块化

1. 模块化优点

目前由于MVVM模式的流行,各种语言都更注重模块化。模块化设计的好处:

  1. 作用域:避免全局变量污染;
  2. 复用:可重复利用封装的模块为不同地方实现相同功能;
  3. 解耦:减少不同功能代码相互关联性,让代码分工明确,也方便Debug;
  4. 按需加载:加快加载速度、异步加载不必要的部分;

2. 模块化方法整理

从最早的script标签开始,前端模块化经过了多种编程方案的演化,逐步完善。

2.1 script标签

<script src="module_a.js"></script>
<script src="module_b.js"></script>
<script src="main.js"></script>

所有的js文件共享全局作用域,容易引起作用域污染。

2.2 闭包函数(立即执行函数)

(function(){
    // xxx
})()

这是笔者早年使用最多的js编程方式 0_0

虽然避免了作用域污染的问题,但多个文件内的函数互相调用时,处理较为麻烦。常用方法是:

  1. 在闭包内定义一个 window.myFunc = function(){} 的方法,在闭包外可以调用;
  2. 使用事件监听,给需要外部调用的方法设置事件,从外部触发事件;介绍一种自定义事件来控制闭包函数间传值的方法:
// 利用闭包函数自定义一个事件监听触发机制
// 自定义机制,不会受到默认的事件影响
var EventManager = (function() {
    var events = {}
    return {
        add: function(name, fn) {
            if(!events[name]){
                events[name] = [];
            }
            events[name].push(fn);
        },
        fire: function(name, args) {
            var fnList = events[name];
            if(fnList){
                for (var i = 0, l = fnList.length; i < l; i++) {
                    var fn = fnList[i];
                    if (fn && typeof fn == 'function') {
                        fn(args);
                    }
                }
            }
        }
    }
})()   

// 在闭包内监听,可调用闭包内方法
(function() {
    EventManager.add('user.login', function(data) {
        console.log('user.login', data)
    })
})()

// 在任意位置触发
EventManager.fire('user.login', {name: 'lc'})

自定义事件监听,相对于window下的函数,更加灵活,也更符合模块化的思想。举个例子:
通信系统中,用户登录后,需要获取聊天记录、通信录、个人信息,这些分别在不同的模块(闭包)中。
如果用函数的思想,需要在登录的地方进行不同的方法调用,这样就使得登录模块与多个业务模块产生了耦合;如果用自定义事件的方法,登录后只需要广播一个事件,同时在多个业务模块分别监听事件,各模块间就完全没有耦合,就算任意删掉一个模块也可以保证其他模块正常运行。

存在问题:

不论是全局函数、全局事件、自定义事件,在调用每个闭包中的方法时,斗需要确保该闭包先执行后调用。在复杂项目中,需要先执行大量闭包函数,会导致启动慢、逻辑复杂等各种问题。

2.3 AMD - 异步模块定义

define('myfunc', ['math'], function(math) {
    math.sum(1, 2)
});

通过 define函数引入需要的依赖包,每个模块所依赖的包/模块一目了然。

2.4 CMD - 通用模块定义

define(function(require, exports, module) {
    const math = require('math')
    math.sum(1, 2)
})

CMD的原则是将引入模块尽量后置,在使用的时候才去引入。
这样使得js执行时效率更高,更符合lazy load的思维方式,但对代码管理确不是很方便。

2.5. CommonJS

const math = require('math')

module.exports = function() {
    math.sum(1, 2)
}

目前NodeJS的模块管理常用的就是这种方式,很多NPM的包也是这样处理的模块引入。

2.6 ES6的模块化

import { myFunc1, myFunc2 } from 'myFuncs';
import Vue from 'vue';

export function hello() {};
export default {
  // ...
};

Vue、React等常用框架目前都在使用这种模块化方法。

比如在vue中,配合vue-router,在组件中按需import模块或模块中的函数,可以通过webpack实现按需加载。同时,这种模块化方式使得模块间的方法调用更加方便,不需要考虑模块声明前后顺序,因为webpack会自动生成依赖树。

2.7 样式文件模块化

// util.less
.common {
    color: pink;
}

// main.less
@import 'util.less'
.red {
    color: red;
}

样式文件目前也支持模块化。


参考:《深入浅出Webpack》


LichKing24
72 声望6 粉丝

世界与你而言,