jQuery Autocomplete plugin 1.2

修正因网络延迟而导致的显示错误
增加关键词高亮功能
修正了部分代码
使用固定的回调函数

插件代码:

(function($){
	$.tools = $.tools || {version: '1.2'};
	$.tools.suggest = {};
	$.tools.suggest.defaults = {
		url : null,
		params : null,
		delay : 100,
		cache : true,
		formId : '#search_form',
		focus:null
	}
	$.tools.suggest.borderKey = {
		UP: 38,
		DOWN: 40,
		TAB: 9,
		ESC: 27,
		ENTER:13
	}
 
	$.fn.suggest=function(options,fn){
		var options,key = $.tools.suggest.borderKey;
		if($.isFunction(options)){
			fn=options;
			options = $.extend({}, $.tools.suggest.defaults, key);
		}else{
			options = $.extend({}, $.tools.suggest.defaults, key, options);
		}
		return this.each(function(){
			var
				self = $(this),
				url = options.url,
				params = options.params,
				searchUrl = null,
				searchtimer = 0,
				delay = options.delay,
				cache = options.cache,
				formobj = $(options.formId),
				focus = options.focus,
				rebox = $('<ul />').attr("id","suggest"),
				htmlLi = null,
				litop = null,
				lileft = null,
				liwth = null,
				tip = false,
				val = null,
				rlen = null,
				UP = options.UP,
				DOWN = options.DOWN,
				TAB = options.TAB,
				ESC = options.ESC,
				ENTER = options.ENTER,
				index = -1,
				choseKey = null,
				backval = null,
				hidden = false,
				locksuggest = false,
				timer = null
			;
 
			/* 初始化 */
			if(focus){
				self.trigger('focus');
			}
 
			/* 初始化绑定事件 */
			self.bind("focus",function(){
				backval = (backval=$.trim(self.val()))==''?null:backval;	/* 触发焦点时初始化backval的值 */
				getQueue();
			})
			.bind("blur",function(){
				clearQueue();
				hideResult();
			})
			.bind("keydown",function(e){
				switch (e.keyCode){
				case UP:
					clearQueue();
					if($('#suggest').css('display') == 'none'){
						reSet();
						return false;
					}
					index--;
					if(index&lt;0){
						index=Math.abs(rlen)-1;
					}
					changeSelect(index);
					e.preventDefault();
					break;
				case DOWN:
					clearQueue();
					if($('#suggest').css('display') == 'none'){
						reSet();
						return false;
					}
					index++;
					if(index>=rlen){
						index=0;
					}
					changeSelect(index);
					e.preventDefault();
					break;
				case TAB:
					clearQueue();
					hideResult();
					break;
				case ESC:
					clearQueue();
					hideResult();
					e.preventDefault();
					break;
				case ENTER:
					clearQueue();
					break;
				default:
					getQueue();
					break;
				}
			});
 
			// 获取关键词
			function getKey(){
				val = $.trim(self.val());
				if(!!val && val!=backval){	/* 关键词不为空且关键词不重复 */
					backval = val;
					if(cache && !!$.tools.suggest[val]){	/* 如不需要缓存结果,设cache为false */
						index = -1;
						rlen = $.tools.suggest[val][1];
						appendSuggest($.tools.suggest[val][0]);
					}else{
					    $.tools.suggest[val] = ["",0];
						searchurl = url+'?'+$.param(params);
						getResult(searchurl);
					}
				}
				if(!!!val && !hidden){
					hideResult();	/* 如果关键词为空并且层没有隐藏则隐藏提示层 */
				}
			}
 
			/* 获取提示数据 */
			function getResult(searchurl){
				$.ajax({
                                    type: "GET",
                                    url: searchurl,
                                    cache: true,
                                    dataType: "jsonp",
                                    jsonpCallback: "$.fn.suggest.suggetCallback"
                                });
			}
 
			/* 插入提示数据 */
			function appendSuggest(result){
				locksuggest = hidden = false;
				/* 如果检索结果为空,隐藏提示层反之则输出 */
				if(!!result){
					if(!tip){
						litop = self.offset().top+self.outerHeight()-1;
						lileft = self.offset().left;
						liwth = self.outerWidth()-2;
						rebox.css({'position':'absolute','top':litop,'left':lileft,'width':liwth}).html(result).appendTo('body').show();
						tip = true;
					}else{
						rebox.html(result).show();
					}
					rebox.find('li').bind('mouseover',function(){
						locksuggest = true;		/* 锁定提示层 */
						index = $(this).index();
						changeSelect(index,false);
					})
					.bind('click',function(){
						changeSelect(index);
						searchSubmit();
					});
					rebox.bind('mouseout',function(){
						locksuggest = false;	/* 解锁提示层 */
					});
				}else{
					rebox.hide();
				}
			}
 
			/* JSONP 回调函数 */
                        $.fn.suggest.suggetCallback = function(tmp){
				var data = tmp,
			        htmltemp = '',
				htmllen = data.total,
				inputWord = data.kw;
 
				if(htmllen > 0){
				    $.each(data.list,function(i,n){
					    if(n.word != inputWord){
						    htmltemp += '<li data-value="'+n.word+'">'+n.word+'</li>';
					    }
				    });
				    htmltemp = htmltemp.toLocaleLowerCase();
				    var regex = eval("/\>"+inputWord+"/g"); 
				    htmltemp = htmltemp.replace(regex,"><b>"+inputWord+"</b>");
				    $.tools.suggest[inputWord]=[htmltemp,htmllen];
				}
 
				if(self.val() == inputWord){
				    rlen = htmllen;
				    index = -1;
				    appendSuggest(htmltemp);
				}
 
			}
 
			/* 创建队列 */
			function getQueue(){
				var n = $(document).queue("suggest");
				if(n.length < 1){
					$(document).queue("suggest",getQueue);
					getKey();
					timer = setTimeout((function(){
						$(document).dequeue("suggest");
					}),delay);
				}
			}
 
			/* 清除队列 */
			function clearQueue(){
				$(document).clearQueue("suggest");
				clearTimeout(timer);
			}
 
			/* 变更选项 */
			function changeSelect(index,v){
				v=v==false?false:true;
				var obj = rebox.find('li').eq(index);
				rebox.find('li.mo').removeClass('mo');
				obj.addClass("mo");
				if(v){
					choseKey = backval = obj.attr("data-value");
					self.val(choseKey);
				}
			}
 
			/* 重置层 */
			function reSet(){
				if(!!self.val()){
					index = -1;
					$('#suggest').css('display','block');
					rebox.find('li.mo').removeClass('mo');
					rlen = rebox.find('li').size();		/* 根据html结构重新计算提示结果长度 */
				}
			}
 
			/* 隐藏结果层 */
			function hideResult(){
				if(!locksuggest){
					choseKey = backval = null;
					hidden = true;
					rebox.hide();
				}
			}
 
			/*  提交表单 */
			function searchSubmit(){
				self.val(choseKey);
				clearQueue();
				hideResult();
				formobj.submit();
			}
 
		});
	}
})(jQuery);

