2.模块化开发及按需加载原理

随着web项目越来越复杂,排除掉外部引入的代码,开发的代码动辄上千过万行,甚至更多。按照最初的开发经验,我们把代码全部放在一个文件上,除了代码多以外,还有命名容易被覆盖,冲突等问题。 从管理上我们会选择把它分开若干个js,分成各种部分,每个js管理某些部分或功能。不同文件中的代码可能由不同的多个人开发,如果把变量都放在全局上,可能会造成变量被篡改,函数名冲突等问题。

通常我们把每个分出来的部分,叫做模块,模块的编写每个团队都有自己的规范,为了更好的管理,进而引申出模块化开发,即把整个项目分成若干个模块,分发给不同的人,每个模块处理某些功能,最后通过协定把它们凑合在一起,最终形成了一个项目。

需求

然而web的模块化并不那么容易,如果单纯的使用js来处理逻辑,我们可以利用灵活的js特性,通过闭包或者面对对象方式解耦来处理,如果涉及到了html和css,我们需要考虑更多问题。

  1. html和css作为字符串的方式引入到js中,可能会导致调试的不便;

  2. 把html单独抽出来作为一个片段,需要部署到服务器中,文件直接打开无法获取;

  3. 将html放入页面中,注册事件以及引用其它对象,一不小心就会造成内存泄漏,内存的合理使用在webapp中是非常重要的;

  4. css和html文件一样,是通过片段方式的引入,还是通过全局引入,如果以片段方式的引入,在切换页面的时候,移除样式,如果样式中包含全局样式,可能会照成页面抖动或布局重绘。

整体思想

通过以上讨论,以下列的方式进行统一规定

  1. 纯js的模块,如果有必要的会必须要有dispose方法,用来作为删除不需要的内存引用。然后在index.html引入该文件,或用的时候异步引入。

    简单案例代码1

     var m1 = {
         list: [],
         doMethod: function () { 
            for (var i = 0; i < 1000; i++) this.list.push(i); 
         },
         // list除了可以通过dispose()来删除,还可以直接通过m1.list.length = 0来输出
         dispose: function () { 
            this.list.length = 0; 
         }
    };
    

    简单案例代码2

    var m2 = (function () {
        var list = [];
        var doMethod = function () {
            for (var i = 0; i < 1000; i++) list.push(i);
        }
        return {
            doMethod: doMethod,
            // list内的内容只能m2.dispose()来删除,不然永远滞留在内存在, 从而保护私有变量
            dispose: function () { list.length = 0; }
        }
    })();
    
  2. 如果带有html的片段,首先以单独html片段方式引入,由于ajax获取html片段是需要短暂的时间,在特殊情况下可以酌情以字符串的形式嵌入,对于dom的使用和事件的引用,要在模块销毁之前进行分离引用。

    简单案例代码

    var m3 = (function () {
        var domList = {}, eventList = {}, parentDom = null;
        // 加载html片段到dom中
        var addDom = function (dom, bk) {
            ajax.fetch("temp.html", function (text) {
                parentDom = dom;
                dom.innerHTML = text;
                bk();
            })
        } 
        // 绑定事件,缓存dom
        var attachDomEvent = function (key, type, eventFn) {
            domList[key] = domList[key] || parentDom.querySelector(key);
            if (eventList[key]) eventList[key].push(eventFn);
            else eventList[key] = [eventFn];
            domList[key].addEventListener(type, eventFn);
        }
        // 清除引用,待系统回收内存
        var dispose = function () {
            // 清除事件
            for (var key in domList) {
                if (eventList[key]) {
                    for (var i = 0; i < eventList[key].length; i++) 
                        domList.removeEventListener(domList[key], eventList[key][i])
                }
            }
            // 移除dom
            parentDom.innerHTML = "";
            // 移除引用
            domList = null;
            eventList = null;
        }
        // 暴露外部使用
        return {
            addDom: addDom, attachDomEvent: attachDomEvent, dispose: dispose
        }
    })();
    
  3. 如果是css的样式,那就以全局的方式在最开始的时候在index.html引入多个css文件,利用css的特性进行模块筛选。

    在index.html中引入 part1.css, part2.css。如果开发的人很多,可以引入更多, 通过后代选择器进行样式书写。

    part1.css简单案例

    .part1 container { margin: 0 ; }
    

    part2.css简单案例

    .part2 container { margin: 20px; }
    

