jquery插件一般是这么干的: $.fn.插件名称 = function(){}, 把插件的名称加在.fn上,在源码里面实际上是扩展到构造函数的原型对象上,如果你没看过jquery的源代码,或者你曾经看过,但是不知道为什么把插件扩展到fn上,那么本篇文章就能解答你的疑惑。关于jquery插件开发方式,可以参考我的这篇文章:[js高手之路]jquery插件开发实战-选项卡详解
关于选项卡这个功能具体怎么做,不在这里详解,这个是入门级的功能,本文重在讨论插件开发的架构,扩展,以及参数设置。
如果你使用过jquery的选项卡插件,或者其他类型的插件,他们一般都是这么调用的:
$( ".tab" ).tabs( {} )
$(".tab").tabs( function(){} );
一种是传递参数定制插件行为
一种是传递函数定制插件行为
$(".tab") 选择到元素,然后返回的是jquery对象
tabs方法扩展在fn上就是扩展都jquery构造函数的原型对象上, 那么对象( $(".tab") )调用原型对象上的方法( tabs )当然就顺利成章了。
所以jquery插件扩展本质就是: 构造函数 + 原型对象扩展方法
定义一个构造+原型的方式,下面代码的原理,我在这篇文章有详细论述:[js高手之路] 设计模式系列课程 - jQuery的链式调用与灵活的构造函数
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 var G = function( selectors, context ){ 2 return new G.fn.init( selectors, context ); 3 } 4 G.fn = G.prototype = { 5 length : 0, 6 constructor : G, 7 size : function(){ 8 return this.length; 9 }, 10 init : function( selector, context ){ 11 this.length = 0; 12 context = context || document; 13 if ( selector.indexOf( '#' ) == 0 ){ 14 this[0] = document.getElementById( selector.substring( 1 ) ); 15 this.length = 1; 16 }else { 17 var aNode = context.querySelectorAll( selector ); 18 for( var i = 0, len = aNode.length; i < len; i++ ) { 19 this[i] = aNode[i]; 20 } 21 this.length = len; 22 } 23 this.selector = selector; 24 this.context = context; 25 return this; 26 } 27 } 28 29 G.fn.init.prototype = G.fn;
接下来,我们还要添加一个插件扩展机制:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 G.extend = G.fn.extend = function () { 2 var i = 1, 3 len = arguments.length, 4 dst = arguments[0], 5 j; 6 if (dst.length === undefined) { 7 dst.length = 0; 8 } 9 if (i == len) { 10 dst = this; 11 i--; 12 } 13 for (; i < len; i++) { 14 for (j in arguments[i]) { 15 dst[j] = arguments[i][j]; 16 dst.length++; 17 } 18 } 19 return dst; 20 };
在这篇文章:[js高手之路] 设计模式系列课程 - jQuery的extend插件机制 有详细的论述,extend插件扩展机制
像使用jquery一样暴露接口:
var $ = function( selectors, context ){
return G( selectors, context );
}
window.$ = $;
这个插件的扩展机制和元素选择机制就完成了,如果要扩展插件,只要在
G.fn上扩展插件的名称即可,如:
1 G.fn.tabs = function( options ){ 2 options = options || {}; 3 var defaults = { 4 contentClass : 'tab-content', 5 navClass : 'tab-nav', 6 activeClass : 'active', 7 triggerElements : '*', 8 activeIndex : 0, 9 evType : 'click', 10 effect : 'none' 11 }; 12 13 var opt = G.extend( {}, defaults, options ); 14 return this; 15 }
这样,我们就在G的原型对象上扩展了一个tabs( 选项卡 )插件
options可以定制插件的行为:
contentClass : 'tab-content', 选项卡内容区域的class名称
navClass : 'tab-nav', 标签卡区域的class名称
activeClass : 'active', 标签卡默认选择的class名称:active
triggerElements : '*', 标签卡默认触发元素
activeIndex : 0, 默认选中第几个标签卡
evType : 'click', 选项卡触发的事件类型
effect : 'none' 是否有过渡特效:如透明度
var opt = G.extend( {}, defaults, options );
这一段是把定制的配置和默认配置合成到一个对象opt里面,后面的插件行为,就可以根据opt的配置进行定制,这是插件开发参数定制中,常用的一招。
这样做的好处,可以防止污染默认配置defaults
1 var tabContent = this[0].querySelector( "." + opt.contentClass ); 2 var tabContentEle = tabContent.children; 3 var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements ); 4 5 var _contentLen = tabContentEle.length; 6 var _index = opt.activeIndex;
获取对应的元素。
有了选项卡的元素和配置,我们就开始做业务处理(为所有选项卡添加处理的事件,进行选项卡切换)
定义一个专门的对象_api = {}, 扩展业务api
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 G.fn.tabs = function( options ){ 2 options = options || {}; 3 var defaults = { 4 contentClass : 'tab-content', 5 navClass : 'tab-nav', 6 activeClass : 'active', 7 triggerElements : '*', 8 activeIndex : 0, 9 evType : 'click', 10 effect : 'none' 11 }; 12 13 var opt = G.extend( {}, defaults, options ); 14 15 var tabContent = this[0].querySelector( "." + opt.contentClass ); 16 var tabContentEle = tabContent.children; 17 var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements ); 18 19 var _contentLen = tabContentEle.length; 20 var _index = opt.activeIndex; 21 22 var _api = {}; 23 24 _api.setIndex = function( index ){ 25 //当前标签加上active样式,其余标签删除active样式 26 for ( var i = 0; i < _contentLen; i++ ) { 27 if ( tabNavEle[i].classList.contains( 'active' ) ) { 28 tabNavEle[i].classList.remove('active'); 29 } 30 } 31 tabNavEle[index].classList.add( 'active' ); 32 switch ( opt.effect ){ 33 case 'fade': 34 break; 35 default: 36 for ( var i = 0; i < _contentLen; i++ ) { 37 tabContentEle[i].style.display = 'none'; 38 } 39 tabContentEle[index].style.display = 'block'; 40 _index = index; 41 } 42 } 43 44 _api.setIndex( _index ); //默认的选项卡 45 46 //所有的标签绑定事件 47 for( var i = 0, len = tabNavEle.length; i < len; i++ ) { 48 tabNavEle[i].index = i; 49 tabNavEle[i].addEventListener( opt.evType, function(){ 50 var i = this.index; 51 _api.setIndex( i ); 52 }, false ); 53 } 54 55 return this; 56 }
完整的插件代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /** 2 * Created by ghostwu(吴华). 3 */ 4 (function(){ 5 var G = function( selectors, context ){ 6 return new G.fn.init( selectors, context ); 7 } 8 G.fn = G.prototype = { 9 length : 0, 10 constructor : G, 11 size : function(){ 12 return this.length; 13 }, 14 init : function( selector, context ){ 15 this.length = 0; 16 context = context || document; 17 if ( selector.indexOf( '#' ) == 0 ){ 18 this[0] = document.getElementById( selector.substring( 1 ) ); 19 this.length = 1; 20 }else { 21 var aNode = context.querySelectorAll( selector ); 22 for( var i = 0, len = aNode.length; i < len; i++ ) { 23 this[i] = aNode[i]; 24 } 25 this.length = len; 26 } 27 this.selector = selector; 28 this.context = context; 29 return this; 30 } 31 } 32 33 G.fn.init.prototype = G.fn; 34 G.extend = G.fn.extend = function () { 35 var i = 1, 36 len = arguments.length, 37 dst = arguments[0], 38 j; 39 if (dst.length === undefined) { 40 dst.length = 0; 41 } 42 if (i == len) { 43 dst = this; 44 i--; 45 } 46 for (; i < len; i++) { 47 for (j in arguments[i]) { 48 dst[j] = arguments[i][j]; 49 dst.length++; 50 } 51 } 52 return dst; 53 }; 54 55 G.fn.tabs = function( options ){ 56 options = options || {}; 57 var defaults = { 58 contentClass : 'tab-content', 59 navClass : 'tab-nav', 60 activeClass : 'active', 61 triggerElements : '*', 62 activeIndex : 0, 63 evType : 'click', 64 effect : 'none' 65 }; 66 67 var opt = G.extend( {}, defaults, options ); 68 69 var tabContent = this[0].querySelector( "." + opt.contentClass ); 70 var tabContentEle = tabContent.children; 71 var tabNavEle = this[0].querySelectorAll( "." + opt.navClass + '>' + opt.triggerElements ); 72 73 var _contentLen = tabContentEle.length; 74 var _index = opt.activeIndex; 75 76 var _api = {}; 77 78 _api.setIndex = function( index ){ 79 //当前标签加上active样式,其余标签删除active样式 80 for ( var i = 0; i < _contentLen; i++ ) { 81 if ( tabNavEle[i].classList.contains( 'active' ) ) { 82 tabNavEle[i].classList.remove('active'); 83 } 84 } 85 tabNavEle[index].classList.add( 'active' ); 86 switch ( opt.effect ){ 87 case 'fade': 88 break; 89 default: 90 for ( var i = 0; i < _contentLen; i++ ) { 91 tabContentEle[i].style.display = 'none'; 92 } 93 tabContentEle[index].style.display = 'block'; 94 _index = index; 95 } 96 } 97 98 _api.setIndex( _index ); //默认的选项卡 99 100 //所有的标签绑定事件 101 for( var i = 0, len = tabNavEle.length; i < len; i++ ) { 102 tabNavEle[i].index = i; 103 tabNavEle[i].addEventListener( opt.evType, function(){ 104 var i = this.index; 105 _api.setIndex( i ); 106 }, false ); 107 } 108 109 return this; 110 } 111 112 var $ = function( selectors, context ){ 113 return G( selectors, context ); 114 } 115 window.$ = $; 116 })();
选项卡布局:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <!--作者:ghostwu(吴华)--> 5 <meta charset="UTF-8"> 6 <title>选项卡插件 - by ghostwu</title> 7 <link rel="stylesheet" href="css/tab.css"/> 8 <script src="./js/tab.js"></script> 9 <script> 10 window.onload = function () { 11 // console.log( $(".tab1 .tab-nav li") ); 12 // $( ".tab1" ).tabs( { 'evType' : 'mouseenter' } ); 13 $( ".tab1" ).tabs(); 14 } 15 </script> 16 </head> 17 <body> 18 <div class="main"> 19 <div class="tab tab1"> 20 <ul class="tab-nav"> 21 <li class="active"><a href="javascript:;">标签1</a></li> 22 <li><a href="javascript:;">标签2</a></li> 23 <li><a href="javascript:;">标签3</a></li> 24 <li><a href="javascript:;">标签4</a></li> 25 </ul> 26 <div class="tab-content"> 27 <p>内容1</p> 28 <p>内容2</p> 29 <p>内容3</p> 30 <p>内容4</p> 31 </div> 32 </div> 33 </div> 34 </body> 35 </html>
选项卡插件样式:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 * { 2 margin: 0; 3 padding: 0; 4 } 5 body { 6 font-size: 14px; 7 font-family: Tahoma, Verdana,"Microsoft Yahei"; 8 } 9 a{ 10 text-decoration: none; 11 color:#000; 12 } 13 ul,li{ 14 list-style-type: none; 15 } 16 img { 17 border:none; 18 } 19 .main { 20 width:960px; 21 margin:20px auto; 22 } 23 .tab{ 24 margin: 0 auto 20px; 25 } 26 .tab1 .tab-nav{ 27 margin-bottom:8px; 28 } 29 .tab .tab-nav { 30 overflow:hidden; 31 } 32 .tab1 .tab-nav .active{ 33 border-bottom:1px solid #000; 34 } 35 .tab1 .tab-nav li { 36 float:left; 37 margin:0 10px; 38 } 39 .tab1 .tab-nav li a { 40 line-height:40px; 41 display:block; 42 } 43 .tab1 .tab-content { 44 height:250px; 45 overflow:hidden; 46 } 47 .tab1 .tab-content p { 48 height:250px; 49 background:#eee; 50 }
最终效果: