模块的原理及使用

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

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

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

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

相关推荐
菜市口的跳脚长颌6 小时前
一个 Vite 打包配置,引发的问题—— global: 'globalThis'
前端·vue.js·vite
胖虎2656 小时前
实现无缝滚动无滚动条的 Element UI 表格(附完整代码)
前端·vue.js
小左OvO6 小时前
基于百度地图JSAPI Three的城市公交客流可视化(一)——线路客流
前端
星链引擎6 小时前
企业级智能聊天机器人 核心实现与场景落地
前端
GalaxyPokemon6 小时前
PlayerFeedback 插件开发日志
java·服务器·前端
爱加班的猫6 小时前
深入理解防抖与节流
前端·javascript
用户12039112947266 小时前
从零实现AI Logo生成器:前端开发者的DALL-E 3实战指南
javascript
信码由缰6 小时前
Java智能体框架的繁荣是一种代码异味
javascript·ai编程
自由日记7 小时前
学习中小牢骚1
前端·javascript·css