返回数据格式

$.fn.suggest.suggetCallback({"total":2,"list":[{"id":"8926","word":"ZAMA\u00b7\u54b1\u4eec"},{"id":"44723","word":"ZOBON"}],"kw":"z"})

原文链接(235 views)|评论 (3)

dojo 中对象扩展与原型继承的实现

dojo 是一款优秀的javascript框架,通常我们可以在复杂的企业级应用中看到它的身影。

dojo 提供了两个方法来实现继承 dojo.mixindojo.extend

dojo.mixin 作用在对象上,它负责扩展一个对象
dojo.extend 作用在实例上,他负责扩展实例(本质上是扩展产生实例的类)

而这两个方法的底层实现是dojo._mixin。

因此,我将这一部分抽出,并进行分析。

(function(){
// 创建一个命名空间
var dojo = {};
// 初始化一些变量
var extraNames, extraLen, empty = {};
    // 判断是否为ie浏览器,如果不是则将 dojo._extraNames 设为空数组
    // 如果是则将其设置为["hasOwnProperty", "valueOf", "isPrototypeOf",
    // "propertyIsEnumerable", "toLocaleString", "toString", "constructor"]
    for(var i in {toString: 1}){ extraNames = []; break; }
    dojo._extraNames = extraNames = extraNames || ["hasOwnProperty", "valueOf", "isPrototypeOf",
    "propertyIsEnumerable", "toLocaleString", "toString", "constructor"];
    extraLen = extraNames.length;
 
    dojo._mixin = function(/*Object*/ target, /*Object*/ source){
    var name, s, i;
    // 历遍源对象
    for(name in source){
        // 将源对象中的属性依次赋给s
        s = source[name];
        // 如果目标对象不存在属性name 或者 目标对象中属性name 的值不等于s
        // 同时这个属性不是js Object 对象的保留属性
        if(!(name in target) || (target[name] !== s && (!(name in empty) || empty[name] !== s))){
            // 更新目标对象
            target[name] = s;
        }
    }
 
    // IE doesn't recognize some custom functions in for..in
    // 如果是ie浏览器 --> dojo._extraNames 数组不为0
    if(extraLen && source){
        for(i = 0; i < extraLen; ++i){
            // 调用数组中的那些方法
            name = extraNames[i];
            s = source[name];
            // 用 dojo._extraNames 中的方法检查是否可将源对象扩展到目标对象中
            if(!(name in target) || (target[name] !== s && (!(name in empty) || empty[name] !== s))){
                target[name] = s;
            }
        }
    }
 
    return target; // Object
}
 
dojo.mixin = function(/*Object*/obj, /*Object...*/props){
    // 如果目标为空,初始化一个对象
    if(!obj){ obj = {}; }
    // 调用dojo_mixin() 扩展目标对象
    for(var i=1, l=arguments.length; i<l; i++){
        dojo._mixin(obj, arguments[i]);
    }
    return obj; // Object
}
 
dojo.extend = function(/*Object*/ constructor, /*Object...*/ props){
    for(var i=1, l=arguments.length; i<l; i++){
         // 调用dojo_mixin() 扩展构造函数的原型链
        dojo._mixin(constructor.prototype, arguments[i]);
    }
    return constructor; // Object
}
 
window.dojo = dojo;
})();
 
