对比直接使用jquery和angularjs,在CRUD中的区别。
Bind
angularjs最大的优势,在于对于CRUD采取了对象化。以前要用jquery将数据绑定到dom中,现在是直接对象处理。所以在这个层次上来讲,angularjs的定位和思路是很不错的。
对比下面jquery做的一个用户注册页面,其实jquery已经简化了很多工作:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>SRS监控系统</title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript" src="js/bsm.monitor.api.js"></script>
<script type="text/javascript" src="js/bsm.logs.js"></script>
<script type="text/javascript" src="js/bsm.utility.js"></script>
<script type="text/javascript" src="js/winlin.utility.js"></script>
<script type="text/javascript" src="js/bsm.page.template.js"></script>
<style>
body{
padding-top: 55px;
}
</style>
<script type="text/javascript">
var query = parse_query_string();
var api = new MonitorApi();
// main entrance
$(function(){
render_footer($("#footer"));
render_nav($("#nav"), null);
logs.render($("#log"));
logs.info("verify qq-auth, token=" + query.access_token);
api.qqauths_verify(query.access_token, on_error, on_auth_success);
});
// when verify qq-auth success, login or create user.
function on_auth_success(openid, nickname, gender, figureurl, qqurl) {
logs.info("qq-auth ok, nickname=" + nickname);
api.users_load(openid, on_error, function(user){
if (user != null) {
on_user_login(user);
return;
}
create_user_for_openid(openid, nickname, gender, figureurl, qqurl);
});
}
// create session when user is null.
// the user corresponding to openid doesn't exists.
function create_user_for_openid(openid, nickname, gender, figureurl, qqurl) {
logs.info("create user for openid="+openid);
var username = build_random_username();
var password = build_random_password();
$("#user_name").val(username);
$("#user_password").val(password);
$("#user_nickname").val(nickname);
$("#user_openid").val(openid);
$("#create_user").click(on_click_btn_create_user);
$("#create_user_ui").show();
}
// submit to create new user
function on_click_btn_create_user(){
logs.info("submit to create user.");
$("#create_user_ui").find("input").attr("disabled", true);
$("#create_user_ui").find("button").attr("disabled", true);
var reset =function() {
$("#create_user_ui").find("input").attr("disabled", false);
$("#create_user_ui").find("button").attr("disabled", false);
$("#create_user_ui").hide();
}
var err = function(code, msg) {
reset();
on_error(code, msg);
}
var username = $("#user_name").val();
var password = $("#user_password").val();
var nickname = $("#user_nickname").val();
var email = $("#user_email").val();
var mobile = $("#user_mobile").val();
var qq = $("#user_qq").val();
var openid = $("#user_openid").val();
api.users_create2(username, password, openid, nickname, email, mobile, qq, err, function(user){
reset();
on_user_login(user);
});
}
// session created or loaded
function on_user_login(user) {
logs.info("user login ok");
window.location.href = "index.html";
}
</script>
</head>
<body>
<div id="nav"></div>
<div id="log"></div>
<div class="container">
<div class="accordion hide" id="create_user_ui">
<div class="accordion-group">
<div class="accordion-heading">
<span class="accordion-toggle" data-toggle="collapse">
<strong>注册新用户</strong>
</span>
</div>
<div class="accordion-body collapse in">
<div class="accordion-inner form-horizontal">
<div class="control-group">
<label class="control-label" for="user_name">用户名</label>
<div class="controls">
<input id="user_name" class="input-large" type="text" value="">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_password">密码</label>
<div class="controls">
<input id="user_password" class="input-large" type="text" value="">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_nickname">昵称</label>
<div class="controls">
<input id="user_nickname" class="input-large" type="text" value="">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_email">Email</label>
<div class="controls">
<input id="user_email" class="input-large" type="text" value="" placeholder="Email">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_mobile">CellPhone</label>
<div class="controls">
<input id="user_mobile" class="input-large" type="text" value="" placeholder="CellPhone">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_qq">QQ</label>
<div class="controls">
<input id="user_qq" class="input-large" type="text" value="" placeholder="QQ">
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" id="create_user">注册新用户</button>
</div>
</div>
<input type="hidden" id="user_openid">
</div>
</div>
</div>
</div>
</div>
<div id="footer"></div>
</body>
</html>
/**
* verify the access token and got informations.
* @param access_token the token when login success by qq.
* @param on_error a function(code, msg) callback when error, where msg is optional.
* @param on_success a function(openid, nickname, gender, figureurl, qqurl) when success.
*/
MonitorApi.prototype.qqauths_verify = function (access_token, on_error, on_success) {
var url = "/api/v1/qqauths?access_token=" + access_token;
this.__api_get(url, on_error, function(data){
on_success(data.openid, data.nickname, data.gender, data.figureurl, data.qqurl);
});
}
/**
* load user from openid.
* @param openid the openid response by qq-auth
* @param on_error a function(code, msg) callback when error, where msg is optional.
* @param on_success a function(user) when success, where user is a object identify the user by openid.
* user is null when user not find by the openid. please create a session by openid.
*/
MonitorApi.prototype.users_load = function (openid, on_error, on_success) {
var url = "/api/v1/users?openid=" + openid;
this.__api_get(url, on_error, function(data){
on_success(data.user);
});
}
/**
* create/register new user
* @param username the username of user.
* @param password the password of user.
* @param openid the openid response by qq-auth
* @param nickname the user nickname response by qq-auth
* @param on_error a function(code, msg) callback when error, where msg is optional.
* @param on_success a function(user) when success, where user is a object.
*/
MonitorApi.prototype.users_create = function (username, password, openid, nickname, on_error, on_success) {
var data = {};
data.openid = openid;
data.nickname = nickname;
data.username = username;
data.password = password;
var url = "/api/v1/users";
this.__api_post(url, data, on_error, function(data){
on_success(data.user);
});
}
/**
* create/register new user
* @param username the username of user.
* @param password the password of user.
* @param openid the openid response by qq-auth
* @param nickname the user nickname response by qq-auth
* @param email the email of user. optional, canbe null.
* @param mobile the mobile of user. optional, canbe null.
* @param qq the qq of user. optional, canbe null.
* @param on_error a function(code, msg) callback when error, where msg is optional.
* @param on_success a function(user) when success, where user is an object.
*/
MonitorApi.prototype.users_create2 = function (username, password, openid, nickname, email, mobile, qq, on_error, on_success) {
var data = {};
data.openid = openid;
data.nickname = nickname;
data.username = username;
data.password = password;
// optionals
data.email = email;
data.mobile = mobile;
data.qq = qq;
var url = "/api/v1/users";
this.__api_post(url, data, on_error, function(data){
on_success(data.user);
});
}
用angularjs实现的版本:
<!DOCTYPE html>
<html ng-app="bsmApp">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>SRS监控系统</title>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"/>
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript" src="js/angular-route.min.js"></script>
<script type="text/javascript" src="js/angular-resource.js"></script>
<script type="text/javascript" src="js/bsm.ng.router.js"></script>
<script type="text/javascript" src="js/bsm.logs.js"></script>
<script type="text/javascript" src="js/bsm.utility.js"></script>
<style>
body{
padding-top: 55px;
}
</style>
<script type="text/javascript">
$(function() {
logs.render($("#log"));
});
</script>
</head>
<body ng-controller="MainController">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="javascript:void(0)" title="BSM(Bravo SRS Monitor)">监控</a>
<div class="nav-collapse collapse">
<ul class="nav" ng-repeat="nav in navs">
<li class="{{nav.class}}"><a href="{{nav.url}}" target="{{nav.target}}">{{nav.text}}</a></li>
</ul>
</div>
</div>
</div>
</div>
<div id="log"></div>
<div ng-view></div>
</body>
</html>
<div class="container">
<div class="accordion {{user_reg_visible}}">
<div class="accordion-group">
<div class="accordion-heading">
<span class="accordion-toggle" data-toggle="collapse">
<strong>注册新用户</strong>
</span>
</div>
<div class="accordion-body collapse in">
<div class="accordion-inner form-horizontal">
<div class="control-group">
<label class="control-label" for="user_name">用户名</label>
<div class="controls">
<input id="user_name" class="input-large" type="text" value="{{user_reg_username}}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_password">密码</label>
<div class="controls">
<input id="user_password" class="input-large" type="text" value="{{user_reg_password}}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_nickname">昵称</label>
<div class="controls">
<input id="user_nickname" class="input-large" type="text" value="{{user_reg_nickname}}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_email">Email</label>
<div class="controls">
<input id="user_email" class="input-large" type="text" value="{{user_reg_email}}" placeholder="Email">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_mobile">CellPhone</label>
<div class="controls">
<input id="user_mobile" class="input-large" type="text" value="{{user_reg_phone}}" placeholder="CellPhone">
</div>
</div>
<div class="control-group">
<label class="control-label" for="user_qq">QQ</label>
<div class="controls">
<input id="user_qq" class="input-large" type="text" value="{{user_reg_qq}}" placeholder="QQ">
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn" ng-click="user_register()">注册新用户</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
// the application, system main object.
var bsmApp = angular.module('bsmApp', ['ngRoute', 'bsmControllers', 'bsmFilters', 'bsmServices']);
// the controllers, used to generate variety controllers for views.
var bsmControllers = angular.module('bsmControllers', []);
// the services, system model, RESTful data from backend api.
var bsmServices = angular.module('bsmServices', ['ngResource']);
// config the route
bsmApp.config([
'$routeProvider', function($routeProvider) {
$routeProvider.when('/servers', {
templateUrl: 'views/servers.html',
controller: 'BsmServers'
}).when('/users', {
templateUrl: 'views/users.html',
controller: 'BsmUsers'
}).otherwise({
redirectTo: '/servers'
});
}
]);
// controller: BsmUsers, for the view users.html.
bsmControllers.controller('BsmUsers', ['$scope', '$routeParams', 'QQAuth', 'User', function($scope, $routeParams, QQAuth, User){
$scope.$parent.nav_active_servers();
$scope.user_reg_visible = "hide";
$scope.user_reg_username = build_random_username();
$scope.user_reg_password = build_random_password();
$scope.user_reg_nickname = "";
$scope.user_reg_email = "";
$scope.user_reg_phone = "";
$scope.user_reg_qq = "";
$scope.user_reg_openid = "";
$scope.user_register = function() {
User.users_create2({
openid: $scope.user_reg_openid,
nickname: $scope.user_reg_nickname,
username: $scope.user_reg_username,
password: $scope.user_reg_password,
// optionals
email: $scope.user_reg_email,
mobile: $scope.user_reg_phone,
qq: $scope.user_reg_qq
}, function(data) {
$scope.__on_user_login(data.user);
});
};
logs.info("正在验证access_token=" + $routeParams.access_token);
QQAuth.get({access_token:$routeParams.access_token}, function(data) {
var obj = data.data;
$scope.__on_auth_success(obj.openid, obj.nickname, obj.gender, obj.figureurl, obj.qqurl);
});
$scope.__on_auth_success = function(openid, nickname, gender, figureurl, qqurl) {
logs.info("验证access_token成功, openid=" + openid + ", nickname=" + nickname);
User.load_by_openid({openid: openid}, function(data) {
if (data.user) {
$scope.__on_user_login(data.user);
return;
}
logs.info("create user for openid="+openid);
$scope.user_reg_nickname = nickname;
$scope.user_reg_openid = openid;
$scope.user_reg_visible = "show";
});
}
$scope.__on_user_login = function(user) {
logs.info("user login ok");
window.location.href = "index.html";
}
}]);
后者不仅在代码行数上要少,而且前端html和js的对象是直接对应,省去$("#xxx").val()这种取值和赋值的操作。若用于展示,优势更明显。
Filter
filter在处理数据变换,譬如init状态为“初始化”,时间从毫秒变成年月日等,这些变换上和jquery的区别也很大。
下面是jquery在展示数据变换的例子:
logs.info("render server list");
for (var i = 0; i < servers.length; i++) {
var server = servers[i];
var obj_id = "server_node_" + server.id;
var obj = server_list.find('#' + obj_id);
if (obj.length <= 0) {
obj = $("<tr/>").html($("#server_list_tmpl").html());
obj.attr("id", obj_id);
server_list.append(obj);
}
obj.find("#server_list_server_id").html(server.id);
obj.find("#server_list_server_ip").html(server.ip + ":" + server.rtmp_port);
obj.find("#server_list_server_api_port").text(server.api_port).attr("href", servers_get_api_summaries(server.ip, server.api_port));
obj.find("#server_list_server_desc").html(server.desc);
obj.find("#server_list_server_status").empty().append(
$("<span/>").addClass("label").addClass(servers_status2class(server.status)).html(servers_status2text(server.status))
);
if (server.basic == null) {
obj.find("#server_list_basic_time").html("未知");
obj.find("#server_list_basic_load").html("未知");
obj.find("#server_list_basic_cpu").html("未知");
obj.find("#server_list_basic_mem").html("未知");
continue;
}
var basic_time = server.basic.time;
if (basic_time.srs_uptime > 24 * 3600) {
obj.find("#server_list_basic_time").html(parseInt(basic_time.srs_uptime / 24 / 3600) + "天 ");
obj.find("#server_list_basic_time").append(relative_seconds_to_HHMMSS(basic_time.srs_uptime));
} else {
obj.find("#server_list_basic_time").html(relative_seconds_to_HHMMSS(basic_time.srs_uptime));
}
var basic_load = server.basic.load;
obj.find("#server_list_basic_load").html(
Number(basic_load.load_1m).toFixed(1) + ", " + Number(basic_load.load_5m).toFixed(1)
+ ", " + Number(basic_load.load_15m).toFixed(1)
);
var basic_cpu = server.basic.cpu;
obj.find("#server_list_basic_cpu").html(
basic_cpu.cpus + "U, " + Number(basic_cpu.cpus * basic_cpu.cpu_percent * 100).toFixed(1)
+ "%, " + Number(basic_cpu.srs_cpu_percent*100).toFixed(1) + "%"
);
var basic_mem = server.basic.mem;
obj.find("#server_list_basic_mem").html(
parseInt(basic_mem.total / 1024) + "M, " + parseInt(basic_mem.total * basic_mem.used_percent / 1024)
+ ", " + parseInt(basic_mem.total * basic_mem.srs_percent / 1024)
);
}
页面上放置一个模板节点,隐藏:
<table id="server_list" class="table table-bordered table-hover">
<tr>
<th>
<abbr title="监控新的服务器" class="initialism">
<a href="javascript:void(0)" id="server_create_button"><i class="bravo-icon-plus"></i>添加</a>
</abbr>
</th>
<th><abbr title="便于记忆的服务器名称" class="initialism">名称</abbr></th>
<th><abbr title="服务器的IP,监控系统必须要能访问到" class="initialism">服务器IP</abbr></th>
<th><abbr title="SRS的运行时间(天 时:分:秒),非系统运行时间。" class="initialism">时间</abbr></th>
<th><abbr title="系统1分钟、5分钟、15分钟的负载,参考top命令。" class="initialism">负载</abbr></th>
<th>
<abbr title="系统的CPU个数(包括超线程的虚拟核)、系统CPU使用率百分比、SRS占用的百分比。" class="initialism">CPUs,Sys%,SRS%</abbr>
</th>
<th><abbr title="系统的总内存MB、系统已使用、SRS使用的内存。" class="initialism">内存MB,Sys,SRS</abbr></th>
<th><abbr title="服务器的API地址,显示服务器的基本信息" class="initialism">API</abbr></th>
<th><abbr title="服务器目前的状态,每隔一定时间监控系统会刷新数据" class="initialism">状态</abbr></th>
</tr>
<tr class="hide" id="server_list_tmpl">
<td id="server_list_server_id"></td>
<td id="server_list_server_desc"></td>
<td id="server_list_server_ip"></td>
<td id="server_list_basic_time"></td>
<td id="server_list_basic_load"></td>
<td id="server_list_basic_cpu"></td>
<td id="server_list_basic_mem"></td>
<td><a href="javascript:void(0)" id="server_list_server_api_port" target="_blank">#</a></td>
<td id="server_list_server_status"></td>
</tr>
</table>
换成angularjs的filter方式:
<table class="table table-bordered table-hover">
<tr>
<th>
<abbr title="监控新的服务器" class="initialism">
<a href="javascript:void(0)" id="server_create_button"><i class="bravo-icon-plus"></i>添加</a>
</abbr>
</th>
<th><abbr title="便于记忆的服务器名称" class="initialism">名称</abbr></th>
<th><abbr title="服务器的IP,监控系统必须要能访问到" class="initialism">服务器IP</abbr></th>
<th><abbr title="SRS的运行时间(天 时:分:秒),非系统运行时间。" class="initialism">时间</abbr></th>
<th><abbr title="系统1分钟、5分钟、15分钟的负载,参考top命令。" class="initialism">负载</abbr></th>
<th>
<abbr title="系统的CPU个数(包括超线程的虚拟核)、系统CPU使用率百分比、SRS占用的百分比。" class="initialism">CPUs,Sys%,SRS%</abbr>
</th>
<th><abbr title="系统的总内存MB、系统已使用、SRS使用的内存。" class="initialism">内存MB,Sys,SRS</abbr></th>
<th><abbr title="服务器的API地址,显示服务器的基本信息" class="initialism">API</abbr></th>
<th><abbr title="服务器目前的状态,每隔一定时间监控系统会刷新数据" class="initialism">状态</abbr></th>
</tr>
<tr ng-repeat="server in servers">
<td>{{server.id}}</td>
<td>{{server.desc}}</td>
<td>{{server.ip + ":" + server.rtmp_port}}</td>
<td>{{server.basic |servers_uptime}}</td>
<td>{{server.basic |servers_load}}</td>
<td>{{server.basic |servers_cpu}}</td>
<td>{{server.basic |servers_mem}}</td>
<td><a href="{{server |servers_link}}" target="_blank">{{server.api_port}}</a></td>
<td><span class="label {{server.status |servers_status_class}}">{{server.status |servers_status_text}}</span></td>
</tr>
</table>
使用angularjs的filter:
bsmFilters.filter('servers_status_class', function(){
return function(status) {
var status_dict = {
init: "label-inverse",
ready: "label-inverse",
sync: "label-info",
online: "label-success",
offline: "",
error: "label-important"
};
return status_dict[status]
};
}).filter('servers_status_text', function(){
return function(status) {
var text_dict = {
init: "未导入",
ready: "待同步",
sync: "同步中",
online: "已上线",
offline: "离线中",
error: "异常中"
}
return text_dict[status]
};
}).filter('servers_uptime', function(){
return function(basic) {
if (!basic) {
return "未知";
}
var srs_uptime = basic.time.srs_uptime;
if (srs_uptime > 24 * 3600) {
return parseInt(srs_uptime / 24 / 3600) + "天 " + relative_seconds_to_HHMMSS(srs_uptime);
} else {
return relative_seconds_to_HHMMSS(srs_uptime);
}
};
}).filter('servers_load', function(){
return function(basic) {
if (!basic) {
return "未知";
}
var basic_load = basic.load;
return Number(basic_load.load_1m).toFixed(1) + ", " + Number(basic_load.load_5m).toFixed(1)
+ ", " + Number(basic_load.load_15m).toFixed(1);
};
}).filter('servers_cpu', function(){
return function(basic) {
if (!basic) {
return "未知";
}
var basic_cpu = basic.cpu;
return basic_cpu.cpus + "U, " + Number(basic_cpu.cpus * basic_cpu.cpu_percent * 100).toFixed(1)
+ "%, " + Number(basic_cpu.srs_cpu_percent*100).toFixed(1) + "%";
};
}).filter('servers_mem', function(){
return function(basic) {
if (!basic) {
return "未知";
}
var basic_mem = basic.mem;
return parseInt(basic_mem.total / 1024) + "M, " + parseInt(basic_mem.total * basic_mem.used_percent / 1024)
+ ", " + parseInt(basic_mem.total * basic_mem.srs_percent / 1024);
};
}).filter('servers_link', function(){
return function(server) {
return servers_get_api_summaries(server.ip, server.api_port);
};
});
CodeLines
最后完成的代码行数对比,同样的功能,angularjs只有jquery一半的代码量,很赞:
[winlin@dev6 jquery.angularjs.compare]$ ~/srs/research/code-statistic/csr.py jquery *.html,*.js
total:1679 code:1404 comments:275(16.38%) block:220 line:55
[winlin@dev6 jquery.angularjs.compare]$ ~/srs/research/code-statistic/csr.py angularjs *.html,*.js
total:644 code:565 comments:79(12.27%) block:72 line:7
angularjs的代码只有jquery的一半不到。