背景
最近的项目遇到了一些性能瓶颈,本篇文章先不谈数据库方面的问题,仅拿前端加载一定量的数据来进行阐述,觉得目前方式比较耗时。前段时间也在做些系统优化,效果并不明显。现在是怀疑出在前端的一些ajax调用以及jQuery本身存在的一些性能问题上;于是,先试着做出些原型,进行各种形式下前端加载数据时的性能对比。
详细分析
1. 首先,创建一系列的实体类:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
///
<summary>
///
用户信息
///
</summary>
public
class
UserInfo
{
public
int
UserId {
get
;
set
; }
public
string
UserName {
get
;
set
; }
public
string
Email {
get
;
set
; }
public
Class Class {
get
;
set
; }
public
List
<
UserRight
>
UserRightList {
get
;
set
; }
}
///
<summary>
///
班级信息
///
</summary>
public
class
Class
{
public
int
ClassId {
get
;
set
; }
public
string
ClassName {
get
;
set
; }
}
///
<summary>
///
用户权限信息
///
</summary>
public
class
UserRight
{
public
int
RightId {
get
;
set
; }
public
string
RightName {
get
;
set
; }
}
其中包括用户信息类、班级信息类、用户权限类。
2. 在Web.config配置一个appsettings节点
<appSettings>
<add key="DataCount" value="3000"/>
</appSettings>
表示一次加载的数据量(用户信息数)为3000。
并且将compilation节点的dubug属性设置为false。
3. JSON加载数据测试
1)首先先测试 WebService客户端调用并且最后返回JSON加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDataWebServiceJson() {
var
watch
=
new
Stopwatch();
watch.start();
JsonService.GetUserList(
function
(data) {
var
builder
=
new
Sys.StringBuilder();
for
(
var
i
=
0
, length
=
data.length; i
<
length; i
++
) {
builder.append(String.format(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, data[i].UserId, data[i].UserName, data[i].Email));
}
$(
"
#msg2
"
).html(builder.toString());
watch.stop();
$(
"
#time2
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
});
}
(注:这里引入了一个Stopwatch计时器,很简单,具体可以参考:http://www.cnblogs.com/liping13599168/archive/2009/08/15/1546673.html)
通过代码,我们可以看到它调用了JsonService.asmx中Web服务的GetUserList方法:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
[WebMethod]
public
List
<
UserInfo
>
GetUserList()
{
int
count
=
Convert.ToInt32(ConfigurationManager.AppSettings[
"
DataCount
"
]);
List
<
UserInfo
>
list
=
new
List
<
UserInfo
>
();
for
(
int
i
=
0
; i
<
count; i
++
)
{
list.Add(
new
UserInfo { UserId
=
i, UserName
=
"
leepy
"
+
i, Email
=
"
sunleepy
"
+
i
+
"
@gmail.com
"
});
}
return
list;
}
返回了一个3000条记录的UserInfo实体列表,而前端页面通过对列表返回客户端时的Json对象,进行Html字符串拼接,最后通过jQuery的Dom元素html(‘…’)方法赋值。运行加载结果为:
目前我的项目大多数就是采用这样的模式。
2)接着,如果使用jQuery的$.ajax调用并且最后JSON加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDatajQueryAjaxJson() {
var
watch
=
new
Stopwatch();
watch.start();
$.ajax({
url:
"
JsonHandler.ashx
"
,
dataType:
'
json
'
,
cache:
false
,
success:
function
(data) {
var
builder
=
new
Sys.StringBuilder();
for
(
var
i
=
0
, length
=
data.length; i
<
length; i
++
) {
builder.append(String.format(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, data[i].UserId, data[i].UserName, data[i].Email));
}
$(
"
#msg4
"
).html(builder.toString());
watch.stop();
$(
"
#time4
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
}
});
}
通过JsonHandler.ashx的页面处理程序执行返回数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
public
void
ProcessRequest (HttpContext context) {
context.Response.ContentType
=
"
application/x-javascript
"
;
int
count
=
Convert.ToInt32(ConfigurationManager.AppSettings[
"
DataCount
"
]);
List
<
UserInfo
>
list
=
new
List
<
UserInfo
>
();
for
(
int
i
=
0
; i
<
count; i
++
)
{
list.Add(
new
UserInfo { UserId
=
i, UserName
=
"
leepy
"
+
i, Email
=
"
sunleepy
"
+
i
+
"
@gmail.com
"
});
}
StringBuilder builder
=
new
StringBuilder();
string
data
=
"
[
"
;
for
(
int
i
=
0
, length
=
list.Count; i
<
length; i
++
)
{
data
+=
"
{
"
+
string
.Format(
"
'UserId':'{0}','UserName':'{1}', 'Email':'{2}'
"
, list[i].UserId, list[i].UserName, list[i].Email)
+
"
},
"
;
}
data
=
data.TrimEnd(
'
,
'
)
+
"
]
"
;
context.Response.Write(data);
}
也是3000条用户数据,运行加载结果为:
比WebService快了一些。
3)接着,使用$.getJSON来调用并且JSON加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDatajQueryGetJson() {
var
watch
=
new
Stopwatch();
watch.start();
$.getJSON(
"
JsonHandler.ashx
"
,
function
(data) {
var
builder
=
new
Sys.StringBuilder();
for
(
var
i
=
0
, length
=
data.length; i
<
length; i
++
) {
builder.append(String.format(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, data[i].UserId, data[i].UserName, data[i].Email));
}
$(
"
#msg5
"
).html(builder.toString());
watch.stop();
$(
"
#time5
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
});
}
同样通过JsonHandler.ashx的页面处理程序执行返回数据,运行加载结果为:
和$.ajax相差无几,实际从jQuery代码中可以看出实际上$.getJSON调用的就是$.ajax函数:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
get:
function
(url, data, callback, type)
{
//
shift arguments if data argument was ommited
if
(jQuery.isFunction(data))
{
callback
=
data;
data
=
null
;
}
return
jQuery.ajax({
type:
"
GET
"
,
url: url,
data: data,
success: callback,
dataType: type
});
},
getJSON:
function
(url, data, callback)
{
return
jQuery.get(url, data, callback,
"
json
"
);
},
4)接着,使用xmlHttp.js的原生对象调用并且JSON加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDataPrototypeAjaxJson() {
var
watch
=
new
Stopwatch();
watch.start();
Request.sendGET(
"
JsonHandler.ashx
"
,
false
,
function
(data) {
var
jsonData
=
JSON.parse(data,
null
);
var
builder
=
new
Sys.StringBuilder();
for
(
var
i
=
0
, length
=
jsonData.length; i
<
length; i
++
) {
builder.append(String.format(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, jsonData[i].UserId, jsonData[i].UserName, jsonData[i].Email));
}
$(
"
#msg11
"
).html(builder.toString());
watch.stop();
$(
"
#time11
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
});
}
其中var jsonData = JSON.parse(data, null); 用到了一个json2.js 的开源JSON解析组件,将data的字符串转换为一个JSON对象。运行结果为:
和jQuery的$.ajax函数也是基本相差无几。
测试说明:$.ajax,$.getJSON,原生对象返回JSON加载数据的效率基本一样,比WebService的效率好些。
4. Html字符串加载数据测试
1)WebService客户端调用返回Html字符串加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDataWebServiceHtml() {
var
watch
=
new
Stopwatch();
watch.start();
HtmlService.GetUserListHtml(
function
(data) {
$(
"
#msg1
"
).html(data);
watch.stop();
$(
"
#time1
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
});
}
通过代码,我们可以看到它调用了HtmlService.asmx中Web服务的GetUserListHtml方法:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
[WebMethod]
public
string
GetUserListHtml() {
List
<
UserInfo
>
list
=
GetUsers();
StringBuilder builder
=
new
StringBuilder();
for
(
int
i
=
0
, length
=
list.Count; i
<
length; i
++
)
{
builder.AppendFormat(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, list[i].UserId, list[i].UserName, list[i].Email);
}
return
builder.ToString();
}
将前端页面对于Html字符串拼接的工作放在WebService中(或者相关后台代码)中去执行,最后通过jQuery的Dom元素html(‘…’)方法赋值。运行加载结果为:
2)jQuery的$.ajax调用返回Html字符串加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDatajQueryAjaxHtml() {
var
watch
=
new
Stopwatch();
watch.start();
$.ajax({
type:
"
get
"
,
url:
"
HtmlHandler.ashx
"
,
cache :
false
,
success:
function
(data) {
$(
"
#msg3
"
).html(data);
watch.stop();
$(
"
#time3
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
}
});
}
通过HtmlHandler.ashx的页面处理程序执行返回数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
public
void
ProcessRequest (HttpContext context) {
int
count
=
Convert.ToInt32(ConfigurationManager.AppSettings[
"
DataCount
"
]);
List
<
UserInfo
>
list
=
new
List
<
UserInfo
>
();
for
(
int
i
=
0
; i
<
count; i
++
)
{
list.Add(
new
UserInfo { UserId
=
i, UserName
=
"
leepy
"
+
i, Email
=
"
sunleepy
"
+
i
+
"
@gmail.com
"
});
}
StringBuilder builder
=
new
StringBuilder();
for
(
int
i
=
0
, length
=
list.Count; i
<
length; i
++
)
{
builder.AppendFormat(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, list[i].UserId, list[i].UserName, list[i].Email);
}
context.Response.Write(builder.ToString());
}
运行加载结果为:
比WebService也快了一些。
3)原生Ajax调用返回Html字符串加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
function
bindDataPrototypeAjaxHtml() {
var
watch
=
new
Stopwatch();
watch.start();
Request.sendGET(
"
HtmlHandler.ashx
"
,
false
,
function
(data) {
$(
"
#msg6
"
).html(data);
watch.stop();
$(
"
#time6
"
).html(
"
用时:
"
+
watch.ms
+
"
毫秒.
"
);
});
}
运行载结果为:
和jQuery的$.ajax函数也是基本相差无几。
测试说明:$.ajax,$.getJSON,原生对象返回返回Html字符串加载数据的效率基本一样,比WebService的效率好些;并且通过3和4的比较,说明通过在后台拼接Html字符串,比在前台来拼接的过程来得更高效。
5. 在我的实例中,大家也许注意到了有“原生Html赋值”和“jQuery Dom赋值”的单选按钮,这是什么呢?很简单,前者就是形如document.getElementById(‘…’),后者就是我们项目中使用的$('#…’).html(‘…’)这样的形式。这两种创建DOM元素上有什么差别吗?用测试说话。
我任意挑选几张对比截图:
测试说明:使用原生对象Html赋值比jQuery的Html赋值性能上提升了许多;换句话,就是尽量多使用document.getElementById(‘…’)这样来赋值。
6. 最后我们通过cs代码隐藏文件加载数据:
代码
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ /><br/ />-->
protected
void
Page_Load(
object
sender, EventArgs e)
{
Stopwatch watch
=
new
Stopwatch();
watch.Start();
int
count
=
Convert.ToInt32(ConfigurationManager.AppSettings[
"
DataCount
"
]);
List
<
UserInfo
>
list
=
new
List
<
UserInfo
>
();
for
(
int
i
=
0
; i
<
count; i
++
)
{
list.Add(
new
UserInfo { UserId
=
i, UserName
=
"
leepy
"
+
i, Email
=
"
sunleepy
"
+
i
+
"
@gmail.com
"
});
}
StringBuilder builder
=
new
StringBuilder();
for
(
int
i
=
0
, length
=
list.Count; i
<
length; i
++
)
{
builder.AppendFormat(
"
<div>UserId:{0}, UserName:{1}, Email:{2}</div>
"
, list[i].UserId, list[i].UserName, list[i].Email);
}
msg7.InnerHtml
=
builder.ToString();
time7.Text
=
"
用时:
"
+
watch.ElapsedMilliseconds
+
"
毫秒.
"
;
}
public
int
GetDataCount()
{
return
Convert.ToInt32(ConfigurationManager.AppSettings[
"
DataCount
"
]);
}
运行结果为:
使用后台代码进行页面上的业务逻辑实现是相当高效的。
测试说明:在页面第一次加载的时候,尽量能够在服务端后台代码对服务器控件进行数据绑定,也就是说多使用runat=”server”这样的服务端控件,特别可以充分使用repeater控件的优势来进行数据绑定。
总结
再把前面的测试结果列举一下:
1. .ajax,$.getJSON,原生对象返回JSON加载数据的效率基本一样,比WebService的效率好些。
2. $.ajax,$.getJSON,原生对象返回返回Html字符串加载数据的效率基本一样,比WebService的效率好些;
3. 通过测试3和测试4的比较,说明通过在后台拼接Html字符串,比在前台来拼接的过程来得更高效。
4. 使用原生对象Html赋值比jQuery的Html赋值性能上提升了许多;换句话,就是尽量多使用document.getElementById(‘…’)这样来赋值。
5. 在页面第一次加载的时候,尽量能够在服务端后台代码对服务器控件进行数据绑定,也就是说多使用runat=”server”这样的服务端控件,特别可以充分使用repeater控件的优势来进行数据绑定。
这是我对于前端页面加载数据时,通过几种方式测试得出来的结果以及结论,如果不妥的地方,希望大家能够提出,欢迎交流,谢谢指教:)
下一篇,我会介绍下,在当前项目下,如何能够快速优化当前的系统,比如我现在是使用WebService,如何在不改变前端JS代码的情况下,切换到$.ajax或者原生对象中去,有点像“适配器”的概念吧。