Javascript模块化开发心得

来源:互联网 发布:淘宝找人代付退款给谁 编辑:程序博客网 时间:2024/05/18 20:11

遇到的问题

先看一段代码截图:

too-many-code
在这段代码的回调方法里面经常是上千行的业务逻辑代码。

在遇到复杂业务场景时,我们在调试阶段经常就已经被前后交织的逻辑搞得焦头烂额。特别是当代码交接出去给其他人维护的时候,接手代码的同事一定不会很高兴。

以上代码的分析

  • 一个文件中的代码量太多,逻辑不明确
  • 变量名称容易被污染
  • 页面功能只能一个人开发,不易分工

怎么办

以上代码中已经包含了解决方案:

requirejs来加载外部依赖的文件

实践方法

requirejs除了引用外部库或者第三方插件的功能外,还可以将页面功能拆解为相对独立的javascript文件,这些文件可以用来做以下几点事情:
1. 将页面拆分成相对独立的几个逻辑区域
2. 每个区域的逻辑放在对应的js中开发
3. 在requirejs引用的入口文件中引用每个区域的javascript文件

举例

一)页面内容多

往往一个功能的首页有比较大量的内容展示,其中包括一些数据交互和动效展示,例如:

众筹首页

这个页面可以拆解成以下几个部分:

  • 页头的感恩回馈(这一部分是根据业务需求配置是否展示的)
  • 感恩回馈下面的菜单栏
  • 首页有女生图片的滚屏
  • 热门项目楼层等等……

这些比较明显可以视觉上区分出的区域或者功能上比较独立的区域都可以单独新建javascript文件来书写逻辑

目录结构

入口main.js

