javascript之模仿jQuery实现框架雏形
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下。