第二章 jQuery技术解密 (二)

本文深入剖析jQuery框架的核心概念,包括jQuery对象的性质、迭代器、功能扩展、参数处理及名字空间等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2.2.6 延续 -- 迭代器

在 jQuery 框架中,jQuery 对象是一个很奇怪的概念,具有多重身份,所以很多初学者一听说 jQuery 对象就感觉很是不解,误以为它是 John Resig 制造的新概念。我们可以对jQuery 对象进行如下分解。

第一,jQuery 对象是一个数据集合,它不是一个个体对象。因此,你无法直接使用 JavaScript 的方法来操作它。

第二,jQuery 对象实际上就是一个普通的对象,因为它是通过 new 运算符创建的一个新的实例对象。它可以继承原型方法或属性,同时也拥有 Object 类型的方法和属性。

第三,jQuery 对象包含数组特性,因为它赋值了数组元素,以数组结构存储返回的数据。我们可以以 JavaScript 的概念理解 jQuery 对象,例如下面的示例。

<script type="text/javascript">
	var jquery = {			// 定义对象直接量
		name: "jQuery",		// 以属性方式存储信息
		value: "1.3.2"
	};
	jquery[0] = "jQuery";	// 以数组方式存储信息
	jquery[1] = "1.3.2";	
	alert(jquery.name);		// 返回 "jQuery"
	alert(jquery[0]);		// 返回 "jQuery"
</script>
上面的 jQuery 对象就是一个典型的 jQuery 对象,jQuery 对象的结构就是按这种形式设计的。可以说,jQuery 对象就是对象和数组的混合体,但是它不拥有数组的方法,因为它的数组结构是人为附加的,也就是说它不是 Array 类型数据,而是 Object 类型数据。

第四,jQuery 对象包含的数据都是 DOM 元素,是通过数组形式存储的,即通过 jQuery[n] 形式获取。同时 jQuery 对象又定义了几个模仿 Array 基本特性的属性,如 length 等。

所以,jQuery 对象是不允许直接操作的,只有分别读取它包含的每一个 DOM 元素,才能实现各种操作,如插入、删除、嵌套、赋值和读写 DOM 元素属性等。

那么如何实现直接操作 jQuery 对象中的 DOM 元素呢?

在实际应用中,我们可以看到类似下面的 jQuery 用法。

$("div").html()

也就是直接在 jQuery 对象上调用 html(),并实现操作 jQuery 包含的所有 DOM 元素。那么这个功能是怎么实现的呢?

jQuery 定义了一个工具函数 each(),利用这个工具可以遍历 jQuery 对象中所有的 DOM 元素,并把需要操作的内容封装到一个回调函数中,然后通过在每个 DOM 元素上调用这个回调函数即可。实现代码如下所示,演示效果如图 2.2 所示。

<script type="text/javascript">
var $ = jQuery = function(selector, context){			// 定义类      
	return new jQuery.fn.init(selector, context);		// 返回选择器的实例
};

