VC模式下javascript项目重构

拟墨画扇 提交于 2019-12-09 10:34:47

项目现状

项目为单页web应用,只针对chrome浏览器,无开发文档。由于是追求进度的项目,开发约定极少,除了jquery、LAB.js、bootstrap以及一些UI组件外,没有使用其他开源组件。
项目简单封装了ajax请求,form表单信息获取,缓存,获取和渲染模板函数以及一些UI组件,ajax请求没有按照restful api进行封装。
现在项目开发中所遇到的问题:

  1. 代码命名不规范,例如action,do_action

  2. 代码结构不清晰,没有按照功能或mvc拆分成独立的文件,只能依靠dom去查找具体的业务代码在哪里

  3. 模板写的有问题,复用模板使用id来做dom索引,或混入javascript业务逻辑,也有整个模板只包含一行代码

  4. 工具类封装与引用不规范,主要是没有注释,没有规范的例子可做参照,导致出现错误的使用工具类

  5. 业务逻辑的流转依靠dom操作,例如,在实现数据联动的时候,往往需要手动点击刷新按钮或者$(...).click()。

  6. 模板渲染封装太薄,导致不同模块的模板渲染方法各异,且会渲染模板中的javascript代码

  7. 无法对代码进行代码测试,只能依靠人工测试确保代码质量

  8. css文件过大,基本上所有的css都集中在一个css文件下

  9. 页面跳转策略是隐藏老页面,显示新页面。

重构项目方案

代码书写规范

css规范: http://www.iteye.com/news/27577
重点注意命名规范。

javascript命名规范: http://blog.sina.com.cn/s/blog_8db76d6d010143pi.html

javascript注释规范:http://www.jetbrains.com/webstorm/webhelp/creating-jsdoc-comments.html
具体规范要待确立注释要达到什么效果后(例如:是否要自动生成doc)再写

文件结构

前提:

  1. 尽量不动现有文件夹名称

  2. 尽量少的移动文件

  3. 尽量减少修改现有代码

如何重构:

//fileStructure//注意,一个模块可按照功能继续往下划分,相应的,与之对应的css和img也要在目录上与之对应{
    src:{//开发环境,主要做业务逻辑  新增
        moduleName:{//模块名称
            src:[sourceJSFiles],
            style:[cssFiles]
        }
    },
    components:{//开发环境:自行开发的插件,公共组件等, 新增//组件也可以作为单独项目独立出来,便于测试,维护
        componentName:{
            src:[sourceJSFiles],
            style:[cssFiles]//可能存在的style
        }
    }
    libs:{//存放项目依赖的插件  新增
        pluginName:[plugin_files],
        ...:...
    },

    javascripts:{//生产环境的js源码
        moduleName:[combineJSFiles],
        ...:...
        components:{
            component:[CombineJSFIles]
            ...:...
        }
    },

    stylesheets:{//生产环境的css,css文件可合并在一起,但注意合并的顺序配置。
        moduleName:[combineCSSFile],
        ...:...
        components:{
            component:[CombineJSFIles]
            ...:...
        }
    },

    views:{//存放模板,开发生产统一为一个文件
        moduleName:[tempFiles],
        ...:...
    },

    images:{//存放图片
        moduleName:[imgFiles],
        components:[imgFiles]//粒度不再往下划分
    },

    file:[files],//存放被下载的资源

    test:{//测试
         moduleName:[filesName]//fileName要以Spec结尾。例如 abcSpec.js   方便写脚本
    },
    node_modules:{//存放开发环境 grunt依赖的插件
        ...:...
    },
    app.js//总的js文件,负责加载、配置以及开发和生产环境的切换。
    app.css//总的css文件,可放在stylesheets文件夹下面。
    main.html//index页面
    grunt.js//引入grunt来做集成化测试以及代码的合并压缩 新增
    package.json

}

思考:
我们真需通过文件结构去划分去记忆去调试代码吗?对一个前端项目来讲。虽然刚开始认为:这个是必须去做的事情。但后来,随着思考的加深,文件结构这方面,反而是可有可无的。我们直接可以通过chrome debug 来快速锁定具体的文件甚至是具体的代码在哪里,我们还真的迫切需要文件结构吗?