var obj = {
    age: 24
}
 
// 调用 dojo.mixin 方法扩展obj对象
obj = dojo.mixin(obj,{name:"zhangsan"});
 
alert(obj.name + "'s age is " + obj.age == "zhangsan's age is 24");
 
var User = function (){};
 
var obj = new User();
 
// 调用 dojo.extend 方法扩展 User 对象的原型链
User = dojo.extend(User,{
    saybye: function(){
        return "byebye";
    }
});
 
// obj 对象具备了saybye()的方法
alert(obj.saybye() == "byebye");

原文链接(146 views)|暂无评论(赶紧抢沙发)

对象中的公有方法、私有方法、特权方法

了解如何创建一个对象的时候,我们就需要给这个对象增加方法和属性。

在其他的书籍教程中,我们经常看到各种名称的方法,例如静态方法、实例方法等等,开始我查阅的时候就被这些说法搞的稀里糊涂的,于是自己总结了下,若有错误,还请指出。

1、静态方法和属性
静态方法既只允许父类调用,其他的实例均无法访问和调用的方法和属性。

var user = function(age){
    // 变量age 是user对象的私有属性
    var age = age;
}
 
// user.age 是user对象的静态方法
user.age = function(){
    return this.age;
}
 
// 当我们将user对象实例化的时候
// zhangsan 无法使用 user 对象中
// 的 age方法
var zhangsan = new user(18);
 
// 实例中没有继承user对象的age方法
alert(!!zhangsan.age == false);

2、公共方法
公共方法既依此父类生成的所有实例均可以使用的方法。

var user = function(name,age){
    // 公共变量
    this.name = name;
    this.age = age;
}
 
// 公共方法
user.prototype = {
    getInfo: function(){
        return "他的名字叫:" +this.name+ ",他的年龄是:" +this.age;
    }
}
 
// 当我们将user对象实例化的时候
var zhangsan = new user("zhangsan",18);
 
// getInfo() 就是user对象的公共方法 name\age 既user对象的公共变量
alert(zhangsan.getInfo() == "他的名字叫:zhangsan,他的年龄是:18");
alert(zhangsan.name == "zhangsan");
alert(zhangsan.age == 18);

3、私有方法
私有方法是只有父类可以访问的方法和属性,他与静态方法一致,只是表现形式不一样。构造器中的var变量和参数都是私有方法。

var user = function(age){
    // age 是user对象的私有属性
    var age = age;
 
    // getAget 是user对象的私有方法
    function getAge(){
        return age;
    }
}
 
var zhangsan = new user(18);
 
// 实例中不包含user对象的age属性
// 及getAge方法;
alert(zhangsan.age == undefined);
alert(zhangsan.getAge == undefined);

4、特权方法
当父类中的某一个公共方法可获取父类中的私有方法,同时我们无法修改该方法(可以删除、替换),我们称这种方法为特权方法。
可以访问私有方法,并且在公共域中可见的方法称为特权方法。特权方法不可被修改,只能替换或者删除。

