JQuery是如此的强大,所以我决定模仿jQuery造一个轮子,边造轮子边学习jQuery是如何利用各种技巧实现那些非常强大的功能的。既然是模仿jQuery,我决定将新的框架命名为jqc,jQuery copy之意。那么接下来让我们从零实现一个框架的雏形吧,如有谬误有劳告知。
沙箱模式
在一切的开始,我们需要定义一个沙箱来将我们的所有代码放在里面,只留部分接口供外部调用。在沙箱内的所有变量都属于局部变量,不会污染全局变量环境。
(function(window,undefined){
var jqc = function(selector){
//.....
}
//.......
window.jqc = jqc;
})(window)
在该自调用函数中有两个形参window和undefined,并传入一个window当做实参。此时在内部使用window对象时直接在函数内部就能查询到window对象,不需要在作用域链中一层一层的往上查找。而由于没有传入第二个参数,所以在函数内使用undefined变量时,值为undefined。这是为了防止低版本浏览器没有undefined关键字,在使用undefined时会因为没有undefined变量而导致浏览器报错。
在最后,将jqc挂在window对象上,对外提供一个接口来操作,而其他变量将被隐藏保护起来。
构造函数
之后,我们需要一个构造函数,用来返回对象。在jQuery中,new jQuery()与jQuery等价。是因为使用了构造函数调用模式的特性,当使用new关键字声明的函数return的是对象时,返回的是return对象。
(function(window,undefined){
var jqc = function(selector) {
return new F(selector);
}
var F = function(selector){};
//一些工具方法可以直接挂在jqc对象上面
jqc.push = function(){};
jqc.each = function(){};
jqc.isString = function(){};
jqc.isDom = function(){};
//.....
//jqc对象继承的方法添加到F原型对象上
F.prototype = {
constructor:F,
appendTo:function(){},
push:function(){},
each:function(){}
//.....
}
window.jqc = jqc;
})(window)
也就是说在以上代码中,new jqc()和jqc()是等价的。此时和jQuery的效果大致相同了,但是有一点需要注意的是,现在暴露在外的接口仅仅是jqc,而最重要的构造函数F没有对外公开,也就是说外界无法修改F的原型对象来为jqc对象添加新的方法,也就无法为jqc写插件。
在jQuery中,将构造函数与jQuery函数联系起来,仅仅需要暴露jQuery就可以对构造函数进行扩展,也就是为jQuery写插件。
var jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context, rootjQuery);
}
jQuery.fn = jQuery.prototype = {
init:function(selector , context, rootjQuery){
//........
}
}
jQuery.fn.init.prototype = jQuery.fn;
可以看到,在jQuery中,调用jQuery函数将实例化一个init构造函数返回,这里的init就相当于上面我写的F构造函数。jQuery将init构造函数放入jQuery函数的原型中,并且将jQuery的原型对象赋值给init原型对象,这样修改jQuery的原型对象就相当于修改构造函数init的原型对象。所以就可以实现只提供一个接口的前提下对jQuery的原型进行扩展,也就是可以为jQuery实现插件。
而且,源码里还为jQuery函数的原型对象提供了一个简写fn,这样访问原型对象时就不需要写"prototype"这么长的单词,仅仅使用"fn"就可以了。
(function(window,undefined){
var jqc = function(selector) {
return new jqc.fn.init(selector);
}
jqc.fn = jqc.prototype = {
constructor:jqc,
init:function(){},
//实例可继承的方法在这里添加
appendTo:function(){},
push:function(){},
each:function(){}
}
//为jqc添加方法则可以直接jqc.XXX = function(){}
jqc.push = function(){};
jqc.each = function(){};
jqc.isLikeArr = function(){};
jqc.isString = function(){};
jqc.isFunc = function(){};
jqc.isDom = function(){};
jqc.fn.init.prototype = jqc.fn;
window.jqc = jqc;
})(window)
修改代码如上后,jqc框架的骨架大致成型。接下来就是不断的添加方法了。但是还缺了点东西,看注释,如果不断的在原型对象里添加方法则会显得十分臃肿,各种功能的方法堆积在一起。为jqc添加方法也是一样,不断的重复写jqc.XXX = function(){}。所以我们需要一个让所有方法分组的办法。
扩展与分模块
来让我们看看jQuery是如何解决上面的问题吧
jQuery.extend = jQuery.fn.extend = function() {
//实现继承的方法
}
在jQuery中为jQuery函数以及jQuery的原型对象实现了一个名为extend的方法,该该方法的作用是将传入的对象所有属性赋值给this。由于extend的算法特别庞大,让我们来实现一个简单的extend方法。
jqc.extend = jqc.fn.extend = function(obj){
var k;
for( k in obj){
this[k] = obj[k];
}
}
这样我们就能通过调用jqc.extend为jqc添加新的属性。通过jqc.fn.extend为jqc的原型对象添加方法。现在jqc框架算是初具雏形了,接下来让我们重新组织上面的方法吧。由于JQuery的大部分方法的算法实在是复杂,所以我不打算照搬jQuery的各种方法,牺牲一些兼容性来用简便的方法实现一个大致能用的框架。
(function(window,undefined){
var jqc = function(selector) {
return new jqc.fn.init(selector);
}
jqc.fn = jqc.prototrype = {
constructor:jqc,
length:0,
//初始化方法
init:function ( selector , context ){
var context = context || document;
//判断传入的是否是 null '' undefined 0
if (!selector) return;
//判断selector是否是字符串
if (jqc.isString(selector)) {
if (selector.charAt(0) === "<") {
//当为html标签时将调用parseHTML方法获取dom数组
//将获取到的数组调用pushArr方法添加到自身
this.push( parseHTML(selector) );
}else{
//否则调用select方法获取dom数组
//将获取到的数组调用pushArr方法添加到自身
this.push( select(selector,context) );
}
//判断selector是否是dom节点
} else if(jqc.isDom(selector)) {
//当传入的是dom元素时将dom元素添加到自身
this.push( [selector] );
//判断传入的是否是dom数组
} else if(jqc.isLikeArr(selector) && jqc.isDom(selector[9]) ){
this.push(selector);
//判断传入的是否是jqc对象
} else if( jqc.isJqc(selector) ){
return this;
}
},
push:function (){}
}
jqc.fn.init.prototype = jqc.fn;
//工具类方法模块
jqc.extend({
//实现push方法
push:[].push,
//循环遍历方法
each:function (){}
});
//判断类型模块
jqc.extend({
//判断是否是数组或者伪数组
isLikeArr:function(obj){},
isString:function(obj){},
isFunc:function(obj){},
//是否是dom对象
isDom:function(obj){},
//是否是jqc对象
isJqc:function(obj){}
});
//dom操作方法
jqc.fn.extend({
appendTo:function(selector){}
})
//html转换dom对象并返回数组对象
var parseHTML = function(html){}
//查询dom元素并返回数组对象
var select = function ( selector ) {};
window.jqc = jqc;
})(window)
以上代码删除了所有方法的方法体仅仅保留了init构造函数的方法体,完整代码请看GitHub地址:https://github.com/XLandMine/jqc。通过上面的伪代码就可以看到jqc框架已经大致成型,今后扩展就可以使用extend对jqc或者jqc的原型对象添加新的方法,而且还可以通过不同的extend代码块来对方法按功能分组,而且在沙箱之外,也可以通过extend来为jqc框架添加新的方法来写jqc插件。
jqc框架模拟了jQuery框架的使用方法,如果补全上面的代码,那么可以通过jqc("<div>1</div>").appendTo("body")来创建一个div元素并添加到body下。