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

特点

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

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

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

目录
相关文章
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
1月前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
12天前
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。
|
27天前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
29 2
|
1月前
|
设计模式 安全 Java
Kotlin - 改良设计模式 - 构建者模式
Kotlin - 改良设计模式 - 构建者模式
|
1月前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第40天】在JavaScript的世界里,异步编程是一道不可或缺的风景线。它允许我们在等待慢速操作(如网络请求)完成时继续执行其他任务,极大地提高了程序的性能和用户体验。本文将深入浅出地探讨Promise、async/await等异步编程技术,通过生动的比喻和实际代码示例,带你领略JavaScript异步编程的魅力所在。
25 1
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
42 1
|
2月前
|
前端开发 JavaScript UED
探索JavaScript中的异步编程模式
【10月更文挑战第21天】在数字时代的浪潮中,JavaScript作为一门动态的、解释型的编程语言,以其卓越的灵活性和强大的功能在Web开发领域扮演着举足轻重的角色。本篇文章旨在深入探讨JavaScript中的异步编程模式,揭示其背后的原理和实践方法。通过分析回调函数、Promise对象以及async/await语法糖等关键技术点,我们将一同揭开JavaScript异步编程的神秘面纱,领略其带来的非阻塞I/O操作的魅力。让我们跟随代码的步伐,开启一场关于时间、性能与用户体验的奇妙之旅。
|
2月前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
32 3
下一篇
DataWorks