项目现状
项目为单页web应用,只针对chrome浏览器,无开发文档。由于是追求进度的项目,开发约定极少,除了jquery、LAB.js、bootstrap以及一些UI组件外,没有使用其他开源组件。
项目简单封装了ajax请求
,form表单信息获取
,缓存
,获取和渲染模板函数
以及一些UI组件,ajax请求没有按照restful api进行封装。
现在项目开发中所遇到的问题:
代码命名不规范,例如
action
,do_action
代码结构不清晰,没有按照功能或mvc拆分成独立的文件,只能依靠dom去查找具体的业务代码在哪里
模板写的有问题,复用模板使用id来做dom索引,或混入javascript业务逻辑,也有整个模板只包含一行代码
工具类封装与引用不规范,主要是没有注释,没有规范的例子可做参照,导致出现错误的使用工具类
业务逻辑的流转依靠dom操作,例如,在实现数据联动的时候,往往需要手动点击刷新按钮或者$(...).click()。
模板渲染封装太薄,导致不同模块的模板渲染方法各异,且会渲染模板中的javascript代码
无法对代码进行代码测试,只能依靠人工测试确保代码质量
css文件过大,基本上所有的css都集中在一个css文件下
页面跳转策略是隐藏老页面,显示新页面。
重构项目方案
代码书写规范
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)再写
文件结构
前提:
尽量不动现有文件夹名称
尽量少的移动文件
尽量减少修改现有代码
如何重构:
//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 来快速锁定具体的文件甚至是具体的代码在哪里,我们还真的迫切需要文件结构吗?
代码模块化
前提:
所有用到的javascript文件会在用户登录前加载好。
依据现有代码,每个业务逻辑模块都会将其引用暴露在handler全局变量下面。
公用组件可重写
如何重构:业务逻辑代码:
关于现有javascript逻辑代码的重构,重点放在将现有js文件进行分拆,按照功能和模板细化到具体的文件夹下面去。
然后写好grunt合并压缩脚本,可将一个js模块按照特定顺序合并到一个js文件里,然后,再将合并的文件压缩后,放置在文件夹javascripts相应的模块下面去。css:
在修改业务逻辑代码的时候,尽量将相应的css归到相应的文件夹下。公共组件:
公共UI组件尽量不使用width
,height
css样式,多用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重构)
模板重构
前提:
模板使用的流程:获取模板(如果已缓存,则从缓存中获取模板),渲染模板(String.replace的加强版),将渲染后的模板添加到dom中。
重构前提:
可大规模的合并模板
模板内可以不允许拥有js代码片段(简单一点的,例如
onclick="abc()"
可以有)。即使允许有js代码片段,也不允许js代码片段可被渲染。chrome debug熟练使用
如何重构:去除模板id
前端id的作用是用来标识dom全局唯一变量,是用来做dom结构和逻辑块划分的,不能拿来做数据索引转换,而且由于页面的转换方式,无法确定,某一个公共模板是否有多个实例存在于当前dom下,为了解决这个问题。出现了构建模板间的沟通机制
以及修改模板命名
。
构建模板间的沟通机制
TODO:
没有双向绑定所导致的额外手段
主要功能:防止公共模板(尤其存在模板以id作为索引)多个实例存在于dom时,操作紊乱。现在的想法是,做个当前系统运行时模板状态集合以及模板操作的工具集。
模板集合的数据结构:
{
tempName:[
{
status:$num,//代表当前的模板是在显示,还是被隐藏,被销毁的会被直接干
node:$document,//document引用
path:$stirng,//格式 nodeName[id|className] 整个路径都小写,父div索引
}
]
}
}
模板操作的工具集的功能:
可按照模板名称和加载当前模板(模板加载方式是
$(div).html(template)
)的父div element或索引来查到具体模板实例。封装个具体查询模板下dom的方法,方便操作,要兼容jquery。
封装模板加载函数,用来替换当前的模板加载方式,要兼容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 {
"":""
}
}
来源:oschina
链接:https://my.oschina.net/u/1179600/blog/261803