我们现在已经具备了足够的知识,去完成一个比较有挑战性的任务——构造一个简化版的jQuery库——myQuery。

我选择去构造myQuery的动机是:

1 jQuery足够优秀,是模块封装的典范;

2 如果我们能够实现一个良好的jQuery模块,那么就意味着我们能够应付绝大多数的模块封装问题。

 

如果读者读过jQuery源码,本文依然能够为你带来一些价值,因为在本文中封装myQuery所使用的方法是与jQuery完全不同的,而且还实现了jQuery所没有做到的底层数据的私有性。

 

jQuery最大好处在于免除了开发人员学习那糟糕的DOM API。另外从编码的角度去看,jQuery的选择器和链式调用等,大大简化了代码。

为了实现以上的优点,jQuery将DOM元素和相应DOM操作封装成一个jQuery对象,使得我们能够舍弃糟糕的DOM API,而去拥抱简单易用的jQuery API

 

jQuery还有一个重要的特点,它是可以被动态扩展的,我们可以简单地添加自己的jQuery API/插件。为实现这一点,jQuery的数据和方法是“分开”定义的,方法的集合对象暴漏出去,让开发人员有机会往里面加自定义的内容。分开的数据和方法,最后以某种方式结合起来,统一为一个jQuery对象。方法可以不和数据耦合在一起,“分开”定义,听起来很不可思议,但是javascript利用this延迟绑定就能够做到这一点。我们可以在不同的地方分别定义数据和方法,然后可以利用apply/call/bind/原型链



我们的myQuery将着眼于如何封装实现上面提及到jQuery的重要特点,需求如下:

1. selector支持#id和tagName

2. 支持text(),text(str),each(function(index,dom)), size()方法

3. 支持链式调用

4. 支持方法的扩展

尽管需求很少,但是在这些需求实现的背后,往往是我们封装模块的时候最可能遇到的问题,而且在这个简化版jQuery的基础上,完全可以扩展为一个完整的jQuery。 

 

目录:

私有模式实现myQuery

委托模式实现myQuery


私有模式实现myQuery

 

myQuery架构思想:

1. 底层数据:使用一个[],存放选择器选择出来的dom元素;

2. 对外方法:使用一个{},存放所有公用方法。

3. 关联数据与方法:调用$("xxx")时,dom元素数组和公用方法object关联起来构成一个myQuery对象。




对看过jQuery源码的同学说的话:jQuery底层数据其实是一个由dom元素构成的“类数组”,所有的方法都是操作这个类数组。但是jQuery的类数组不是私有的,可以被直接修改,例如$("div")[0] = document.getElementById("xxx")。我们可以使用前两章学过的闭包克服这个缺点,将dom数组隐藏起来,使外界完全不可能直接修改dom数组,仅仅能修改dom数组的成员,就是说类数组[div1,div2]中的div1、div2的属性可以改变,但是[div1,div2]不能变成[div1,div3]。


方法如下:


<!DOCTYPE HTML>
<HTML>
<HEAD>
<TITLE>闭包实现myQuery</TITLE>
</HEAD>

<BODY>
<div id="lazy2009">hello,lazy2009!</div>
<div id="lazy_">hello,lazy_!</div>

<script>
	if (!Function.prototype.bind) {
		Function.prototype.bind = function(context) {
			var args = Array.prototype.slice.call(arguments, 1);
			var that = this;
			return function() {
				return that.apply(context, args.concat(Array.prototype.slice.call(arguments)));
			}
		}
	}

	(function(win) {
		var slice = Array.prototype.slice;

		//使用闭包实现的类似jQuery的封装
		//selector只支持#id和tagName两种选择器,例如$("#id"),$("div")
		var myQuery = function(selector) {
			//私有成员,不让外界直接修改
			var arrDom = [];
			//调用document.getElementById或者document.getElementsByTagName获取元素集合
			if (selector.charAt(0) === '#') {
				arrDom[0] = document.getElementById(selector.substring(1));
			} else {
				var elements = document.getElementsByTagName(selector);
				for ( var i = 0; i < elements.length; i++) {
					arrDom[i] = elements[i];
				}
			}
			var myQueryObj = {};
			//导入myQuery对象公用方法
			var methods = myQuery.methods;
			for ( var methodName in methods) {
				myQueryObj[methodName] = methods[methodName].bind(myQueryObj,
						arrDom);
			}
			return myQueryObj;
		};
		//myQuery导出到window全局作用域
		win.$ = win.myQuery = myQuery;

		//myQuery对象公用方法
		myQuery.methods = {
			version : function() {
				return "1.0";
			},
			text : function(arrDom, s) {
				if (!s) {
					return arrDom[0].innerText;
				} else {
					for ( var i = 0; i < arrDom.length; i++) {
						arrDom[i].innerText = s;
					}
				}
				return this;
			},
			each : function(arrDom, fn) {
				for ( var i = 0; i < arrDom.length; i++) {
					if (false === fn.call(arrDom[i], i, arrDom[i])) {
						break;
					}
				}
				return this;
			},
			get : function(arrDom, index) {
				if (!index) {
					return slice.call(arrDom);
				} else {
					return arrDom[i];
				}
			},
			size : function(arrDom) {
				return arrDom.length;
			}
		};
	})(window);
</script>
<script>
	//封装DOM API
	alert($("#lazy2009").text());
	alert($("#lazy_").text());
	//链式代码
	$("div").text("hello, world").each(function(i, o) {
		alert(o.innerText + "==" + this.innerText);
	});
	alert($("div").size());
	//扩展方法
	if (!myQuery.methods.toArray) {
		myQuery.methods.toArray = function(arrDom) {
			return arrDom.slice(0);
		}
	}
	alert($("div").toArray());
</script>
</BODY>
</HTML>



下面重点解析代码的几个地方


1. 防止过多的全局变量



(function(win){...})(window) :杜绝全局变量污染 

 

      win.$ = win.myQuery = myQuery : 仅仅把$、myQuery这两个元素导出到全局作用域,外部可以通过$.methods或者myQuery.methods扩展myQuery。