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

特点

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

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

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

目录
相关文章
|
7天前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
21天前
|
前端开发 JavaScript UED
探索JavaScript中的异步编程模式
【10月更文挑战第21天】在数字时代的浪潮中,JavaScript作为一门动态的、解释型的编程语言,以其卓越的灵活性和强大的功能在Web开发领域扮演着举足轻重的角色。本篇文章旨在深入探讨JavaScript中的异步编程模式,揭示其背后的原理和实践方法。通过分析回调函数、Promise对象以及async/await语法糖等关键技术点,我们将一同揭开JavaScript异步编程的神秘面纱,领略其带来的非阻塞I/O操作的魅力。让我们跟随代码的步伐,开启一场关于时间、性能与用户体验的奇妙之旅。
|
9天前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第33天】在JavaScript的世界里,异步编程是提升应用性能和用户体验的关键。本文将带你深入理解异步编程的核心概念,并展示如何在实际开发中运用这些知识来构建更流畅、响应更快的Web应用程序。从回调函数到Promises,再到async/await,我们将一步步解锁JavaScript异步编程的秘密,让你轻松应对各种复杂的异步场景。
|
1月前
|
前端开发 JavaScript 开发者
JS 异步解决方案的发展历程以及优缺点
本文介绍了JS异步解决方案的发展历程,从回调函数到Promise,再到Async/Await,每种方案的优缺点及应用场景,帮助开发者更好地理解和选择合适的异步处理方式。
|
1月前
|
JavaScript 前端开发 API
探索Node.js中的异步编程模式
【10月更文挑战第4天】在JavaScript的世界中,异步编程是提升应用性能和用户体验的关键。本文将深入探讨Node.js中异步编程的几种模式,包括回调函数、Promises、async/await,并分享如何有效利用这些模式来构建高性能的后端服务。
|
1月前
|
缓存 JSON JavaScript
Node.js模块系统
10月更文挑战第4天
36 2
|
1月前
|
JavaScript 前端开发 调度
探索Node.js中的异步编程模式
在Node.js的世界里,异步编程是核心。本文将带你深入了解异步编程的精髓,通过代码示例和实际案例分析,我们将一起掌握事件循环、回调函数、Promises以及async/await等关键概念。准备好迎接挑战,让你的Node.js应用飞起来!
|
1月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
31 3
|
1月前
|
JavaScript 前端开发 开发者
探索Node.js中的异步编程模式
【9月更文挑战第33天】在JavaScript的后端领域,Node.js凭借其非阻塞I/O和事件驱动的特性,成为高性能应用的首选平台。本文将深入浅出地探讨Node.js中异步编程的核心概念、Promise对象、Async/Await语法以及它们如何优化后端开发的效率和性能。
25 7
|
1月前
|
JavaScript 应用服务中间件 Apache
Node.js Web 模块
10月更文挑战第7天
29 0