模块化

34 阅读2分钟

模块化

模块的必要条件

  1. 必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
  2. 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

这里的必要条件理解

  1. 有个封闭的函数,并且调用返回一个模块实例
  2. 返回的实例,至少要有个函数能够修改内部属性或状态
function CoolModule() {
    var something = "cool";
    var another = [1, 2, 3];
    function doSomething() { 
        console.log( something );
    }
    function doAnother() {
        console.log( another.join( " ! " ) );
    }
    return {
        doSomething: doSomething, 
        doAnother: doAnother
    };
}
var foo = CoolModule(); 
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

上面的例子

CoolModule 每次调用,都会生成一个新的函数。

简写,IIFE

var foo = (function CoolModule() {
    var something = 'cool';
    var another = [1,2,3];

    function doSomething() {}

    function doAnother() {}

    return {
        doSomething: doSomething,
        doAnother: doAnother
    }
})();

foo.doSomething();
foo.doAnother();

模块命名

var foo = (function CoolModule(id) {
    function change() {
        // 修改公共 API
        publicAPI.identify = identify2;
    }
    function identify1() { 
        console.log( id );
    }
    function identify2() {
        console.log( id.toUpperCase() );
    }
    var publicAPI = { 
        change: change,
        identify: identify1
    };
    return publicAPI;
})( "foo module" );
foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE

通过在模块实例的内部保留对公共 API 对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改它们的值。

上面的例子,一直在讲两个必要条件

  1. 封闭函数,返回实例
  2. 返回体能够修改内部属性

现代的模块机制

大多数模块依赖加载器/管理器本质上都是将这种模块定义封装在一个友好的 API。这里并不会研究某个具体的库,为了宏观了解会简单地介绍一些核心概念。

var MyModules = (function Manager() {
    var modules = {};

    function define(name, deps, impl) {
        for (var i=0; i<deps.length; i++) {
            deps[i] = modules[deps[i]];
        }
        modules[name] = impl.apply( impl, deps );
    }

    function get(name) {
        return modules[name];
    }

    return {
        define: define,
        get: get
    }
})()

上面有两部分组成,一个是定义,一个获取

定义时,这个模块需要的模块要作为参数,放进去。

这里我来复写一遍:

// 模块,定义,和获取
// 注意,定义时,要将该模块所需模块作为参数放入
var MyModules = (function() {
    var modules = {};

    function define(name, needModuleName, handle) {
        for(var i=0; i<needModuleName.length, i++) {
            needModuleName[i] = modules[needModuleName[i]];
        }

        modules[name] = handle.apply(handle, needModuleName);
    }

    function get(name) {
        return modules[name];
    }

    return {
        define: define,
        get: get
    }
})()

// 可以,写出来了,记住核心要点就行

这段代码的核心是 modules[name] = impl.apply(impl, deps)。为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值,也就是模块的 API,储存在一个根据名字来管理的模块列表中。

下面展示了如何使用它来定义模块:

MyModules.define('bar', [], function() {
    function hello(who) {
        return "Let me introduce:" + who;
    }

    return {
        hello: hello
    }
})

MyModules.define('foo', ['bar'], function(bar) {
    var hungry = 'hippo';
    function awesome() {
        console.log(bar.hello(hungry).toUpperCase());
    }

    return {
        awesome: awesome
    }
})

var bar = MyModules.get('bar');
var foo = MyModules.get('foo');

console.log(bar.hello('hippo')); // Let me introduce: hippo

foo.awesome(); // LET ME INTRODUCE: HIPPO

未来的模块机制

ES6 中为模块增加了一级语法支持。但通过模块系统进行加载时,ES6 会将文件当作独立的模块来处理。每个模块都可以导入其他模块或特定的 API 成员,同样也可以导出自己的 API 成员。

基于函数的模块并不是一个能被稳定识别的模式(因为编译器无法识别,它们的 API 语义只有在运行时才会被考虑进来) 相比之下,ES6 模块 API 更加稳定(API 不会在运行时改变)。由于编辑器知道这一点,因此可以在编译期检查对导入模块的 API 成员的引用是否真实存在。如果 API 引用并不存在,编译器会在运行时抛出一个或多个“早期”错误,而不会像往常一样在运行期采用动态的解决方案。

export 和 import

小结

  1. 为创建内部作用域而调用了一个包装函数
  2. 包装函数的返回值必须至少包括一个对内部函数的引用