年终总结 之 校校前端实践

栏目: jQuery · 发布时间: 5年前

内容简介:本文主要分享,2014年7月至9月,我在这次项目拉了@Sherwood,商量下来,整个系统采用前后端解耦的架构,一开始自然而然就想到用SPA的解决方案有很多,包括Google的

本文主要分享,2014年7月至9月,我在 校校 任职期间,在前端技术上的实践

Infrastructure

这次项目拉了@Sherwood,商量下来,整个系统采用前后端解耦的架构,一开始自然而然就想到用 SPA 的解决方案,这样能够提升整个网站的响应速度,降低服务器的负载,做代码更新,CDN都很方便

SPA的解决方案有很多,包括Google的 Angular ,但我个人不喜欢他的模板体系,很难嵌套,至于 Ember ,太重、学习成本很高,当时还不是很了解 Spine ,风车有用它,不过最近用下来感觉非常不好

由于只招到一个前端工程师@picker,而且也是刚转到前端的,项目鸭梨很大,所以最后就决定自己造轮子

但是基础设施不可能自己造,所以这边基础设施的选择就尤为重要:

  1. 构建 工具 套件
2013年的时候就出现了流行前端的构建流程```Yeoman```+```Bower```+```Grunt```,在这次重建整个项目的时候,放弃了之前的所有代码,重新构建了项目
  1. 模块化标准
当时主流的模块化标准有```AMD```,主要是```RequireJS```实现,```CMD```标准,和```CommonJS```,主要是```SeaJS```实现

由于之前的项目使用过SeaJS,对于他的构建方法和整个体系比较了解,能够适用于大中型的前端项目,所以也没啥好说的就选了SeaJS
  1. 核心类库/UI框架
这个方面,其实也没啥好说的,考虑到可维护性,选了```jQuery```,考虑到低版本浏览器的兼容性,我们选用了```1.11.x```的```stable```版本

由于选用的版本没有特别针对```drag```,```drop```事件做兼容,我们自己做了hack
!function(){var deps=["jquery"];define("sui/core/common",deps,function(a){
	SUI.$=a("jquery");
	/**
	 * Hack jQuery to support dataTransfer
	 * contributed by: jquery.ndd
	 */
	var originalFix = SUI.$.event.fix;
	SUI.$.event.fix = function(event) {
    	event = originalFix.apply(this, [event]);
	    if( event.type.indexOf('drag') == 0 || event.type.indexOf('drop') == 0 ) {
	        event.dataTransfer = event.originalEvent.dataTransfer;
	    }
	    return event;
	}
})}();

UI框架方面也是使用了顺手的 Bootstrap ,和 jQuery 能够配合使用

Framework

实践的核心是框架:

模块化/分层

前端采用模块化+MVC的好处是,当前端的逻辑变得复杂时,可以重用大部分模块,针对每个页面写特有的逻辑就可以了,这样页面再多也能轻松维护

分层主要有几部分, application.js 是App的核心,也就是 main loop ,另外还有控制器,模型层,服务层,公共模块等

当页面加载完

<script src="sui/seed.js"></script>
<script>
    SUI.use('scripts/application');
</script>

application模块会加载所有页面的模板,控制器,依赖注入系统,路由组件,注册全局事件监听器,初始化用户模型等,根据当前的url确定加载的首个页面

路由组件

路由组件主要由一个 json 数组定义

"activity/vote/:voteId": {
	regExp: "activity\\/vote\\/([0-9]+)",
	controller: "activity/votedMembersController",
	template: "activity/votedMembers"
}

包括目标路由的url形式,对应控制器以及模板,当然部分路由可以匹配到默认规则就是用默认,如果url部分包含动态内容,如 :voteId ,需要额外定义一个正则匹配模式

路由组件会在匹配完成,返回一个路由对象,包含各种参数名,控制器等,等待被注入到控制器中,这边主要借鉴的是 AngularJS 的注入体系