框架中的使用

  1. 纯js的模块引入及定义

    简单案例说明,假设在/public/services/mk.js

    // 引入模块依赖,避免循环引用和自引用
    // strTool, otherTool就是其它定义的模块,在内部就可以使用
    App.require(["strTool", "otherTool"], function (strTool, otherTool) {
       // 其它定义和事务处理
       // 定义模块, 第一个参数为模块名,第二个为模块的对象
       App.define("mk1", {});
    })
    
  2. 带有html的模块使用,主要用于Page对象,Component对象

    简单代码案例

    // 通过引用外部模块,如果不引用直接传入一个回调函数
    App.require(["mapTool"], function (mapTool) {
       var app = App.getCurrent();
       // 可以存储私有对象和私有处理方案代码
       // 定义page1的Page对象
       app.definePage("page1", {
           render: function (next) {
               this.fetch("./page1.html", function (text) {
                   // 将html模板传入,可以用来渲染页面
                   next(text);
               })
           },
           // 缓存dom和绑定事件。
           getDomObj: function () {
               this.attachDom("button", "confirmBtn")
                   .attachEvent("confirmBtn", this.confirmHandler, false);
           },
           // 引入资源初始化,init方法在html放在页面,并且缓存dom和绑定事件后触发
           init: function () {
               // 初始化
               mapTool.init();
           },
           // 事件回调
           confirmHandler: function (ev) {
               // 原生事件ev, this.domList缓存dom
               console.log(this.domList.confirmBtn === ev.target)
           },
           dispose: function () {
               // 外部资源销毁
               mapTool.dispose();
           }
       });
    })
    

    页面中的事件会通过切换页面内部进行解绑,dom缓存会在恰当时机进行删除。

  3. css的引入,与传统的方案一致

对于1,2两点,可能会有点困惑,传统的方式也能很好的工作,为什么还要进行一次封装呢,主要由以下原因:

  • 随着业务发展,模块可以是上百个,按照传统方式也会由上百个全局变量。因此对于复杂的引用会产生管理困难;
  • 为了按需引用,将每个模块分成通过名称引用,此时需要某个模块的时候,可以判断是否存在,如果不存在,去加载js文件,并且注册。从而提高了资源的利用率

通过以上方案,只要对模块进行统一的注册,内部的一个对象进行管理,使得最终易于管理和使用。

简单案例

为了更好的理解将上一章节的内容代码拷贝下来,并作以下修改

  1. 查看index.js, home.js, static.js中的代码改为引入模块的方式, 如下:

    (function () {})();
    

    修改为

    App.require(function () {});
    
  2. 在services文件夹中添加两个文件,这里注意,要求services类的模块的文件名和定义的模块名称必须是一致。如果不一致需要手动设置模块配置项。

    添加/public/services/strTool.js:注意模块名和文件名要一致

    App.require(function () {
        App.define("strTool", { scriptName: "https://webapi.amap.com/maps/modules?v=1.4.15&key=82c79f1397d8cf32f8e2c7f97f814a3c&vrs=1599633849065" });
    });
    

    添加/public/services/mapTool.js:

    App.require(["strTool"], function (strTool) {
        var map = null;
        var obj = {
            initMap: function (dom, lng, lat) {
                map = new AMap.Map(dom, {
                    center: [lng, lat]
                });
            },
            dispose: function () {
                map.destroy();
                map = null;
            }
        };
        if (typeof AMap === "undefined") {
            var script = document.createElement("script");
            script.src = strTool.scriptName;
            script.onload = function () {
                App.define("mapTool", obj);
            }
            script.onerror = function () {
                App.define("mapTool", {
                    init: function () { alert("js引入失败"); }
                })
            }
            document.head.appendChild(script);
        } else {
            App.define("mapTool", obj);
        }
    })
    
  3. 修改second.js代码

     App.require(["mapTool"], function (mapTool) {
         var app = App.getCurrent();
    
         app.definePage("second", {
             render: function (next) {
                 this.fetch("./second.html", next);
             },
             getDomObj: function () {
                 this.attachDom("button", "btn")
                     .attachDom("div", "div")
                     .attachEvent("btn", "click", this.clickHandler, false);
             },
             init: function () {
                 app.staticPage.header = "次页";
                 app.staticPage.footer = "从首页跳转到次页";
                 // 初始化地图
                 mapTool.initMap(this.domList.div, 120, 27.7);
             },
             clickHandler: function (ev) {
                 history.back();
             },
             dispose: function () {
                 // 销毁地图
                 mapTool.dispose();
             }
         })
     });
    
  4. 查看效果(主要针对移动端,可以通过手机端查看或者用浏览器模拟移动端)将代码放在可以访问的服务器或本地服务上,启动服务,通过浏览器访问,如下地址

结语

主要探索了如何管理复杂项目的构建方法,然后介绍了模块化方法,引出了框架层如何实现按需引入模块从而提高页面加载速度。分模块开发可以让开发更专注,效率更高。

推广

底层框架开源地址:https://gitee.com/string-for-100w/string

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值