require.config({    baseUrl: ctx + 'scripts',    paths: {        // 以下为引用的第三方和公共代码        jquery: 'jquery',        // 顶部广告位        top_banner: 'front/index/top-banner',        // 楼层一        floor_one: 'front/index/floor-one',        // 类目推荐楼层        floors: 'front/index/floors',        // 悬浮按钮和悬浮广告等小功能        widget: 'front/index/widget',        // 播放幻灯片的组件        slide: 'front/index/utils/slide'    },    shim: {        widget: ['jquery'],        floor_one: ['jquery'],        floors: ['jquery'],        top_banner: ['jquery'],        slide: ['jquery']    }});require(['jquery', 'widget', 'floor_one', 'floors', 'top_banner'], function($, widget, floor_one, floors, top_banner) {    floor_one.init();});

被拆分出来的用来实现指定楼层的javascript文件floor-one.js

define(['jquery', 'slide'], function($, Slide) {    // 初始化幻灯片组件    var slideLeft = new Slide();    slideLeft.init({        prevBtn: '.prev',        nextBtn: '.next'    });    // 初始化幻灯片按钮    $('.slide-main-wrap').hover(function() {        $(this).find('.prev, .next').show();    }, function() {        $(this).find('.prev, .next').hide();    });    // 初始化幻灯片背景图    var $wrap = $('.floor-one-bg-wrap'),        width = $wrap.width(),        $ul = $wrap.find('.floor-one-bg-ul')    $ul.width(width);    // 初始化开始日期控件    var min = "1970-01-01", max= new Date();    var val1 = $("#announcement-date1").val();    if (val1!="开始日期") {        min = val1;    }    ECode.calendar({        inputBox: "#announcement-date2",        isWeek: false,        range: {            mindate:min, //"1970-01-01",            maxdate:max// new Date()        },        callback: initCal1    });    // 初始化结束日期控件    var min = "1970-01-01", max = new Date();    var val2 = $("#announcement-date2").val();    if (val2!="结束日期") {        max = val2;    }    ECode.calendar({        inputBox: "#announcement-date1",        isWeek: false,        range: {            mindate: min,            maxdate: max        },        callback: initCal2    });    // 异步请求后台数据渲染页面    $.ajax({        url: 'query-data.json',        type: 'json',        success: function(data) {            // 渲染数据1            // 渲染数据2            // 渲染数据3            // 这里总计400行代码        }    });});

二)方法太长

人们有时会问我,一个函数多长才合适?在我看来,长度不是问题,关键在于函数名称和函数本体之间的语义距离(semantic distance)。如果提炼动作(extracting)可以强化代码的清晰度,那就去做,就算函数名称比提炼出来的代码还长也无所谓。
                            —Martin Fowler《重构》

1)方法中逻辑多

将代码进一步逻辑归类成不通的模块,在代码中封装

下面,对floor-one.js 中的代码进一步封装

define(['jquery', 'slide'], function($, Slide) {    // 初始化幻灯片    var initSlide = function() {        var slideLeft = new Slide();        slideLeft.init({            prevBtn: '.prev',            nextBtn: '.next'        });    };    // 初始化幻灯片按钮    var initSlideBtn = function() {        $('.slide-main-wrap').hover(function() {            $(this).find('.prev, .next').show();        }, function() {            $(this).find('.prev, .next').hide();        });    };    // 初始化幻灯片背景图    var initSlideBg = function() {        var $wrap = $('.floor-one-bg-wrap'),            width = $wrap.width(),            $ul = $wrap.find('.floor-one-bg-ul')        $ul.width(width);    };    // 初始化日期控件    var initCalendar = function() {        function initCal1() {            var min = "1970-01-01", max = new Date();            var val2 = $("#announcement-date2").val();            if (val2!="结束日期") {                max = val2;            }            ECode.calendar({                inputBox: "#announcement-date1",                isWeek: false,                range: {                    mindate: min,                    maxdate: max                },                callback: initCal2            });        }        function initCal2() {            var min = "1970-01-01", max= new Date();            var val1 = $("#announcement-date1").val();            if (val1!="开始日期") {                min = val1;            }            ECode.calendar({                inputBox: "#announcement-date2",                isWeek: false,                range: {                    mindate:min, //"1970-01-01",                    maxdate:max// new Date()                },                callback: initCal1            });        }        initCal1();        initCal2();     };    // 异步请求后台数据渲染页面    var initUserData = function() {        $.ajax({            url: 'query-data.json',            type: 'json',            success: function(data) {                // 渲染数据1                // 渲染数据2                // 渲染数据3                // 这里总计400行代码            }        });    };    return {        // 文件入口        init: function() {            initSlide();            initSlideBtn();            initSlideBg();            initCalendar();            initUserData();        }    };});

这样改造之后,代码的逻辑就一目了然,知道一段逻辑的开始和结束,为代码的调试和之后的维护提供了便利。

2)异步请求的回调方法中的数据处理代码较多

原因:往往一组数据对页面多个地方都有影响,每个地方对数据的使用可能需要对后台返回的数据进行定制化处理后在前台展示。总之,从数据变成用户能看到的效果代码量可能会很大。如以上代码中initUserData方法

这种情况,我们可以使用观察者模式,将页面的渲染逻辑分离出来:

var Observable = {    callbacks: [],    add: function(fn) {        this.callbacks.push(fn);    },    fire: function(data) {        this.callbacks.forEach(function(fn) {            fn(data);        })    }}//使用add开始订阅:Observable.add(function(data) {    show('执行动作一 ' + data)})Observable.add(function(data) {    show('执行动作二')})function ajax(arg) {    setTimeout(function() {        arg.successful(arg.data + ',返回获取到后台的数据')    }, 1000)}//一段ajax请求,成功后处理ajax({    data: '请求数据',    successful: function(data) {        Observable.fire(data); //触发动作    }})

总结

  • javascript入口文件中的页面模块逻辑尽量有条理,一眼看上去比较清晰
  • 在复杂页面逻辑的情况下,将视觉上关联度不高的区域用不同的javascript文件引用到入口文件中
  • 每个模块的javascript代码实现的逻辑不影响其他模块的使用和展现,做到添加和移除该功能不影响页面的正常展示

目标

  1. 复杂页面在开发阶段可分工独立开发,不会相互影响;
  2. 减少项目交接和维护成本;
  3. 重构页面部分功能时不影响页面其他功能;

前辈的经验

KISS – Keep It Simple Stupid
DRY – Don’t Repeat Yourself
最好设想你的代码会被一个挥着斧头的精神病来维护。

0 0
原创粉丝点击