JavaScript设计模式(三十五):大心脏-异步模块模式

简介: 大心脏-异步模块模式

异步模块模式——AMD(Asynchronous Module Definition)

模块化:将复杂的系统分解成高内聚、低耦合的模块,使系统开发变得可控、可维护、可拓展,提高模块的复用率

请求发出后,继续其他业务逻辑,直到模块加载完成执行后续的逻辑,实现模块开发中对模块加载完成后的引用

异步加载文件中的模块

image.png

  • index.js

      /**
       * 同步模块模式-SMD
       */
    
      // 向闭包中传入模块管理器对象F(~屏蔽压缩文件时,前面漏写;报错)
      ~(function (F) {
         
         
          /**
           * 定义模块管理器
           * @param {string} str 模块路由
           * @param {Function} fn 模块方法
           * @returns F模块管理器对象
           */
          F.define = function (str, fn) {
         
         
              // 过滤掉第一个元素为F的字符串数组
              let keys = str.replace(/^F\./, '').split('.');
              // 屏蔽对define与module模块方法的重写
              if (keys[0] === 'define' || keys[0] === 'module') return this;
              // 遍历字符串数组,赋值到当前对象内
              let obj = this;
              for (let i = 0; i < keys.length; i++) {
         
         
                  // 防止后续模块覆盖以前定义的模块
                  if (typeof obj[keys[i]] === 'undefined') {
         
         
                      obj[keys[i]] = {
         
         };
                      if (i === keys.length - 1) {
         
         
                          obj[keys[i]] = fn && fn();
                      }
                  }
                  obj = obj[keys[i]];
              }
              // 当前对象
              return this;
          };
    
          /**
           * 模块调用方法
           * @param  {...any} args 需要最后的参数为function
           */
          F.module = function (...args) {
         
         
              // 依赖模块列表
              let modules = [];
              // 判断最后一个参数是否为函数
              let fn = typeof args[args.length - 1] === 'function' ? args.pop() : null;
              // 将参数转换为数组
              let arr = (args.length === 1 && Array.isArray(args[0])) ? args[0] : args;
              // 遍历参数
              let i = 0;
              while (i < arr.length) {
         
         
                  // 判断参数是否是字符串(通过define创建的)
                  if (typeof arr[i] === 'string') {
         
         
                      let keys = arr[i].replace(/^F\./, '').split('.');
                      // 获取当前F对象
                      let mod = this;
                      for (let j = 0; j < keys.length; j++) {
         
         
                          // 替换当前的mod,查询到最里层对象
                          mod = mod[keys[j]];
                      }
                      modules.push(mod);
                  } else {
         
         
                      modules.push(arr[i])
                  }
                  i++;
              }
              fn && fn.apply(this, modules);
          };
      }((function () {
         
         
          return window.F = {
         
         };
      }())));
    
  • demo.js
      F.define('demo.say', function () {
         
         
          return function (name) {
         
         
              console.log(`Hello ${
           
           name}`);
          }
      });
    
  • index.html

      <script src="./index.js"></script>
      <script>
          /**
           * 加载脚本文件
           * @param {string} src 脚本路径
           */
          function loadScript(src) {
          
          
              let _script = document.createElement('script');                 // 创建脚本元素
              _script.type = 'text/javascript';                               // 设置类型
              _script.charset = 'UTF-8';                                      // 确认编码
              _script.async = true;                                           // 异步加载
              _script.src = src;                                              // 设置加载路径
              document.getElementsByTagName('head')[0].appendChild(_script);  // 将元素插入到页面中
          }
          // 加载demo.js脚本
          loadScript('./demo.js');
    
          // 调用demo.say模块方法 (Error ---> Uncaught TypeError: Cannot read properties of undefined (reading 'say'))
          F.module('demo.say', function (say) {
          
          
              say('Lee');
          });
    
          setTimeout(() => {
          
          
              // 调用demo.say模块方法 (Success ---> Hello Lee)
              F.module('demo.say', function (say) {
          
          
                  say('Lee');
              });
          }, 2000);
      </script>
    

报错原因:

由于浏览器中的文件是异步加载的,虽然现在开始加载demo.js文件,不过在文件没有加载完之前你可以继续做其他的事情,并且你写的方法,对于文件什么时候加载完成,你是无法获知的。

同步模块模式会立即引用该模块,此时文件加载尚未加载完成,因此你是引用不到该模块的。

当延迟了2s后,demo.js文件已经加载完毕,所以能得到demo.say模块,所以访问无问题。