define(function(require,exports,module){
	var routerObj = require("scripts/router");
	var route = function(url) {
		if(routerObj.hasOwnProperty(url)) {
			return routerObj[url];
		} else {
			for(route in routerObj) {
				if(routerObj[route].hasOwnProperty('regExp')) {
					var routeRegExp = new RegExp(routerObj[route].regExp);
					if(routeRegExp.test(url)) {
						/**
						 * Fix Bug: 修复多个参数无法获取到的问题
						 */
						var valueArr = url.match(routeRegExp),
							paramArr = route.match(/:([\w]*)/g);
						for(var i=1,len=valueArr.length;i<len;i++) {
							routerObj[route][paramArr[i-1].slice(1)] = valueArr[i];
						}
						return routerObj[route];
					}
				}
			}
			return null;
		}
	};
	module.exports = route;
});

依赖注入

系统的核心,可以将各处注册,初始化的组件,注入到Controller中,被使用,比如说登陆的用户,路由信息,其他Service,Model都能被注入到控制器层,以免重复Require,主要用了Javascript的反射

/**
 * Global Dependency Injector
 * @author megrez
 */
define(function(require,exports,module){
	/**
	 * [injector description]
	 * @param  {[type]} $scope [description]
	 * @return {[type]}        [description]
	 */
	var injector = function($scope) {
		this.all = {
			'$EventModel': require('scripts/models/EventModel'),
			'$UserModel': require('scripts/models/UserModel'),
			'$ApplyFormModel': require('scripts/models/ApplyFormModel')
		};
		this.deps = [];
		this.depStr = "";
	};
	/**
	 * [instantiate description]
	 * @param  {[type]} controller [description]
	 * @param  {[type]} $scope     [description]
	 * @return {[type]}            [description]
	 */
	injector.prototype.instantiate = function(controller,$scope) {
		var $inject = controller['$inject'] || [];
		this.all['$scope'] = $scope;
		this.deps = [];
		this.depStr = "";
		for(var i=0,len=$inject.length;i<len;i++) {
			this.deps.push(this.all[$inject[i]]);
			this.depStr += "args["+i+"],";
		}
		var IController = new Function('fn','args','return new fn('+ this.depStr.slice(0,-1) +');');
		return IController.apply(null,[controller,this.deps]);
	};
	/**
	 * [register description]
	 * @param  {[type]} moduleName [description]
	 * @param  {[type]} value      [description]
	 * @return {[type]}            [description]
	 */
	injector.prototype.register = function(moduleName,value) {
		this.all[moduleName] = value;
	};
	module.exports = injector;
});

只需要在Controller中,绑定一个 $injector 属性

// XXXController.js
var Controller = function($scope, $User) {
};
Controller['$inject'] = ['$scope', 'User'];
// application.js 用注入器初始化对象
// 路由的结果被保存到$inject这个对象中
var tmp = injector.instantiate(controller, {
	'router': routeRes
});

全局请求

在请求方面,做了切面处理,根据服务器端约定的协议,拦截数据;

在处理异步请求方面,我们用了 Q.js 来处理异步

当服务器返回HTTP级别的异常时,进行错误处理,抛出 WebService Error

只有当服务器返回数据时,全局请求器先判断是否有协议内定义的关键字如CAPTCHA,EXPIRED等状态,这时抛出异常或者直接进行处理,其他情况都直接返回结果

最后返回一个 Promise 对象,可以链式调用,业务级别的错误在各控制器内分别处理

当然更好的做法是对业务错误有统一的处理,并且能够根据情况 Override

/**
 * 全局异步请求处理器
 * @required Q.js jQuery
 * @param {Object} data [Ajax参数]
 * @param {Boolean} options [传true会跳到homepage]
 * @example
 * {
 *     url: '/api/session/create',
 *     type: 'post',
 *     dataType: 'json'
 * }
 * @return {Q Promise} [200返回代码,handle函数处理服务器返回的异常;其他错误抛出WebService错误]
 */