var user = function(age){
    // age 是user对象的私有属性
    var age = age;
 
    // 创建一个特权方法用来获取
    // user 对象下的私有属性age
    this.getInfo = function(){
       return age;
    }
}
 
var zhangsan = new user(18);
alert(zhangsan.getInfo() == 18);

参考资料:http://javascript.crockford.com/private.html

原文链接(109 views)|评论 (2)

jQuery 源码分析(3)

上次我们分析了jQuery对象的构建方法以及如何去扩展jQuery对象的方法extend()。

这次让我们先看看jQuery本身还有那些静态属性和方法实例方法。

需要注意的是,这里定义的所有jQuery的静态属性/方法实例方法也是是jQuery对象的静态属性/方法实例方法。

jQuery.fn = jQuery.prototype = {
	init: function( selector, context ) {
		// code here ...
	},
 
	// 为所有jQuery对象初始化一个空的选择器
	selector: "",
 
	// The current version of jQuery being used
	jquery: "1.4.2",
 
	// 初始化 jQuery 对象长度(jQuery含有多少个DOM对象,长度就是几)
	length: 0,
 
	// 获取jQuery.length属性的方法
	size: function() {
		return this.length;
	},
 
	// 将this对象转为数组
        //注意,这里的slice方法并不是Array.slice(),而是jQuery.slice()
	toArray: function() {
		return slice.call( this, 0 );
	},
 
	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	// 取得匹配DOM对象的集合,或者根据传入的参获取对应的DOM对象
	get: function( num ) {
		return num == null ?
 
			// Return a 'clean' array
			this.toArray() :
 
			// Return just the object
			( num < 0 ? this.slice(num)[ 0 ] : this[ num ] );
	},
 
	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	// 将一个数组或者对象压入栈中,同时返回一个新的对象集合
	// 这是一个非常巧妙的方法,在后面,我们会看到
        // jQuery利用这个方法来筛选、查找上一个,下一个对象
	pushStack: function( elems, name, selector ) {
		// Build a new jQuery matched element set
		// 新建一个jQuery对象
		var ret = jQuery();
 
		// 如果传入的对象是数组
                // 则调用Array.push方法 (jQuery.push只是Array.push的一个引用)
		// 如果是对象,则合并且返回一个新的jQuery对象
		if ( jQuery.isArray( elems ) ) {
			push.apply( ret, elems );
		} else {
			jQuery.merge( ret, elems );
		}
 
		// Add the old object onto the stack (as a reference)
		// 将当前调用的对象(this),作为新jQuery对象的前一个对象
		ret.prevObject = this;
 
		// 将当前调用的对象(this)的文档集、DOM对象集
                // 或者jQuery对象赋给新的jQuery对象
		ret.context = this.context;
 
		if ( name === "find" ) {
                        // 如果name是find方法,构建一个子查询器(Sizzle)
			ret.selector = this.selector + (this.selector ? " " : "") + selector;
		} else if ( name ) {
                        // 如果name是其他方法,则调用其他方法
			ret.selector = this.selector + "." + name + "(" + selector + ")";
		}
 
		// 返回这个新的jQuery对象
		return ret;
	},
 
	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
        // 后面再说each方法
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},
 
        // 判断jQuery是否载入完毕
	ready: function( fn ) {
		// Attach the listeners
		// 调用bindReady方法,建立一个侦听
		jQuery.bindReady();
 
		// If the DOM is already ready
		// 如果DOM已经渲染完成
		if ( jQuery.isReady ) {
			// Execute the function immediately
			// 执行fn,并且将jQuery作为参数传入fn中
			// 实际上,$(function(){...})内的fn是一个存在于window/document对象下匿名函数
			// 传入jQuery后,我们可以将它看作
			// (function (jQuery){ ... })(jQuery);
			fn.call( document, jQuery );
 
		// Otherwise, remember the function for later
		// 如果没有准备好,则将要执行的函数放到readyList数组中暂存
		} else if ( readyList ) {
			// Add the function to the wait list
			readyList.push( fn );
		}
 
		return this;
	},
 
        // 调用jQuery.slice方法取对用dom元素
	eq: function( i ) {
		return i === -1 ?
			this.slice( i ) :
			this.slice( i, +i + 1 );
	},
 
        // 获取对象中的第一个dom元素
	first: function() {
		return this.eq( 0 );
	},
 
        // 获取对象中的最后一个dom元素
	last: function() {
		return this.eq( -1 );
	},
 
        // 将函数实参转为数组
	slice: function() {
		return this.pushStack( slice.apply( this, arguments ),
			"slice", slice.call(arguments).join(",") );
	},
 
        // 将当前jQuery对象中的每一个元素作为参数放入callback中执行,然后返回执行后的新数组
	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function( elem, i ) {
			return callback.call( elem, i, elem );
		}));
	},
 
        // 返回当前调用对象之前的对象,注意pushStack()
        end: function() {
		return this.prevObject || jQuery(null);
	},
 
	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: [].sort,
	splice: [].splice
};

