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);
              }
          }
      });
    

特点

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

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

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

目录
相关文章
|
13天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
5天前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
15天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
8天前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
24 1
|
1月前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
21 3
|
1月前
|
前端开发 JavaScript 开发者
JS 异步解决方案的发展历程以及优缺点
本文介绍了JS异步解决方案的发展历程,从回调函数到Promise,再到Async/Await,每种方案的优缺点及应用场景,帮助开发者更好地理解和选择合适的异步处理方式。
|
1月前
|
缓存 JSON JavaScript
Node.js模块系统
10月更文挑战第4天
34 2
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
33 0
|
1月前
|
JavaScript 应用服务中间件 Apache
Node.js Web 模块
10月更文挑战第7天
29 0
|
1月前
|
JavaScript 网络协议
Node.js 工具模块
10月更文挑战第7天
19 0

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    42
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    46
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    53
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    37
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    61
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    56
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    40
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    49
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    105
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    75