jQuery.fn = jQuery.prototype = {				// jQuery 类的原型对象
	init: function(selector, context){			// 定义选择器构造器
		selector = selector || document;		// 设置默认值为 document
		context = context || document;			// 设置默认值为 document
		
		if(selector.nodeType){					// 如果选择符为节点对象
			this[0] = selector;					// 把参数节点传递给实例对象的数组
			this.length = 1;					// 并设置实例对象的 length 属性,定义包含的元素个数
			this.context = selector;			// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
		if(typeof selector === "string"){		// 如果选择符是字符串
			var e = context.getElementsByTagName(selector);	// 获取指定名称的元素
			for(var i = 0; i<e.length; i++){	// 遍历元素集合,并把所有元素填入到当前实例数组中
				this[i] = e[i];
			}
			this.length = e.length;				// 设置实例的 length 属性,即定义包含的元素个数
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		} else {
			this.length = 0;					// 否则,设置实例的 length 属性值为 0
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
	},
	html: function(val){		// 模仿 jQuery 框架中的 html() 方法,为匹配的每一个DOM元素插入html代码
		jQuery.each(this, function(val){	// 调用 jQuery.each() 工具函数,为每一个 DOM 元素执行回调函数
			this.innerHTML = val;
		}, val);
	},
	jquery: "1.3.2",          		// 原型属性
	size: function(){ 				// 原型方法
		return this.length;
	}
};

jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象
// 扩展 jQuery 工具函数
jQuery.each = function(object, callback, args){
	for(var i=0; i<object.length; i++){
		callback.call(object[i], args);
	}
	return object;
};

$("div").html("测试代码");
</script>
在上面的示例中,通过先为自己的 jQuery 对象绑定 html() 方法,然后利用 jQuery() 选择器获取页面中所有的 div 元素,再调用 html() 方法,为所有匹配的元素插入 HTML 源码。

注意,在上面的代码中,each() 函数的当前作用对象是 jQuery 对象,故 this 指向当前 jQuery 对象,即 this 表示一个集合对象;而在 html() 方法中,由于 each() 函数是在指定 DOM 元素上执行的,所以该函数内的 this 指针指向的是当前 DOM 元素对象,即 this 表示一个元素。


2.2.7 延续 -- 功能扩展

根据一般设计习惯,如果要为 jQuery 或者 jQuery.prototype 添加函数或方法,可以直接通过点语法实现,或者在 jQuery.prototype 对象结构中增加一个属性即可。但是,如果分析 jQuery 框架的源代码,你会发现它是通过 extend() 函数来实现功能扩展的。例如,下面两段代码都是 jQuery 框架通过 extend() 函数来扩展功能的。

jQuery.extend({          // 扩展工具函数

noConflict: function(deep){},

isFunction: function(obj){},

isArray: function(obj){},

isXMLDoc: function(elem){},

globalEval: function(data){}

});

或者

jQuery.fn.extend({  // 扩展 jQuery 对象方法

show: function(speed, callback){},

hide: function(speed, callback){},

toggle: function(fn, fn2){},

fadeTo: function(speed, to, callback){},

animate: function(prop, speed, easing, callback){},

stop: function(clearQueue, gotoEnd){}

});

这样做的好处是什么呢?

extend() 函数能够方便用户快速扩展 jQuery 框架的功能,但是不会破坏 jQuery 框架的原型结构,从而避免后期人工手动添加工具函数或者方法破坏 jQuery 框架的单纯性,同时也方便管理。如果不需要某个插件,只需要简单地删除即可,而不需要在 jQuery 框架源代码中去筛选和删除。

extend() 函数的功能实现起来也很简单,它只是把指定对象的方法复制给 jQuery 对象或者 jQuery.prototype 对象。例如,在下面的示例中,我们为 jQuery 类和原型定义了一个扩展功能的函数 extend() ,该函数的功能很简单,它能够把指定参数对象包含的所有属性复制给 jQuery 或者 jQuery.prototype 对象,这样就可以在应用中随时调用它,并动态扩展 jQuery 对象的方法。

<div></div>
<div></div>
<div></div>
<script type="text/javascript">
var $ = jQuery = function(selector, context){			// 定义类      
	return new jQuery.fn.init(selector, context);		// 返回选择器的实例
};

jQuery.fn = jQuery.prototype = {				// jQuery 类的原型对象
	init: function(selector, context){			// 定义选择器构造器
		selector = selector || document;		// 设置默认值为 document
		context = context || document;			// 设置默认值为 document
		
		if(selector.nodeType){					// 如果选择符为节点对象
			this[0] = selector;					// 把参数节点传递给实例对象的数组
			this.length = 1;					// 并设置实例对象的 length 属性,定义包含的元素个数
			this.context = selector;			// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
		if(typeof selector === "string"){		// 如果选择符是字符串
			var e = context.getElementsByTagName(selector);	// 获取指定名称的元素
			for(var i = 0; i<e.length; i++){	// 遍历元素集合,并把所有元素填入到当前实例数组中
				this[i] = e[i];
			}
			this.length = e.length;				// 设置实例的 length 属性,即定义包含的元素个数
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		} else {
			this.length = 0;					// 否则,设置实例的 length 属性值为 0
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
	},
	jquery: "1.3.2",          		// 原型属性
	size: function(){ 				// 原型方法
		return this.length;
	}
};

jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象
// jQuery 功能扩展函数
jQuery.extend = jQuery.fn.extend = function(obj){
	for(var prop in obj){
		this[prop] = obj[prop];
	}
	return this;
};
// 扩展 jQuery 对象方法
jQuery.fn.extend({
	test: function(){
		alert("测试扩展功能");
	}
});
// 测试代码
$("div").test();
</script>

在上面的示例中,先定义了一个功能扩展函数 extend(),然后为 jQuery.fn 原型对象调用 extend() 函数,为其添加一个测试方法 test()。这样就可以在实践中应用,如 $("div").test() 。

jQuery 框架定义的 extend() 函数的功能要强大很多,它不仅能够完成基本的功能扩展,还可以实现对象合并等功能。

2.2.8 延续 -- 参数处理

在很多时候,你会发现 jQuery 的方法都要求传递的参数为对象结构,例如:

$.ajax({

type: "GET",

url: "test.js",

dataType: "script"

});

使用对象直接量作为参数进行传递,方便参数管理。当方法或者函数的参数长度不固定时,使用对象直接量作为参数存在很多优势。例如,对于下面的用法,ajax()函数就需要进行更加复杂的参数排查和过滤。

$.ajax("GET", "test.js", "script");

如果 ajax() 函数的参数长度是固定的,且是必须的,那么通过这种方式进行传递也就无所谓了,但是如果参数的个数和排序是动态的,那么使用 $.ajax("GET", "test.js", "script"); 这种方法是无法处理的。而 jQuery 框架的很多方法都包含大量的参数,且都是可选的,位置也没有固定要求,所以使用对象直接量是惟一的解决方法。

使用对象直接量作为参数传递的载体,这里就涉及参数处理问题。如何解析并提出参数?如何处理参数和默认值?我们可以通过下面的方法来实现。

<script type="text/javascript">
var $ = jQuery = function(selector, context){			// 定义类      
	return new jQuery.fn.init(selector, context);		// 返回选择器的实例
};

jQuery.fn = jQuery.prototype = {				// jQuery 类的原型对象
	init: function(selector, context){			// 定义选择器构造器
		selector = selector || document;		// 设置默认值为 document
		context = context || document;			// 设置默认值为 document
		
		if(selector.nodeType){					// 如果选择符为节点对象
			this[0] = selector;					// 把参数节点传递给实例对象的数组
			this.length = 1;					// 并设置实例对象的 length 属性,定义包含的元素个数
			this.context = selector;			// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
		if(typeof selector === "string"){		// 如果选择符是字符串
			var e = context.getElementsByTagName(selector);	// 获取指定名称的元素
			for(var i = 0; i<e.length; i++){	// 遍历元素集合,并把所有元素填入到当前实例数组中
				this[i] = e[i];
			}
			this.length = e.length;				// 设置实例的 length 属性,即定义包含的元素个数
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		} else {
			this.length = 0;					// 否则,设置实例的 length 属性值为 0
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
	},
	setOptions: function(options){
		this.options = {	// 方法的默认值,可以扩展
			StartColor: "#000",
			EndColor: "#DDC",
			Step: 20,
			Speed: 10	
		};
		jQuery.extend(this.options, options || {});	// 如果传递参数,则覆盖原默认参数	
	},
	jquery: "1.3.2",          		// 原型属性
	size: function(){ 				// 原型方法
		return this.length;
	}
};

jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象

jQuery.extend = jQuery.fn.extend = function(destination, source){	// 重新定义 extend() 函数
	for (var property in source){
		destination[property] = source[property];
	}
	return destination;
};
</script>

在上面的示例中,定义了一个原型方法 setOptions(),该方法能够对传递的参数对象进行处理,并覆盖默认值。这种用法在本书插件部分还将进行讲解。

在 jQuery 框架中, extend() 函数包含了所有功能,它既能够为当前对象扩展方法,也能够处理参数对象,并覆盖默认值。

2.2.9 涅槃 -- 名字空间

现在,我们终于模拟出了 jQuery 框架的雏形,虽然它还比较稚嫩,经不起风雨,但至少能够保证读者理解 jQuery 框架构成的初期状态。不过对于一个成熟的框架来说,需要设计者考虑的问题还是很多的,其中最核心的问题就是名字空间冲突问题。

当一个页面中存在多个框架,或者自己写了很多 JavaScript 代码,我们是很难确保这些代码不发生冲突的,因为任何人都无法确保自己非常熟悉 jQuery 框架中的每一行代码,所以难免会出现名字冲突,或者功能覆盖现象。为了解决这个问题,我们必须把 jQuery 封装在一个孤立的环境中,避免其他代码的干扰。

在详细讲解名字空间之前,我们先来温习两个 JavaScript 概念。首先,请看下面的代码。

var jQuery = function(){};

jQuery = function(){};

上面所示的代码是两种不同的写法,且都是合法的,但是它们的语义完全不同。第一行代码声明了一个变量,而第二行代码定义了 Window 对象的一个属性,也就是说它等同于下面的语句。

window.jQuery = function();

在全局作用域中,变量和 Window 对象的属性是可以相等的,也可以是互通的,但是当在其他环境中 (如局部作用域中),则它们是不相等的,也是无法互通的。

因此如果希望 jQuery 具有类似 $.method(); 调用方式的能力,就需要将 jQuery 设置为 Window 对象的一个属性,所以你就会看到 jQuery 框架中是这样定义的。

<script type="text/javascript">
	var jQuery = window.jQuery = window.$ = function(selector, context){
		return new jQuery.fn.init(selector, context);
	};
</script>
你可能看到过下面的函数用法。

(function(){

alert("观察我什么时候出现");

})();

这是一个典型的匿名函数基本形式。为什么要用到匿名函数呢?

这时就要进入正题了,如果希望自己的 jQuery 框架与其他任何代码完全隔离开来,也就是说如果你想把 jQuery 装在一个封闭空间中,不希望暴露内部信息,也不希望别的代码随意访问,匿名函数就是一种最好的封闭方式。此时我们只需要提供接口,就可以方便地与外界进行联系。例如,在下面的示例中分别把 f1 函数放在一个匿名函数中,而把 f2 函数放在全局作用域中。可以发现,全局作用域中的 f2 函数可以允许访问,而匿名函数中的 f1 函数是禁止外界访问的。

<script type="text/javascript">
	(function(){
		function f1(){
			return "f1()";
		}
	})();
	
	function f2(){
		return "f2()";
	}
	
	alert(f2()); 	// 返回 "f2()"
	alert(f1());	// 抛出异常,禁止访问
</script>
实际上,上面的匿名函数就是所谓的闭包,闭包是 JavaScript 函数中一个最核心的概念。

当然,$ 和 jQuery 名字并非是 jQuery 框架的专利,其他一些经典框架中也会用到 $ 名字,也许读者也会定义自己的变量 jQuery 。

在这之前我们需要让它与其他框架协同工作,这就带来一个问题,如果我们都使用 $ 作为简写形式就会发生冲突,为此 jQuery 提供了一个 noConflit() 方法,该方法能够实现禁止 jQuery 框架使用这两个名字。为了实现这样的目的,jQuery 在框架的最前面,先使用 _$ 和 _jQuery 临时变量寄存 $ 和 jQuery 这两个变量的内容,当需要禁用 jQuery 框架的名字时,可以使用一个临时变量 _$ 和 _jQuery 恢复 $ 和 jQuery 这两个变量的实际内容。实现代码如下。

<script type="text/javascript">
(function(){	
var 
	window = this,
	undefined,
	_jQuery = window.jQuery,	// 暂存 jQuery 变量内容
	_$ = window.$,				// 暂存 $ 变量内容
	jQuery = window.jQuery = window.$ = function(selector, context){
		return new jQuery.fn.init(selector, context);
	},
	quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
	isSimple = /^.[^:#\[\.,]*$/;
	
jQuery.fn = jQuery.prototype = {
	init: function(selector, context){			// 定义选择器构造器
		selector = selector || document;		// 设置默认值为 document
		context = context || document;			// 设置默认值为 document
		
		if(selector.nodeType){					// 如果选择符为节点对象
			this[0] = selector;					// 把参数节点传递给实例对象的数组
			this.length = 1;					// 并设置实例对象的 length 属性,定义包含的元素个数
			this.context = selector;			// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
		if(typeof selector === "string"){		// 如果选择符是字符串
			var e = context.getElementsByTagName(selector);	// 获取指定名称的元素
			for(var i = 0; i<e.length; i++){	// 遍历元素集合,并把所有元素填入到当前实例数组中
				this[i] = e[i];
			}
			this.length = e.length;				// 设置实例的 length 属性,即定义包含的元素个数
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		} else {
			this.length = 0;					// 否则,设置实例的 length 属性值为 0
			this.context = context;				// 设置实例的属性,返回选择范围
			return this;						// 返回当前实例
		}
	},
	setOptions: function(options){
		this.options = {	// 方法的默认值,可以扩展
			StartColor: "#000",
			EndColor: "#DDC",
			Step: 20,
			Speed: 10	
		};
		jQuery.extend(this.options, options || {});	// 如果传递参数,则覆盖原默认参数	
	},
	jquery: "1.3.2",          		// 原型属性
	size: function(){ 				// 原型方法
		return this.length;
	}
};

jQuery.fn.init.prototype = jQuery.fn; // 使用 jQuery 的原型对象覆盖 init 的原型对象

jQuery.extend = jQuery.fn.extend = function(destination, source){	// 重新定义 extend() 函数
	for (var property in source){
		destination[property] = source[property];
	}
	return destination;
};
})();
</script>
至此,jQuery 框架的设计模式就初见端倪了,后面的工作就是根据应用需要或者功能需要,使用 extend() 函数不断扩展 jQuery 的工具函数和 jQuery 对象的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值