代码模块化

前提:

  1. 所有用到的javascript文件会在用户登录前加载好。

  2. 依据现有代码,每个业务逻辑模块都会将其引用暴露在handler全局变量下面。

  3. 公用组件可重写

如何重构:
业务逻辑代码:
关于现有javascript逻辑代码的重构,重点放在将现有js文件进行分拆,按照功能和模板细化到具体的文件夹下面去。
然后写好grunt合并压缩脚本,可将一个js模块按照特定顺序合并到一个js文件里,然后,再将合并的文件压缩后,放置在文件夹javascripts相应的模块下面去。
css:
在修改业务逻辑代码的时候,尽量将相应的css归到相应的文件夹下。
公共组件:
公共UI组件尽量不使用width,heightcss样式,多用margin,padding,max-width等,无特殊情况下,不要使用id对组件进行索引,多使用class。写公共组件的大体格式如下:

//组件可不提供命名冲突处理,尽量人为避免冲突,命名格式为name+"Mod"
//例如:showDialogMod
//参数如果超过三个的话,使用{}来替代
//需要写注释,最好在注释中,给个使用例子
var componentName=(function(args){
    'use strict';
    //...  
    //...
    return {//如需对组件进行操作(例如对组件中的值进行重新赋值),至少要返回dom引用,更好的方案是返回相应的操作方法
        //...:...
    }
})

注意:
写组件的时候,不要使用jquery,用原生浏览器提供的方法,毕竟我们无法预言会不会出现更好的前端框架。

双向绑定(更改为MVC)

是否需要实现双向绑定

不实现
不实现也就是使用已有代码的流转方式:用户通过view触发事件>>导向至相应的controller->组装数据,发送ajax请求->请求返回->(获取模版->根据模板渲染数据)->改动与之有牵连view。括号内表示可能不需要的步骤。
例如:在记录内容展示页面删除这条记录,就需要更新当前模板,以及更新记录列表内的数据。
实现
业务逻辑代码的流转方式会变成:用户通过view触发事件>>导向至相应的controller->组装数据,发送ajax请求->请求返回->修改缓存model。
需要实现一批filter来对显示数据和model数据进行转换。

实现方式

采用现有框架

avalon
源码4000,易掌控,不会破坏现有数据流转和模板,但页面的渲染方式会发生改变。可通过$watch的运用来减少代码重构的难度。修改幅度相较其他mvc框架小。
angular
需要根据angular对项目结构修改进行,还需要学习scope、directive 等概念,且渲染方式会更改,要自己实现一套router,简单的使用ng-include无法满足页面缓存的现状。
ember
不推荐。需要改动的地方太多,最致命的是,它是对dom侵入的,不易于小幅度修改现有项目。
backbone
属于轻量级的,需要引入underscore.js。render函数会有较多的改动。编程的重心会转移到view展示的编写以及和服务器请求的匹配(backbone按照restful api 设计),虽然代码量不会减少太多,但代码结构会清晰很多,underscore.js提供script模板渲染。

自己写
不具备短期内开发一个较为完善的mvc框架的技术储备。

注意:
以上的实现,对传输的数据结构的规整性和方式都有一定的要求,对于现有的关于历史方面等不需要进行监听的数据,要做相应的处理。
以上框架,除了avalon,都需要为服务器请求过来的数据做适配器(angular$resource要更改)。

不用框架的重构(VC重构)

模板重构

前提:

  1. 模板使用的流程:获取模板(如果已缓存,则从缓存中获取模板),渲染模板(String.replace的加强版),将渲染后的模板添加到dom中。

重构前提:

  1. 可大规模的合并模板

  2. 模板内可以不允许拥有js代码片段(简单一点的,例如onclick="abc()"可以有)。即使允许有js代码片段,也不允许js代码片段可被渲染。

  3. chrome debug熟练使用

