jquery源码猜想(一)
本文是对jquery源码的一个小小的探索,适合刚入门有一点基础的朋友看一看提供思路。jquery本身高效率的API操作可以让工作事半功倍,他的特点是链式操作以及便利的选择器。那么本文就来用js来试探性的猜测一下这些操作是如何实现的:
思路1:
用过jq的人都知道它独特的$操作符;这个操作符有两个作用:
功能1:$(funciton(){})也就是执行一段函数,相当于window.onload
功能2:$(“selector”)选择一个标签;
那么如何实现以上两个功能呢?我们贴上一段代码:
var $=function(arg){
if(typeof arg==="function"){
window.onload=arg;
}
else if(typeof arg==="string"){
return new Proxy(arg);
}
}
我们把$当成一个函数名,判断参数类型即可,那么功能1完成了,功能2如何解决呢?可以看到代码中返回了一个新的构造函数实例,如果你看过《javascript高级程序设计》,就一定知道其中组合使用构造函数模式和原型模式是很常用的一种编程思想,这里就用到了这个思想,贴上一段代码我们看一下这个构造函数proxy:
function Proxy(selector){ this.doms=document.querySelectorAll(selector); } Proxy.prototype={ css:function(key,value){ for(var i=0;i<this.doms.length;i++){ this.doms[i].style[key]=value } return this; }, attr:function(key,value){ for(var i=0;i<this.doms.length;i++){ this.doms[i].setAttribute(key,value) } return this; }, size:function(){ return this.doms.length; } }
可以看到构造函数首先是定义了一个属性this.doms=document.querySelectorAll(selector),这里用到了querySelectorAll,是jquery选择器的核心内容,this.doms就得到了这个选择器选择的所有元素;(这里的this指向每个实例);
接下来看下面用到了原型prototype;熟悉原型就知道所有实例会继承这个prototype的所有方法(具体的细节在高程上有讲),我们得到需要选择的元素之后就要开始操作元素,prototype中的方法就是我们的操作元素的方法,操作谁呢?毫无疑问一定是我们选择的元素存储到的位置:this.doms;这里可以看到我们实现了css,attr,size三个方法;
细心的朋友可以看到每个方法最后都有一个return this,即返回所操作的实例对象;那么就可以接着执行方法,这就是链式操作的实现方法。例如:
$(“selector”).css(key,value).attr(key,value)
但是这个方法有两个缺陷:
缺陷1.这个$(“selector”)选择器选择出来的应该是一个元素数组,但这里显然不是。
缺陷2.这个模拟的的jquery并没有封装,可能会引起全局变量的污染
思路2:
我们依次解决上面的两个缺陷:
解决缺陷1:.把构造函数改成普通函数,并且这个普通函数会返回一个对象,这个对象就是我们选择器选择的元素们,如下:
step1.返回函数
1 var $=function(arg){ 2 if(typeof arg==="function"){ 3 window.onload=arg; 4 } 5 else if(typeof arg==="string"){ 6 return proxy(arg); 7 } 8 } 9 return $
step2.函数会返回一个doms对象。中间用for循环遍历所有_prototype_对象,并且复制给doms。这样一来doms就拥有_prototype_的所有方法:
function proxy(selector){
var doms=document.querySelectorAll(selector);
for(var attr in _prototype_){
doms[attr]=_prototype_[attr]
}
return doms
}
step3._prototype_对象(注意之前的this.doms现在都改成了this)
1 var _prototype_={ 2 css:function(key,value){ 3 for(var i=0;i<this.length;i++){ 4 this[i].style[key]=value 5 } 6 return this; 7 }, 8 attr:function(key,value){ 9 for(var i=0;i<this.length;i++){ 10 this[i].setAttribute(key,value) 11 } 12 return this; 13 }, 14 size:function(){ 15 return this.length; 16 } 17 }
解决缺陷2:
封装函数就好了,注意这里用了一个匿名函数将所有代码包裹起来并且立刻执行,最后一行return $形成闭包,让函数外面可以读取到函数内部的数据。注意最后一定要将这个匿名函数附给变量$,这样才可以找到这个匿名函数。
var $=(function(){ function proxy(selector){ var doms=document.querySelectorAll(selector); for(var attr in _prototype_){ doms[attr]=_prototype_[attr] } return doms } var _prototype_={ css:function(key,value){ for(var i=0;i<this.length;i++){ this[i].style[key]=value } return this; }, attr:function(key,value){ for(var i=0;i<this.length;i++){ this[i].setAttribute(key,value) } return this; }, size:function(){ return this.length; } } var $=function(arg){ if(typeof arg==="function"){ window.onload=arg; } else if(typeof arg==="string"){ return proxy(arg); } } return $ })()
但是问题又出现了:
1.我们在step2中用了for循环复制_prototype_中的每一个方法,这会极大的消耗性能
过程3:
解决缺陷1:要解决这个缺陷,一定要了解选择器选择的元素根本。它实际是继承自NodeList这个元素,不信的话我们用jq输出一下所选择的元素 ,最下面有一个_proto_:NodeList。就是说所选择的元素实际上是NodeList构造函数的实例,那么我们就可以利用让NodeList复制_prototype_中的方法即可解决上面的缺陷。
js中有一切皆为对象的说法,也就是说NodeLits的构造函数也是object。我们这里需要一个复制方法extend,所以我们给object写一个extend方法以便于我们复制NodeList。如下:
Object.prototype.extend=function(source,target){
for(var prop in target){
source[prop]=target[prop]
}
return source;
}
$.fn=Object.extend(NodeList.prototype,_prototype_);
现在我们的所有NodeList都有_prototype_中的方法了,也就是说我们所有选择的元素都有_prototype_中的一切方法。需要注意的是这里面的顺序问题,读者可以自行体会。最后附上这个模拟jq的最终形var $=(function(){
function proxy(selector){ var doms=document.querySelectorAll(selector); /*console.log(doms)*/ /*for(var attr in _prototype_){ doms[attr]=_prototype_[attr] }*/ return doms } var $=function(arg){ if(typeof arg==="function"){ window.onload=arg; } else if(typeof arg==="string"){ return proxy(arg); } } var _prototype_={ css:function(key,value){ for(var i=0;i<this.length;i++){ this[i].style[key]=value } return this; }, attr:function(key,value){ for(var i=0;i<this.length;i++){ this[i].setAttribute(key,value) } return this; }, size:function(){ return this.length; } } Object.prototype.extend=function(source,target){ for(var prop in target){ source[prop]=target[prop] }
//用于增加$fn方法 return source; }
$.fn=Object.extend(NodeList.prototype,_prototype_); return $ })()