用过JQuery的同学们肯定知道$这个函数了,也肯定知道JQ中方法的链式调用的强大,其实链式调用只不过是一种语法招数,能让你通过重用一个初始操作来达到用少量代码表达复杂操作的目的。这种技术包含两个部分:一个创建代码HTML元素对象的工厂,以及一批对这个HTML元素执行某些操作的方法。每一个这种方法都可以在方法名前附上一个圆点后加入调用连中。方法的链式调用可以被视为选择一个或一批DOM元素对其进行一个或多个操作的过程。下面我们将简单的模拟下$的实现,并详细解析链式调用的原理。
$函数通常返回一个HTML元素或一个HTML元素的集合,我们这里先简单实现一个$函数,能够支持基本的CSS选择符(#id, .class tag),也能够将传入的DOM标签元素包装起来返回,同时根据传入的参数个数选择符合参数的元素。但未支持如:“p a”这种形式的CSS嵌套选择。
/* * @param {Object} name * 需要查找的类名 * @param {Object} type * 需要匹配的元素,默认为全部 * 返回符合匹配的元素集合 */ var getElementsByClassName = function(name, type){ var ret = [], elems = document.getElementsByTagName(type || '*'), reg = new RegExp("(^|\\s)" + name + "($|\\s)"); for(var i = 0, len = elems.length; i < len; ++i){ if(reg.test(elems[i].className)) { ret.push(elems[i]); } } return ret; }; /* * 简单的元素选择器 * */ var $ = function(){ var elems = []; for (var i = 0, len = arguments.length; i < len; ++i) { if ('string' === typeof arguments[i]) { //传入字符串 if ('#' === arguments[i].charAt(0)) { //传入ID var elem = doucment.getElementById(arguments[i].substr(1)); if (elem) { elems.push(elem); } } else if ('.' === arguments[i].charAt(0)) {//传入class elems = elems.concat(getElementsByClassName(arguments[i].substr(1))); } else { //传入标签 var tagElems = document.getElementsByTagName(arguments[i]); for (var j = 0, tagLen = tagElems.length; j < tagLen; ++j) { elems.push(tagElems[j]); } } } else if (1 === arguments.nodeType) { //传入标签元素 elems.push(arguments[i]); } } return elems; };
如果把这个函数改造为一个构造器,把那些元素集合保存在一个实例属性中,并让所有定义在构造器函数的prototype属性所指对象中的方法都返回调用方法的那个实例的引用(this),那么它就具有了进行链式调用的能力。我们首先需要把$函数改造成负责创建支持链式调用的对象,同时利用闭包将构造器定义为私有函数。修改后的代码如下:
//自运行函数,用于创建$方法,从而支持链式调用 (function(){ /*私有构造器,用于包装集合元素*/ function _$(els){ this.elems = []; for (var i = 0, len = els.length; i < len; ++i) { if ('string' === typeof els[i]) { //传入字符串 if ('#' === els[i].charAt(0)) { //传入ID var elem = doucment.getElementById(els[i].substr(1)); if (elem) { this.elems.push(elem); } } else if ('.' === els[i].charAt(0)) {//传入class this.elems = this.elems.concat(getElementsByClassName(els[i].substr(1))); } else { //传入标签 var tagElems = document.getElementsByTagName(els[i]); for(var j = 0, tagLen = tagElems.length; j < tagLen; ++j){ this.elems.push(tagElems[j]); } } } else if(1 === els.nodeType){ //传入标签元素 elems.push(els[i]); } } } //公共接口$ window.$ = function(){ return new _$(arguments); }; })();
_$.prototype = { each:function(fn, scope){ for(var i = 0, len = this.elems.length; i < len; ++i){ fn.call(scope||this, this.elems[i]); } return this; }, setStyle:function(prop, value){ this.each(function(elem){ elem.style[prop] = value; }); return this; }, show:function(){ this.each(function(elem){ elem.style.display = 'block'; }); return this; } };