异步模块(使用require.js

image.png

https://requirejs.org/docs/release/2.3.6/comments/require.js

<script src="https://requirejs.org/docs/release/2.3.6/comments/require.js"></script>
<script>
    /**
     * 异步模块(使用require.js)
     */
    require(['./lib/a', './lib/b'], function (a, b) {
    
    
        console.log(a, b);
        a('Lee'); // Hello Lee
        b.demo.getName(); // demo
    });
</script>
// a.js
define([], function () {
   
   
    return function (name) {
   
   
        console.log(`Hello ${
     
     name}`);
    }
});

// b.js 依赖于demo.js
define(['./demo'], function (demo) {
   
   
    return {
   
   
        name: 'b',
        demo
    };
});

// demo.js
define([], function () {
   
   
    return {
   
   
        name: 'demo',
        getName(){
   
   
            console.log(this.name);
        }
    }
});

仿require.js的实现

核心思想:每开始进行加载一个js脚本,计数器加1;每加载完成一个脚本,计数器减1;当全部脚本加载完成,计数器为0,此时执行回调函数,告诉控制台脚本已经加载完成

image.png

image.png

// index.js

/**
 * 异步模块 - 创建与调度模块
 */

// 向闭包中传入模块管理器对象R(~屏蔽压缩文件时,前面漏写;报错)
~(function (R) {
   
   

    // 缓存所有加载的模块
    const cache = {
   
   };

    // 最终的回调
    let cb = function () {
   
    };

    // 脚本加载数量(每添加一个js脚本累加1;加载完成累减1;全部完成变为0)
    let depCount = 0;

    /**
     * 获取脚本路径
     * @param {string} name 路径
     * @returns 路径
     */
    function getScriptSrc(name) {
   
   
        // 拼接完整的文件路径字符串,如'lib/ajax' => 'lib/ajax.js'
        return String(name).replace(/\.js$/g, '') + '.js';
    }

    /**
     * 加载脚本文件
     * @param {string} src 路径
     */
    function loadScript(src) {
   
   
        let _script = document.createElement('script');
        _script.type = 'text/javascript';                               // 文件类型
        _script.charset = 'UTF-8';                                      // 确认编码
        _script.async = true;                                           // 异步加载
        _script.src = src;                                              // 文件路径
        document.getElementsByTagName('head')[0].appendChild(_script);  // 插入页面中
    }

    /**
     * 定义模块(当引入的js加载完成,就会自动执行此方法)
     * @param {string} name 模块名称
     * @param {Array} deps 依赖项 ([{name: 模块名称, path: 模块路径}, ...])
     * @param {Function} callback 回调
     */
    R.define = function (name, deps, callback) {
   
   

        depCount--;

        // 添加依赖脚本
        deps.forEach(dep => {
   
   
            depCount++;
            loadScript(getScriptSrc(dep.path));
        });

        // 是否存在此模块,避免重复赋值
        if (!cache[name]) {
   
   
            cache[name] = {
   
   
                depNames: deps.length ? deps.map(dep => dep.name) : [], // 脚本的依赖脚本模块名称(作为缓存对象的key,用于最终整理)
                hasDep: !!deps.length,                                  // 是否存在脚本的依赖脚本
                export: deps.length ? callback : callback(),            // 最终导出的数据(存在依赖脚本则返回函数,用于后续判断调用;不存在依赖脚本,返回当前函数的执行)
            };
        }

        // 全部加载完成
        if (depCount === 0) {
   
   
            Object.values(cache).forEach(item => {
   
   
                if (item.hasDep) {
   
   
                    let params = item.depNames.map(key => cache[key]['export']);
                    item.export = item.export.apply(null, params);
                }
            });

            let obj = {
   
   };
            Object.keys(cache).forEach(key => {
   
   
                obj[key] = cache[key].export;
            });

            cb.call(null, obj);
        }

    };

    /**
     * 执行各个脚本js模块
     * @param {Array} deps 当前页面的依赖项,js脚本路径集合
     * @param {Function} callback 回调
     */
    R.module = function (deps, callback) {
   
   

        // 保存当前的回调,用于脚本完全加载完成后调用
        cb = callback;

        // 添加依赖脚本
        deps.forEach(dep => {
   
   
            depCount++;
            loadScript(getScriptSrc(dep));
        });

    };

}((function () {
   
   
    return window.R = {
   
   };
}())));
  • html.html

      <script src="./index.js"></script>
      <script>
          console.log(R);
          /**
           * 异步模块
           */
          R.module(['./lib/a', './lib/b'], function (mod) {
          
          
              /**
               * {
               *      a: ƒ (name),
               *      b: {
               *          name: 'b', 
               *          demo1: {name: 'demo1', getName: ƒ}, 
               *          demo2: {name: 'demo2', getName: ƒ},
               *      },
               *      demo1: {name: 'demo1', getName: ƒ},
               *      demo2: {name: 'demo2', getName: ƒ},
               * }
               */
              console.log(mod);
    
              mod.a('Lee'); // Hello Lee
    
              mod.b.demo1.getName(); // demo1
              mod.b.demo2.getName(); // demo2
    
              mod.demo1.getName(); // demo1
              mod.demo2.getName(); // demo2
          });
      </script>
    
  • lib/a.js
      // a.js
      R.define('a', [], function () {
         
         
          return function (name) {
         
         
              console.log(`Hello ${
           
           name}`);
          }
      });
    
  • lib/b.js
      // b.js 依赖于 demo1.js demo2.js
      R.define('b', [{
         
          name: 'demo1', path: './lib/demo1' }, {
         
          name: 'demo2', path: './lib/demo2' }], function (demo1, demo2) {
         
         
          console.log(demo1, demo2);
          return {
         
         
              name: 'b',
              demo1, 
              demo2
          };
      });
    
  • lib/demo1.js
      // demo1.js
      R.define('demo1', [], function () {
         
         
          return {
         
         
              name: 'demo1',
              getName(){
         
         
                  console.log(this.name);
              }
          }
      });
    
  • lib/demo2.js
      // demo2.js
      R.define('demo2', [], function () {
         
         
          return {
         
         
              name: 'demo2',
              getName(){
         
         
                  console.log(this.name);
              }
          }
      });
    

特点

模块化开发不仅解决了系统的复杂性问题,而且减少了多人开发中变量、方法名被覆盖的问题。通过其强大的命名空间管理,使模块的结构更合理。

通过对模块的引用,提高了模块代码复用率。异步模块模式在此基础上增加了模块依赖,使开发者不必担心某些方法尚未加载或未加载完全造成的无法使用问题。

异步加载部分功能也可将更多首屏不必要的功能剥离出去,减少首屏加载成本。

目录
相关文章
|
2月前
|
设计模式 前端开发 搜索推荐
前端必须掌握的设计模式——模板模式
模板模式(Template Pattern)是一种行为型设计模式,父类定义固定流程和步骤顺序,子类通过继承并重写特定方法实现具体步骤。适用于具有固定结构或流程的场景,如组装汽车、包装礼物等。举例来说,公司年会节目征集时,蜘蛛侠定义了歌曲的四个步骤:前奏、主歌、副歌、结尾。金刚狼和绿巨人根据此模板设计各自的表演内容。通过抽象类定义通用逻辑,子类实现个性化行为,从而减少重复代码。模板模式还支持钩子方法,允许跳过某些步骤,增加灵活性。
119 11
|
1天前
|
JavaScript 前端开发 Docker
如何通过pm2以cluster模式多进程部署next.js(包括docker下的部署)
通过这些步骤,可以确保您的Next.js应用在多核服务器上高效运行,并且在Docker环境中实现高效的容器化管理。
56 44
|
14天前
|
设计模式
「全网最细 + 实战源码案例」设计模式——模式扩展(配置工厂)
该设计通过配置文件和反射机制动态选择具体工厂,减少硬编码依赖,提升系统灵活性和扩展性。配置文件解耦、反射创建对象,新增产品族无需修改客户端代码。示例中,`CoffeeFactory`类加载配置文件并使用反射生成咖啡对象,客户端调用时只需指定名称即可获取对应产品实例。
75 40
|
15天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——简单工厂模式
简单工厂模式是一种创建型设计模式,通过工厂类根据传入参数创建不同类型的对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。适用于对象种类较少且调用者无需关心创建细节的场景。
46 19
|
13天前
|
设计模式 Java
「全网最细 + 实战源码案例」设计模式——生成器模式
生成器模式(Builder Pattern)是一种创建型设计模式,用于分步骤构建复杂对象。它允许用户通过控制对象构造的过程,定制对象的组成部分,而无需直接实例化细节。该模式特别适合构建具有多种配置的复杂对象。其结构包括抽象建造者、具体建造者、指挥者和产品角色。适用于需要创建复杂对象且对象由多个部分组成、构造过程需对外隐藏或分离表示与构造的场景。优点在于更好的控制、代码复用和解耦性;缺点是增加复杂性和不适合简单对象。实现时需定义建造者接口、具体建造者类、指挥者类及产品类。链式调用是常见应用方式之一。
47 12
|
15天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——工厂方法模式
简单工厂模式是一种创建型设计模式,通过一个工厂类根据传入参数创建不同类型的产品对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。适用于创建对象种类较少且调用者无需关心创建细节的场景。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。
34 15
|
2月前
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。
|
3月前
|
设计模式 安全 Java
Kotlin - 改良设计模式 - 构建者模式
Kotlin - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
53 2
|
9月前
|
设计模式 JavaScript 算法
js设计模式-策略模式与代理模式的应用
策略模式和代理模式是JavaScript常用设计模式。策略模式通过封装一系列算法,使它们可互换,让算法独立于客户端,提供灵活的选择。例如,定义不同计算策略并用Context类执行。代理模式则为对象提供代理以控制访问,常用于延迟加载或权限控制。如创建RealSubject和Proxy类,Proxy在调用RealSubject方法前可执行额外操作。这两种模式在复杂业务逻辑中发挥重要作用,根据需求选择合适模式解决问题。

热门文章

最新文章