再看豆瓣的JavaScript
a.suggest=function(n,f){
var c=a(n).attr("autocomplete","off");//将this的autocomplete属性设置为off,但是貌似没见他的这个属性啊~
var e=a(document.createElement("ul"));//创建一个ul的list
var m=false;
var d=0;
var p=[];
var o=0;
e.addClass(f.resultsClass).appendTo("body");//给这个list增添一个样式
i();//给这个list定位
a(window).load(i).resize(i);//绑定事件
c.blur(function(){
setTimeout(function(){
e.hide()
},200)
});
c.data("reset",i);//将"i()"存在reset中,name对应value,此value可以是任意数据
c.keyup(l);//keyup事件绑定"l()"
function i(){
var u=c.offset();
e.css({top:(u.top+n.offsetHeight)+"px",left:u.left+"px"})
}
function l(u){//u为默认参数,相当于event参数
if((/27$|38$|40$/.test(u.keyCode)&&e.is(":visible"))||(/^13$|^9$/.test(u.keyCode)&&s())){//对keyCode的控制
if(u.preventDefault){
u.preventDefault()//阻止默认事件
}
if(u.stopPropagation){
u.stopPropagation()
}
u.cancelBubble=true;
u.returnValue=false;
switch(u.keyCode){
case 38:j();//Up
break;
case 40:r();//Down
break;
case 9:case 13:q();//Enter
break;
case 27:e.hide();//Esc
break
}
}
else{
if(c.val().length!=d){//如果搜索的长度不为0
if(m){
clearTimeout(m)
}
m=setTimeout(k,f.delay);//delay默认为100
d=c.val().length;
userInput=c.val()
}
}
}
function k(){
var u=a.trim(c.val());
if(u.length>=f.minchars){
cached=t(u);//如果为第一次输入返回的是false
if(cached){
h(cached.items)//提交缓存的item内容
}
else{
a.get(f.source,{q:u},function(v){//source即为取数据的页面,前台调用的是"/j/subject_suggest"这个玩意,回调函数返回一个v的东西
e.hide();
var w=b(v,u);//此处的w为搜索匹配选项的数组
h(w);//提交
g(u,w,v.length)//u为用户输入内容,w为搜索匹配的数组,v为返回的东西,应该为字符串,g()将搜索的字符串作为缓存
})
}
}
else{
e.hide()
}
}
//取缓存
function t(v){//传入的参数v即为用户所输入的内容
for(var u=0;u<p.length;u++){//p为全局变量,初始为一个空数组,所以如果第一次输入的话,就不会走for这个分支,而直接return false了
if(p[u]["q"]==v){//已存有用户输入的内容
p.unshift(p.splice(u,1)[0]);//直接加到全局数组,也就是缓存数组的最前面
return p[0]
}
}
return false
}
function g(x,u,v){
while(p.length&&(o+v>f.maxCacheSize)){//在此处第一次p数组依旧为空,所以此处不会跑,如果以后数组不为空之后而且字符串特别大,超出maxCacheSize这个范围之后,就把这个值Push了
var w=p.pop();
o-=w.size
}
p.push({q:x,size:v,items:u});//凑成一个Json格式的对象存入数组,以后做缓存用
o+=v
}
function h(u){//u为匹配项的数组
if(!u){
return;
}
if(!u.length){
e.hide();
return;
}
var w="";
for(var v=0;v<u.length;v++){
w+="<li>"+u[v]+"</li>"
}
e.html(w).show();//输出,显示
e.children("li").mouseover(function(){
e.children("li").removeClass(f.selectClass);
a(this).addClass(f.selectClass)//给选择的对象添加一个样式
}).click(function(x){
x.preventDefault();
x.stopPropagation();//一些事件问题
q();//click事件下执行
})
}
function b(u,y){//u为回调返回的data,而y为用户开始输入的值
var v=[];
var z=u.split(f.delimiter);//以"\n"为分隔符
for(var x=0;x<z.length;x++){
var w=a.trim(z[x]);
if(w){
w=w.replace(new RegExp(y,"ig"),function(A){
return'<span class="'+f.matchClass+'">'+A+"</span>"
});
v[v.length]=w;//将返回的data做处理之后,全部存入v[]中,并在最后返回v[];
}
}
return v;
}
function s(){
if(!e.is(":visible")){
return false
}
var u=e.children("li."+f.selectClass);//类选择器查找子元素
if(!u.length){
u=false
}
return u//返回选中的li
}
function q(){//Submit
$currentResult=s();
if($currentResult){
c.val($currentResult.text());//在文本框中输入li的内容
e.hide();
if(f.onSelect){
f.onSelect.apply(c[0])//将c[0]实体传入,要执行f.onSelect这个方法,实际上就是传入this指针的意思
}
}
}
function r(){//Down
$currentResult=s();
if($currentResult){
$currentResult.removeClass(f.selectClass).next().addClass(f.selectClass);
c.val($currentResult.next().text()||userInput)
}
else{
e.children("li:first-child").addClass(f.selectClass);
c.val(e.children("li:first-child").text())
}
}
function j(){//Up
$currentResult=s();
if($currentResult){
$currentResult.removeClass(f.selectClass).prev().addClass(f.selectClass);
c.val($currentResult.prev().text()||userInput)
}
else{
e.children("li:last-child").addClass(f.selectClass);
c.val(e.children("li:last-child").text())
}
}
};
a.fn.suggest=function(c,b){
if(!c){
return
}
b=b||{};
b.source=c;
b.delay=b.delay||100;
b.resultsClass=b.resultsClass||"ac_results";
b.selectClass=b.selectClass||"ac_over";
b.matchClass=b.matchClass||"ac_match";
b.minchars=b.minchars||1;
b.delimiter=b.delimiter||"\n";
b.onSelect=b.onSelect||false;
b.maxCacheSize=b.maxCacheSize||65536;
this.each(function(){
new a.suggest(this,b)
});
return this
}
})(jQuery);
昨天关于由豆瓣注册页面所想到的(求助) 发出后,园子里好多朋友都给出了好多意见,不仅给我指出了算法的链接,同时附带的一些细节性的问题,我也受益匪浅。当然,更有朋友用另外的方式实现了此功能,而且在他指出的网站,我也有去测试过,速度还是可以接受的。
最近贼喜欢偷窥人家的js,幸好人家豆瓣的js是绝对路径的,能down下来,不然也没的机会让给我给研究了。
Douban的jquery使用技巧 一文的作者说过:我比较喜欢它的一体式的事件处理机制,不用写很多的事件绑定代码,只需要通过一定的命名规则就可以自动给页面元素加上一些功能, 它上面几乎所有的功能都通过这个实现, 配合jquery强大的选择器,代码看起来比较简洁清晰.
的确,统一的时间处理机制可以使得代码比较简洁,但是不熟悉的人维护或者开发新的功能,的确有些浪费时间。就象我,为了找一个事件的处理,发了很多的时间去找他的事件处理的入口点,后来才发现事件的入口点在js里面,通过某些函数对所有所有事件进行了绑定,上文中,作者对此已有解释。
豆瓣页面一般引用的是3个js全部压缩好的,因此下载下来想要看的话也是比较费力。一个是jQuery的js,这个毋庸置疑,一个是豆瓣所有初始化,事件的js,另外一个就是豆瓣给每个页面右上角的那个搜索框所作的一个主题建议的js,经过分析,这个js完全是为这个搜索框所服务的。此文要分析的就是这份js代码。
实际上,他的这个效果还是和上次的文章有些类似,不过上次的邮箱匹配时直接用js匹配的,而这次的主题建议是要从后台取数据的,效果还是蛮不错的:
其实也就是咱们平时很常见的主题建议,其实他的js也是命名为什么什么suggest.js,哈哈~
你可以右键查看下该页面的html页面,该输入框的信息也就是由那么一点点,<input class="j a_search_text" id="page_focus" name="search_text"
title="书籍、电影、音乐" type="text" size="22" maxlength="60" value="" />
除此之外,没了...实在找不出引发输入框事件的入口点...后来才想起上面那篇文章所提到的统一绑定事件,看它的class,那么怪,上面我提到的那篇文章已对这种命名有了解释了....
,根据输入框的id,在douban52.js中有这么一段:
$("#page_focus").suggest("/j/subject_suggest",{
onSelect:function(){
$(this).parents("form").append('<span><input name="add" value="1" type="hidden"/></span>').submit()
}
})
}
那$.suggest是什么呢,记得原始jQuery中并没有这样一个玩意啊,应该是它扩展的,其实也就是整个提示的js,就是实现这个自动suggest功能的。这段代码就是最上面那段JS,下载下来,发现他已经压缩,然后我慢慢排好版,一段一段看懂,写了些注释,其实也就是那么些玩意,功能里面嵌套功能,函数里面嵌套函数,虽然看似比较繁琐,静下心来看看其实还是有蛮多收获的,对于这个扩展,很佩服这个coder的组织能力。
有一个疑问的地方是:
这个js有想实现类似缓存建议的功能,从代码的逻辑就能看出:
if(u.length>=f.minchars){
cached=t(u);//如果为第一次输入返回的是false
if(cached){
h(cached.items)//提交缓存的item内容
}
else{
a.get(f.source,{q:u},function(v){//source即为取数据的页面,前台调用的是"/j/subject_suggest"这个玩意,回调函数返回一个v的东西
e.hide();
var w=b(v,u);//此处的w为搜索匹配选项的数组
h(w);//提交
g(u,w,v.length)//u为用户输入内容,w为搜索匹配的数组,v为返回的东西,应该为字符串,g()将搜索的字符串作为缓存
if后面,如果缓存存在就怎么样怎么样,如果不存在就去后台去。如果不考虑页面状态的问题,实现也完全没有错...
但是,紧紧把所谓的"缓存"存储在一个全局数组p[]里面,就算页面提交后跳转,难道存储在数组的元素还能存在,web页面不是无状态的么
另外,豆瓣到底是用什么写的,看了半天,也不知道他是用什么写的..