不知不觉来百度已有半年之久,这半年是996的半年,是孤军奋战的半年,是跌跌撞撞的半年,一个字:真的是累死人啦!
我所进入的团队相当于公司内部创业团队,人员基本全部是新招的,最初开发时连数据库都没设计,当时评审需求的时候居然有一个产品经理拿了一份他设计的数据库,当时我作为一个前端就惊呆了……
最初的前端只有我1人,这事实上与我想来学习学习的愿望是背道而驰的,但既然来都来了也只能独挑大梁,马上投入开发,当时涉及的项目有:
① H5 站点
② PC 站点
③ Mis 后台管理系统
④ 各种百度渠道接入
第一阶段的重点为H5站点与APP,我们便需要在20天内从无到有的完成第一版的产品,而最初的Native人力严重不足,很多页面依赖于H5这边,所以前端除了本身业务之外还得约定与Native的交互细节。
这个情况下根本无暇思考其它框架,熟悉的就是最好的!便将自己git上的开源框架直接拿来用了起来:[置顶]【blade利刃出鞘】一起进入移动端webapp开发吧
因为之前的经验积累,工程化、Hybrid 交互、各种兼容、体验问题已经处理了很多了,所以基础架构一层比较完备,又有完善的 UI 组件可以使用,这个是最初的设计构想:
构想总是美好的,而在巨大的业务压力面前任何技术愿景都是苍白的,最初我在哪里很傻很天真的用 CSS3 画图标,然后产品经理天天像一个苍蝇一样在我面前嗡嗡嗡,他们事实上是不关注页面性能是何物的,我也马上意识的到工期不足,于是便直接用图标了!
依赖于完善的框架,20天不到的时间,第一版的项目便结束了,业务代码有点不堪入目,页面级的代码也没有太遵循 MVC 规则,这导致了后续的迭代,全部在那里操作 dom。
其实初期这样做问题不大,如果项目比较小(比如什么一次性的活动页面)问题也不大,但是核心项目便最好不要这样玩了,因为新需求、新场景,会让你在原基础上不断的改代码,如果页面没有一个很好的规范,那么他将不再稳定,也不再容易维护,如何编写一个可稳定、扩展性高、可维护性高的项目,是我们今天讨论的重点。
认真阅读此文可能会在以下方面对你有所帮助:
1 2 3 4 5 |
① 网站初期需要统计什么数据?产品需要的业务数据,你该如何设计你的网站才能收集到这些数据,提供给他 ② 完整的请求究竟应该如何发出,H5应该如何在前端做缓存,服务器给出的数据应该在哪里做校验,前端错误日志应该关注js错误还是数据错误? ③ 你在写业务代码时犯了什么错误,如何编写高效可维护的业务代码(页面级别),MVC 到底是个什么东西? ④ 网站规模大了如何复用一些模块? ⑤ 站在业务角度应该如何做性能优化(这个可能不是本文的重点) |
文中是我半年以来的一些业务开发经验,希望对各位有用,也希望各位多多支持讨论,指出文中不足以及提出您的一些建议。
统计需求
通用统计需求
对于服务器端来说,后期最重要的莫过于监控日志,对于前端来说,统计无疑是初期最重要的,通用的统计需求包括:
① PV/UV 统计
② 机型/浏览器/系统统计
③ 各页面载入速度统计
④ 某些按钮的点击统计
⑤ ……
这类统计直接通过百度统计之类的工具即可,算是最基础的统计需求。百度产品的文档、支持团队烂估计是公认的事情了,我便只能挖掘很少一部分用法。但是这类数据也是非常重要了,对于产品甚至是老板判断整个产品的发展有莫大的帮助与引导作用,如果产品死了,任何技术都是没有意义的,所以站点没有这类统计的速度加上吧!
http://tongji.baidu.com/web/welcome/login
渠道统计
所谓渠道统计便是这次订单来源是哪里,就我们产品的渠道有:
① 手机百度 APP 入口(由分为生活+入口、首页 banner 入口、广告入口……)
② 百度移动站点入口
③ 百度地图入口(包括 H5 站点)
④ wise 卡片入口(包括:唯一答案、白卡片、极速版、点到点卡片……)
⑤ 各种大礼包、活动入口
⑥ SEM 入口
⑦ ……
你永远不能预料到你究竟有多少入口,但是这种渠道的统计的重要性直接关乎了产品的存亡,产品需要知道自己的每次的活动,每次的引流是有意义的,比如一次活动便需要得到这次活动每天产生的订单量,如果你告诉产品,爷做不到,那么产品会真叫你爷爷。
当然,渠道的统计前端单方面是完成不了的,需要和服务器端配合,一般而言可以这样做,前端与服务器端约定,每次请求皆会带特定的参数,我一般会与服务器约定以下参数:
1 2 3 4 5 6 |
var param = { head: { us: '渠道', version: '1.0.0' } }; |
这个head参数是每次 ajax 请求都会带上的,而us参数一般由url而来,他要求每次由其它渠道落地到我们的站点一定要带有us参数,us参数拿到后便是我们自己的事情了,有几种操作方法:
① 直接种到 cookie,这个需要服务器端特殊处理
② 存入 localstorage,每次请求拿出来,组装请求参数
③ 因为我们 H5 站点的每一次跳转都会经过框架中转,所以我直接将us数据放到了 url 上,每次跳转都会带上,一直到跳出网站。
SEM 需求
SEM 其实属于渠道需求的一类,这里会独立出来是因为,他需要统计的数据更多,还会包含一个投放词之类的数据,SEM 投放人员需要确切的知道某个投放词每天的订单量,这个时候上面的参数可能就要变化了:
1 2 3 4 5 6 7 |
var param = { head: { us: '渠道', version: '1.0.0', extra: '扩展字段' } }; |
这个时候可能便需要一个 extra 的扩展字段记录投放词是什么,当然 SEM 落地到我们网站的特殊参数也需要一直传下去,这个需要做框架层的处理,这里顺便说下我的处理方案吧
统一跳转
首先我们 H5 站点基本不关注 SEO,对于 SEO 我们有特殊的处理方案,所以在我们的 H5 站点上基本不会出现a标签,我们站点的每次跳转皆是由 js 控制,我会在框架封装几个方法处理跳转:
1 2 3 4 5 6 7 8 9 10 |
forward: function (view) { //处理频道内跳转 } back: function (view) { } jump: function (project, view) { //处理跨频道跳转 } |
这样做的好处是:
① 统一封装跳转会让前端控制力增加,比如 forward 可以是 location 变化,也可以是 pushState/hash 的方式做单页跳转,甚至可以做 Hybrid 中多 Webview 的跳转
② 诚如上述,forward 时可以由 url 获取渠道参数带到下一个页面
③ 统一跳转也可以统一为站点做一些打点的操作,比如单页应用时候的统一加统计代码
最简单的理解就是:封装一个全局方法做跳转控制,所有的跳转由他发出。
请求模块
ajax是前端到服务器端的基石,但是前端和服务器端的交互:
1 2 3 4 |
每个接口必须要写文档! 每个接口必须要写文档! 每个接口必须要写文档! 重要的事情说三遍!!! |
如果不写文档的话,你就等着吧,因为端上是入口,一旦出问题,老板会直观认为是前端的问题,如果发现是服务器的字段不统一导致,而服务器端打死不承认,你就等着吧!
无论什么时候,前端请求模块的设计是非常关键的,因为前端只是数据的搬运工,负责展现数据而已:)
封装请求模块
与封装统一跳转一致,所有的请求必须收口,最烂的做法也是封装一个全局的方法处理全站请求,这样做的好处是:
① 处理公共参数
比如每次请求必须带上上面所述 head 业务参数,便必须在此做处理
② 处理统一错误码
服务器与前端一般会有一个格式约定,一般而言是这样的:
1 2 3 4 5 |
{ data: {}, errno: 0, msg: "success" } |
比如错误码为1的情况就代表需要登录,系统会引导用户进入登录页,比如非0的情况下,需要弹出一个提示框告诉用户出了什么问题,你不可能在每个地方都做这种错误码处理吧
③ 统一缓存处理
有些请求数据不会经常改变,比如城市列表,比如常用联系人,这个时候便需要将之存到 localstorage 中做缓存
④ 数据处理、日志处理
1 2 |
这里插一句监控的问题,因为前端代码压缩后,js错误监控变得不太靠谱,而前端的错误有很大可能是搬运数据过程中出了问题,所以在请求model层做对应的数据校验是十分有意义的 如果发现数据不对便发错误日志,好过被用户抓住投诉,而这里做数据校验也为模板中使用数据做了基础检查 |
服务器端给前端的数据可能是松散的,前端真实使用时候会对数据做处理,同一请求模块如果在不同地方使用,就需要多次处理,这个是不需要的,比如:
1 2 3 |
//这个判断应该放在数据模块中 if(data.a) ... if(data.a.b) ... |
这里我说下 blade 框架中请求模块的处理:
blade 的请求模块
我们现在站点主要还是源于blade框架,实际使用时候做了点改变,后续会回归到 blade 框架,项目目录结构为:
其中 store 依赖于 storage 模块,是处理 localstorage 缓存的,他与 model是独立的,以下为核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
define([], function () { var Model = _.inherit({ //默认属性 propertys: function () { this.protocol = 'http'; this.domain = ''; this.path = ''; this.url = null; this.param = {}; this.validates = []; // this.contentType = 'application/json'; this.ajaxOnly = true; this.contentType = 'application/x-www-form-urlencoded'; this.type = 'GET'; this.dataType = 'json'; }, setOption: function (options) { _.extend(this, options); }, assert: function () { if (this.url === null) { throw 'not override url property'; |