如何重构:
去除模板id
前端id的作用是用来标识dom全局唯一变量,是用来做dom结构和逻辑块划分的,不能拿来做数据索引转换,而且由于页面的转换方式,无法确定,某一个公共模板是否有多个实例存在于当前dom下,为了解决这个问题。出现了构建模板间的沟通机制以及修改模板命名

构建模板间的沟通机制
TODO:
没有双向绑定所导致的额外手段
主要功能:防止公共模板(尤其存在模板以id作为索引)多个实例存在于dom时,操作紊乱。现在的想法是,做个当前系统运行时模板状态集合以及模板操作的工具集。
模板集合的数据结构:

{
    tempName:[
            {        
                status:$num,//代表当前的模板是在显示,还是被隐藏,被销毁的会被直接干
                node:$document,//document引用
                path:$stirng,//格式 nodeName[id|className]  整个路径都小写,父div索引
            }
        ]
    }
}

模板操作的工具集的功能:

  1. 可按照模板名称和加载当前模板(模板加载方式是$(div).html(template))的父div element或索引来查到具体模板实例。

  2. 封装个具体查询模板下dom的方法,方便操作,要兼容jquery。

  3. 封装模板加载函数,用来替换当前的模板加载方式,要兼容jquery。

修改模板命名
由于没有比较好的文件结构,模板命名就变得尤为重要,为此,我们可以适当的在模板内添加一些注释(注释的主要作用:通过debug页面元素,可以快速查找到模板文件)。

重写模板渲染流程
TODO:
现有的模板渲染函数过于简单,也为了减少模板不允许写js所带来的麻烦,需要重写此函数以及添加模板filter机制。
filter机制:就是在读取模板的时候,如果遇到自定义的标签,就将其进行替换,并在模板加载到dom后,立即加载相应的js特效(这个地方遇到的问题,是如何辨识以及保存相应的dom信息,html comments?)。
重写后的渲染函数要达到:

/**
* 支持for循环和if判定 。这个属于额外的,可在实现基础功能后,再来做这个。
* option 选项:{
*    method:'after|html|before',//模板加载方式,默认html 兼容jquery语法
*    dataFill:$fn|$string,//当遇到要渲染的数据为undefined或null是,要做的处理,默认渲染成""。
*    interpolate: /\{\{(.+?)\}\}/g //自行改写匹配内容   默认匹配{{...}}
*   
*}
* 
*
* @data #object 数据
* @elem #document  
* @callback #func 可选 渲染成功后的回调函数
* @option #ojbect 可选 配置
* 
* return obj 用到的filter引用,数据结构待思考
*/
function render(data,elem,callback,option){
    //...实现
    //...修改模板状态,要考虑模板嵌套的问题
    
}

实现可参考 underscore.js的template函数。或者avalon.js的bindingHandlers. each处理。

合并模板
模板的不能太过单薄,一个页面展示出来后仅需要用到只有6到10个模板就可以了。像<div id='abc'></div>这种一行代码一个模板文件的,注意删除。

转移模板中的javascript
为什么要转移模板中的javascript代码呢?很简单,在模板中写的javascript代码无法debug

view操作重构

如何重构:
TODO:
类似avalon的双工绑定(观察者模式)。但由于和服务器交互的数据不会以model的形式进行缓存,就只能在dom之间封装观察者模式。
使用场景:两个input的值同步。
做这个之前,要把模板操作的工具集写好,在实现双向的时候,要好好思考这么一个场景,是不是有多个地方,对同一个数据源做了修改?

/**
* 实现个简单的,先不考虑集合的问题
* source/target为object时,数据结构为:
* {
*   ele:node,
*  
*   attr:string,//attributeName,不填的话,根据ele是否是表单元素 默认为 value或 text
* }
* @source object|node 
* @target object|node
*
* return object 返回一个object,包含,unwatch和fire方法,用来解除观察和触发观察
*
*/
function watch(source,target){
    
}
/**
* 注意,来回触发,造成的死循环问题。
*
*/
function duplexWatch(t1,t2){
    return {
        "":""
    }
}


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!