前言
在移动浪潮袭来的时候,小钗有幸进入框架组做webapp框架开发,过程中遇到了移动端的各种坑,也产生了各种激情,就我们公司的发展历程来说
第一阶段:使用传统方式开发移动站点,少量引入HTML5元素
第二阶段:框架化,使用jquery mobile框架,发现慢,组件不好管理,不好维护给搞掉了
第三阶段:jquery+Backbone的组合,最后为了瘦身将jquery换成了zepto
第四阶段:框架适应Hybrid版本,Hybrid相关频道与H5站点一套代码,业务扩展遍地开花
第五阶段:框架适应ipad版本/实现前端ABTesting,由于不可控原因,计划夭折
框架使用过程中需要快速适应业务需求,框架中过多的掺杂了业务代码,并且随之发展,框架本身的耦合度、设计不合理的地方也体现了出来
小钗虽然知道哪里有问题,但框架的代码不是想象那么好改,一个API的改变会导致整个业务线的崩溃,听之任之又很不爽
开发一套干净webapp框架的想法油然而生,于是该框架便出现了
诚然,此框架比不上Backbone、比不了anglarJS,毕竟小钗的资历、水平有限,所以框架本身可能都会有一些缺陷,但作为初步接触前端的同学
或者想在前端看到一些设计思想的同学,或者想在移动webapp试水的公司。此框架还是有一些他一些优势,我们带着看看不吃亏的想法,还是可以看看嘛!!!
也希望各位多多支持,代码写的不好也在提高,因为小钗不是专业的重构,其中的CSS及DOM结构全部由在线网站down下来了,这里是抄袭我说清楚了哦。
① 这次首先出一篇大体介绍性文章,简单结束
② 而后有1,2篇博客对其中的全局MVC相关做说明,这里使用了hashChange与pushState两种方案
③ 然后对其中的UI继承关系做梳理,并拿其中比较复杂的几个UI组件说一下设计思路,预计会有3-5篇
④ 最后根据反馈对框架做一些调整,效果好的话,我也试试捐赠,我们家的左小萌居然也在搞捐赠,我觉得我也该搞个!!!
框架主要用途还是自我学习交流,以及框架思维整理,BUG在所难免,若是有BUG请给我留言!
框架简介
快速浏览
想简单看看demo的朋友请到: http://yexiaochai.github.io/blade/
对源码有兴趣的朋友请入:https://github.com/yexiaochai/blade
若是PC端浏览请使用chrome浏览器,不用谢我叫雷锋,若是只有IE的同学请装chrome暂时抛弃IE
支持情况
由于做是个人框架,并且是学习,框架便抛弃了一些系统现在支持情况是
IOS6+
android4+
那些手机不过千的浏览器上渲染可能会有问题,至于wp或者低版本兼容,小钗便需要看后续精力与工作强度
框架发展
第一期-MVC
该框架第一期的目标是简单的webapp MVC的实现,现在也基本实现了,app支持hashChange与pushState两种方式做路由加载view
对此有兴趣的同学可以看看helloWord,关于app与页面级View的关系如下:
第二期-通用工具
框架第二期的想法是,完善本身一些通用的东西,比如UI组件或者简单的flip手势工具等
这里小钗不是专业的前端,就直接从线上将公司的CSS Down下来了,也用了他的Dom结构 但是
整个组件的扩展非常方便,有兴趣的同学看看UI一块的代码,UI的继承关系如下:
第三期-ABTesting
框架第三期目标是实现前端ABTesting方案
第四期-ipad适配
框架第四期的目标是一套业务代码,可以同时用于mobile与ipad
第五期-Hybrid
框架第五期目标是实现Hybrid交互适配,由于小钗本身不懂native开发所以此方案要靠后
随机期-疑难杂症
框架还会单开一个频道做一些疑难杂症处理,比如: ① fixed问题 ② 区域滚动问题 ③ app唤醒 ④ History路径问题等
目录简介
有些项目文件或者不相关的文件我们不予关注,整个框架代码在blade文件目录中主要
demo在demo中......
dest为框架打包后的文件
doc用于存放一些文档,暂时还未补齐,我的想法是博客作为文档,因为后期会源码解析,并分析设计思维
grunt存放着打包文件
helloworld为最简单的入门代码,教我们如何进入webapp开发
res存放资源样式文件,这里是直接用的别人的
test用于存放测试用例,当然现在很多用例未加上......
Helloworld
要进入webapp开发非常简单,只需要将代码下载下来即可,其中helloworld异常简单
blade框架每个页面为一个页面片,一个页面片对应一个js文件,并且可能会对应一个html模板文件
一个频道的运行只需要这些文件即可(其实有点多的),最外层的index.html是基本入口
1 doctype html> 2 3 <html class="ie"> 4 5 <html> 6 <head> 7 <meta charset="utf-8" /> 8 <title>hello worldtitle> 9 <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> 10 <meta content="telephone=no" name="format-detection" /> 11 <meta name="apple-mobile-web-app-capable" content="yes" /> 12 <link href="../res/style/style.css" rel="stylesheet" type="text/css" /> 13 14 head> 15 <body onselectstart="return false"> 16 17 <script src="../blade/libs/require.js" type="text/javascript">script> 18 19 <script src="../blade/libs/zepto.js" type="text/javascript">script> 20 <script src="../blade/libs/underscore.js" type="text/javascript">script> 21 <script src="../blade/libs/underscore.extend.js" type="text/javascript">script> 22 <script src="../blade/libs/fastclick.js" type="text/javascript">script> 23 24 <script src="../blade/common.js" type="text/javascript">script> 25 <script type="text/javascript" src="./main.js">script> 26 body> 27 html>
他这里引入了源码文件,我这里未做任何处理,实际使用请参考demo的dest.html
可以看到,js入口为对应的main.js:
1 (function () { 2 3 window.getViewTemplatePath = function (path) { 4 return 'text!helloworld/templates/' + path + '.html'; 5 } 6 7 require.config({ 8 baseUrl: '../', 9 paths: { 10 } 11 }); 12 13 require(['AbstractApp'], function (App) { 14 //实例化App 15 var app = new App({ 16 'defaultView': 'index', 17 'viewRootPath': 'helloworld/views/' 18 }); 19 20 }); 21 })();
那里引入了我们全局抽象App,并进行了实例化,这个时候整个代码便开始运行了,这里默认加载的是index View
1 define(['AbstractView', getViewTemplatePath('index')], function (View, viewhtml) { 2 return _.inherit(View, { 3 onCreate: function () { 4 this.$el.html(viewhtml); 5 console.log('大家好,我是omCreate事件,我会执行并且只会执行一次'); 6 }, 7 8 events: { 9 'click h2': function (e) { 10 this.forward('list'); 11 }, 12 'click .icon_home': function () { 13 window.location = '../index.html'; 14 } 15 }, 16 17 onPreShow: function () { 18 console.log('大家好,我是onPreShow事件,我每次都会执行,执行最后执行turning方法便可显示view'); 19 this.turning(); 20 }, 21 22 onShow: function () { 23 console.log('大家好,我是onShow事件,我在onPreShow执行turning后会执行'); 24 25 }, 26 27 onHide: function () { 28 console.log('大家好,我是onHide事件,每次切换我将隐藏时候我会触发'); 29 } 30 31 }); 32 });
我们每个View皆是继承至MVC中的View,对于业务层的View只需要关注上面四个事件点即可,描述文字写的比较清楚了,我这里便不多说了
这个时候直接运行index.html便会默认加载index.js的逻辑代码,webapp卡片设计由此开始
demo
是否引入第三方组件?
demo中会列举出框架中的一些UI组件与一些通用功能的API,并做简单示例,一个框架是否好用的一个标识便是其API是否丰富好用
blade这里还需要很多努力,这里还望各位多多提点
稍微成熟点的公司一般不会使用什么第三方组件,什么创业团队就最喜欢使用第三方的插件,或者通用功能,我原来就见过一个公司:
① 需要一个弹出层组件便引用国外的,1000-2000行代码,功能非常全面
② 需要一个flip手势工具也引用一个第三方的,代码500-2000行,功能非常全面
但是一来程序员抓不住其中的关键,一旦遇到不能适应项目的功能便完蛋了,这个时候对程序员的水准要求便高了可能需要源码修改
往往完全读懂后却发现自己只是需要其中的一点点功能而已,这个时候很有可能会重写,或者寻找另外的库,所以只有2B公司才会完全依赖什么第三方库
一般放出来的库文件,都是十分全面,会满足很多场景,但是你只会用到一个,或者你基本适用,但是总有一个两个地方不适用,那么怎么办呢?再从新引入一个第三方库么???
这样的话产品前端代码便会越发臃肿难以维护,并且BUG无数,又烂又慢。
demo简介
算了,这里扯远了,回到demo中来,demo中的代码逻辑便稍微复杂一点了,首先他存在着两个入口文件,分别是:
① debug.html 用于调试,其中的所有文件皆是源码形式
② dest.html 这个文件稍有不同,其使用的框架文件和控制器js文件全部被grunt打包好了,我们这里不存在Ajax数据请求,所以整个浏览不会发生请求延时,按道理来说比较快
dest目录便是装的压缩后的文件,我们其实将所有相关文件全部打包至了其中的dest/main.js,包括模板文件,这个多亏grunt与requireJS之功
整个demo的设计我们放到下一章节再说,这里简单描述下,其中比较关键的是ex_mvc中的文件,顾名思义,他代表对框架mvc的扩展
扩展的基石是继承,业内前端面试一道必考提便是继承,但是前端真正使用该技术的人感觉不多,很多前端都喜欢堆代码,还停留在面向过程。
blade这里便对js的继承做了一个很好的示例,希望对js的继承、面向对象知识点有所了解的朋友,不妨看看两个MVC中的代码有何不同
1 define(['AbstractView', 'cHighlight'], function (AbstractView, cHighlight) { 2 3 var hljs = new cHighlight(); 4 5 hljs.registerLanguage("javascript", function(a) { 6 return {aliases: ["js"],k: {keyword: "in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal: "true false null undefined NaN Infinity",built_in: "eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require"},c: [{cN: "pi",b: /^\s*('|")use strict('|")/,r: 10}, a.ASM, a.QSM, a.CLCM, a.CBLCLM, a.CNM, {b: "(" + a.RSR + "|\\b(case|return|throw)\\b)\\s*",k: "return throw case",c: [a.CLCM, a.CBLCLM, a.REGEXP_MODE, {b: /,e: />;/,r: 0,sL: "xml"}],r: 0}, {cN: "function",bK: "function",e: /\{/,c: [a.inherit(a.TM, {b: /[A-Za-z$_][0-9A-Za-z$_]*/}), {cN: "params",b: /\(/,e: /\)/,c: [a.CLCM, a.CBLCLM],i: /["'\(]/}],i: /\[|%/}, {b: /\$[(.]/}, {b: "\\." + a.IR,r: 0}]} 7 }); 8 hljs.registerLanguage("xml", function(a) { 9 var c = "[A-Za-z0-9\\._:-]+"; 10 var d = {b: /<\?(php)?(?!\w)/,e: /\?>/,sL: "php",subLanguageMode: "continuous"}; 11 var b = {eW: true,i: /,r: 0,c: [d, {cN: "attribute",b: c,r: 0}, {b: "=",r: 0,c: [{cN: "value",v: [{b: /"/,e: /"/}, {b: /'/,e: /'/}, {b: /[^\s\/>]+/}]}]}]}; 12 return {aliases: ["html"],cI: true,c: [{cN: "doctype",b: "",r: 10,c: [{b: "\\[",e: "\\]"}]}, {cN: "comment",b: "",r: 10}, {cN: "cdata",b: "<\\!\\[CDATA\\[",e: "\\]\\]>",r: 10}, {cN: "tag",b: "",rE: true,sL: "css"}}, {cN: "tag",b: "