现在jQuery中主要的实例方法已经分析完毕~

同时,最重要的静态方法extend也已经说明

下次将从api入手,依照Core Event Manipulation Ajax这四类来尝试分析

原文链接(595 views)|暂无评论(赶紧抢沙发)

jQuery 源码分析(2)

上文我们提到了jQuery框架中的一个重要功能,扩展方法。

当我们使用new运算法实例化一个对象后,是如何对这个实例的原型再扩展方法的,因为这个实例的原型实际上是他类的原型的引用。(不知道这儿理解的是否正确)

因此当我们试图向实例化对象的原型中增加方法或者属性都是不行的。

function car(){}
 
car.prototype = {
    name: function(name){
        return name;
    }
}
 
var jeep = new car();
jeep.prototype = {
    type: "jeep"
}
 
alert(jeep.name("aodi")); //aodi
alert(jeep.type); //undefined

那么当我们需要扩展jQuery对象或者jQuery中的时候怎么办?

使用 jQuery.fn.extedn()、jQuery.extend()方法。

1、扩展jQuery静态方法

jQuery.extend({
  max: function(a, b) { return a > b ? a : b; }
});
 
jQuery.max(5,4) //5

2、给jQuery对象扩展方法

jQuery.fn.extend({
  max: function(a, b) { return a > b ? a : b; }
});
 
$(elem).max(5,4) //5

3、合并多个对象

var object1 = {
  apple: { value1:1,value2:2}
};
var object2 = {
  apple: { value1:3}
};
$.extend(object1, object2);
 
// object1 === {apple: { value1:3}}

4、深度合并多个对象

var object1 = {
  apple: { value1:1,value2:2}
};
var object2 = {
  apple: { value1:3}
};
$.extend(true, object1, object2);
 
// object1 === {apple: { value1:3,value2:2}}

那么extend方法是如何实现这种扩展的呢?看代码。

jQuery.extend = jQuery.fn.extend = function() {
	// copy reference to target object
	// 在这里我们约定被扩展的对象(object1)叫目标对象,其他被合并的对象(objectN)叫合并对象
	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy;
 
	// 当使用深度扩展的时候
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		i = 2;
	}
 
	// 当目标对象不是一个对象的时候,建立一个空的对象
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}
 
	// 当没有合并对象的时候,则扩展this本身(jQuery,或者jQuery对象)
	if ( length === i ) {
		target = this;
                // 为后面的扩展做处理
		--i;
	}
 
	for ( ; i < length; i++ ) {
		// 当合并对象不为null的情况下,将合并对象赋值给options变量
		if ( (options = arguments[ i ]) != null ) {
			// 遍历合并对象
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];
 
				// 如果两个方法或属性相同,跳出本次循环
				if ( target === copy ) {
					continue;
				}
 
				// 当使用深度扩展并且合并对象中的属性或者方法是一个字面量或数组(真复杂....)
				if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) {
				        // 如果目标对象中存在对应的属性或方法,并且是一个对象字面量或数组,则克隆它
				        // 反之(不存在),则判断合并对象的属性或方法的类型,并依次类型建立对应的对象/数组字面量
					var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src
						: jQuery.isArray(copy) ? [] : {};
 
					// 再扩展这个克隆,并将这个克隆赋给目标对象的属性或者方法中
					target[ name ] = jQuery.extend( deep, clone, copy );
 
				// 当没有使用深度扩展并且合并对象中仍然存在没有合并的方法或属性时
				} else if ( copy !== undefined ) {
				        // 扩展目标对象
					target[ name ] = copy;
				}
			}
		}
	}
 
	// 返回目标对象
	return target;
};

需要注意的是,这是jQuery中的一个静态方法,也是一个实例方法。

jQuery.extend = jQuery.prototype.extend = function() {}

至此,一个面向对象的js框架基础就搭建好了,下一步,我们将向这个对象中扩展各种不同的方法~

下次再说~

原文链接(360 views)|暂无评论(赶紧抢沙发)