$.globalResponseHandler = function(data, options) {
	data.cache = false;
	var handle = function(data) {
		switch (data.status) {
			case 'OK':
				return data;
				break;
			case 'Error':
				throw data.message;
				break;
			case 'Expired':
				/**
				 * Fix Bug: 修复由于path导致的无法清除cookie的问题
				 */
				$.removeCookie("userSession", {
					path: '/'
				});
				window.location.href = homepage;
				break;
			case 'CAPTCHA':
				throw "对不起,您请求过于频繁,请输入验证码后再试";
				break;
			case 'Not Logged In':
				$.removeCookie("userSession", {
					path: '/'
				});
				// window.location.href = homepage;
				break;
			case 'Permission Denied':
				throw "Permission Denied";
				break;
			case 'Inconsistent Argument':
				// Bug Track
				throw "Inconsistent Argument";
				break;
			default:
				throw data;
				break;
		}
	};
	return Q($.ajax(data)).then(function(data) {
		return handle(data);
	}, function(error) {
		seajs.log(error);
		throw "Web Service Error";
	});

};

模板系统

看了市面上非常多的模板系统,大致分成两种,一种是 jade 风格的,有 slim ,另一种是原生的风格,诸如 handlebars , artTemplate ,包括像 erb 也算吧

在这方面我考略重点是易于维护,并且语法宽松,虽然 handlebars 也不错,但是他对于输出的控制非常厉害,不能将部分数据插入到html属性里

最终我选了 artTemplate ,因为他语法简单,支持 CommonJS 打包,速度一流,尤其是官方就出了 grunt-tmod 集成工具,当然我们对他的语法稍稍扩展了一些,在发布时进行AOT静态编译,渲染速度相当快,在代码集成模板也可以很好地控制渲染逻辑

不过3.0版本的简易语法不够用,没有for这样的循环语法,我们自己插入一段 loop 语法,当然 grunt-tmod 在npmjs上面的包有些问题,我们手动打上github最新版的补丁就好了

defaults.parser = function (code, options) {
    code = code.replace(/^\s/, '');
    var split = code.split(' ');
    var key = split.shift();
    var args = split.join(' ');
    switch (key) {
        case 'loop':
            code = 'for(' + args + '){';
            break;
        case '/loop':
            code = '}';
            break;
        case 'if':
            code = 'if(' + args + '){';
            break;

表单验证工具

由于表单在HTML前端重要的作用,我们自己做了一整套表单验证模块,代码略长,仅提供接口

/**
 * 单个表单验证方法
 * @param  {String} value         表单的值
 * @param  {String} rules          表单的验证规则
 * @param  {Function} fnCallback    正确的处理方法
 * @param  {Function} errorCallback 错误的处理方法
 */
var formValid = function(value, rules, fnCallback, errorCallback) {};

/**
 * 验证表单,并在所有表单验证正确的时候返回表单数据
 * @param  {String} sel     [表单选择器]
 * @param  {Object} options [其他需要传输的对象]
 * @return {Boolean|Object} [当错误时返回false,正确时返回所有的对象]
 */
var getFormData = function(sel, options) {};

通过在模板中定义自定义的html属性来进行规则定义,然后拦截 submit 事件,调用 getFormData($formElement)

getFormData内部会调用 formValid 来校验规则

如果返回数据那么表明验证成功,直接提交数据即可

如果返回数据是false,那么就提示错误

当然为了方便在 input 触发 blur 或者是 change 事件时,进行单独校验,所以第一个接口也是暴露的,并且能够传入两个回调函数

Server

  1. 服务器
用青云的服务,前端服务器就一台Debian虚拟机,双核2G,能够承受上万的访问量,并没有做特别的优化
  1. 部署
主要采用scp上传完整包,服务器部署

这边部署我们自己写了部署脚本,基于 Shell 的,采用plist文件列表指定版本号,并且能够产生log追溯问题版本

具体的脚本放在github

https://gist.github.com/lujiajing1126/eca5358fe7ab114bb83f

以上所述就是小编给大家介绍的《年终总结 之 校校前端实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

精通Android游戏开发

精通Android游戏开发

[美] Vladimir Silva / 王恒、苏金国 等 / 人民邮电出版社 / 2011-2 / 45.00元

作为引领移动技术潮流的软件平台,Android发布了NDK以支持Java和C的混合开发,使PC游戏可以在Android平台上焕发更多魅力。 本书是为那些在Android游戏开发工作中寻求突破的人准备的。书中不仅通过Space Blaster和Asteroids这两个炫酷 的街机游戏深入介绍了如何从头构建纯Java游戏,更详细展示了如何将PC上的3D经典游戏Doom和Wolfenstein 3......一起来看看 《精通Android游戏开发》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具