模块的原理及使用

最常见的实现模块模式的方法通常被称为模块暴露

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》。

小伙伴如果喜欢我的分享,可以动动您发财的手关注下我,我会持续更新的!!!

您对我的关注、点赞和收藏,是对我最大的支持!欢迎关注、评论、讨论和指正!

相关推荐
o***Z44810 小时前
前端性能优化案例
前端
张拭心10 小时前
前端没有实际的必要了?结合今年工作内容,谈谈我的看法
前端·ai编程
姜太小白10 小时前
【前端】CSS媒体查询响应式设计详解:@media (max-width: 600px) {……}
前端·css·媒体
weixin_4111918410 小时前
flutter中WebView的使用及JavaScript桥接的问题记录
javascript·flutter
HIT_Weston10 小时前
39、【Ubuntu】【远程开发】拉出内网 Web 服务:构建静态网页(二)
linux·前端·ubuntu
百***060110 小时前
SpringMVC 请求参数接收
前端·javascript·算法
天外天-亮11 小时前
Vue + excel下载 + 水印
前端·vue.js·excel
起个名字逛街玩11 小时前
前端正在走向“工程系统化”:从页面开发到复杂产品架构的深度进化
前端·架构
用户479492835691511 小时前
React 渲染两次:是 Bug 还是 Feature?聊聊严格模式的“良苦用心”
前端·react.js·前端框架
用户479492835691511 小时前
Code Review 惊魂:同事的“优雅”重构,差点让管理员全部掉线
javascript