最常见的实现模块模式的方法通常被称为模块暴露
1. 模块及多例模式
            
            
              javascript
              
              
            
          
                  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()只是一个函数,必须要通过调用它来创建一个模块实例。如果不执行外部函数,内部作用域和闭包都无法被创建。
可以将这个对象类型的返回值看作本质上是模块的公共API。
jQuery就是一个很好的例子。jQuery和$标识符就是jQuery模块的公共API,但它们本身都是函数(由于函数也是对象,它们本身也可以拥有属性)
函数也是对象,也有属性
模块模式需要具备两个必要条件
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
2. 单例模式
上一个示例代码中有一个叫作CoolModule()的独立的模块创建器,可以被调用任意多次,每次调用都会创建一个新的模块实例。当只需要一个实例时,可以对这个模式进行简单的改进来实现单例模式:
(我们将模块函数转换成了立即执行函数IIFE),立即调用这个函数并将返回值直接赋值给单例的模块实例标识符foo。)
            
            
              javascript
              
              
            
          
                  var foo = (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
            };
        })();
        foo.doSomething(); // cool
        foo.doAnother(); // 1 ! 2 ! 3
        3. 模块传参
            
            
              scss
              
              
            
          
                  function CoolModule(id) {
            function identify() {
              console.log(id);
            }
            return {
              identify: identify
            };
        }
        var foo1 = CoolModule("foo 1");
        var foo2 = CoolModule("foo 2");
      foo1.identify();//"foo 1"
      foo2.identify();//"foo 2"
        4. 命名将要作为公共API返回的对象
            
            
              javascript
              
              
            
          
                  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();//修改公共API(foo)返回的对象identify(identify: identify1改为identify: identify2)
        foo.identify(); // FOO MODULE
        通过在模块实例的内部保留对公共API对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改它们的值。
5. 现代模块机制
            
            
              ini
              
              
            
          
                  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);
              //核心,为了模块的定义引入了包装函数(可以传入任何依赖),
              //并且将返回值,也就是模块的API,储存在一个根据名字来管理的模块列表中。
            }
            function get(name) {
              return modules[name];
            }
          
            return {
                define: define,
                get: get
            };
        })();
        上面代码分析:
用于模块内定义函数。
参数name函数名(模块名称);
参数deps数组(依赖项数组),数组值是已定义的函数名,用于在本次定义函数内调用已定义过的函数模块;
参数impl函数模块(实现函数),内部定义函数,并以对象形式返回函数体
应用如下:
            
            
              javascript
              
              
            
          
                  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
        "foo"和"bar"模块都是通过一个返回公共API的函数来定义的。"foo"甚至接受"bar"的实例作为依赖参数,并能相应地使用它。
MyModules包含定义模块define和获取模块get
事例中,先通过调用MyModules.define在MyModules中定义了bar模块,又定义了foo模块,并通过传参deps在foo中定义并调用bar
它们符合前面列出的模块模式的两个特点:调用包装了函数定义的包装函数,并且将返回值作为该模块的API。
6. 未来模块机制
在通过模块系统进行加载时,ES6会将文件当作独立的模块来处理
es6中,文件就是模块
基于函数的模块 并不是一个能被静态识别的模式(编译器无法识别),它们的API语义只有在运行时才会被考虑进来。上面例子返回一个对象{define:define函数,get:get函数,或某个值},是运行时才执行的,不是编译时
相比之下,ES6模块 API是静态的(API不会在运行时改变) 。编译期检查对导入模块的API成员的引用是否真实存在。如果API引用并不存在,编译器会在编译时就抛出"早期"错误,而不会等到运行期再动态解析(并且报错)。
ES6的模块没有"行内"格式,必须被定义在独立的文件中(一个文件一个模块)。浏览器或引擎有一个默认的"模块加载器"(可以被重载,但这远超出了我们的讨论范围)可以在导入模块时同步地加载模块文件。
8.6.1. 导入模块(import,module,export区别)
import和module导入模块,模块是自动执行函数
import 可以将一个模块中的一个或多个API 导入到当前作用域中,并分别绑定在一个变量上(在我们的例子里是hello)。
module 会将整个模块的API 导入并绑定到一个变量上(在我们的例子里是foo和bar)。
export 会将当前模块的一个标识符(变量、函数)导出为公共API。
模块文件中的内容会被当作好像包含在作用域闭包中一样来处理,就和前面介绍的函数闭包模块一样。
参考
- 《你不知道的JavaScript》
 
最后
这是JavaScript系列第5篇,下一篇更新《this》。
小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!
您对我的关注、点赞和收藏,是对我最大的支持!欢迎关注、评论、讨论和指正!