이 기사에서는 예제를 통해 jQuery 플러그인 개발에 대한 몇 가지 세부 사항을 소개합니다. 먼저 jQuery 플러그인 개발에 대한 몇 가지 기본 지식을 소개합니다.
jQuery 플러그인 개발은 크게 두 가지 범주로 나뉩니다.
1. 클래스 수준, 즉 $.ajax, $.get 등과 유사한 jQuery 클래스 자체에서 메서드를 확장합니다.
2. 여기서 말하는 객체는 객체에 대한 jQuery 추가 메서드를 통해 선택된 jQuery 객체를 의미합니다. 예: $('div').css(), $('div').show() 등.
실제 개발에서는 일반적으로 플러그인 개발을 위해 객체 수준 방법을 사용합니다. jQuery의 강력한 선택기와 객체 작업이 이 방법을 선택하는 큰 이유입니다.
다음으로 두 가지 방법 중 구체적인 작성 방법을 살펴보겠습니다.
클래스 레벨 플러그인 개발
$.extend({ foo: function() { //... }, bar: function() { //... } }) //调用 $.foo();
여기서 확장 메서드의 이름은 jQuery 클래스의 원래 메서드와 동일한 이름이 되지 않도록 더욱 주의해야 합니다. 그럼에도 불구하고, 사용자 정의 네임스페이스를 생성할 수 있는 클래스에서 여러 메서드를 확장해야 할 때 이름 지정 충돌이 계속 발생할 수 있습니다.
$.myPlugin = { foo: function() { //... }, bar: function() { //... } } //调用 $.myPulgin.foo();
객체 수준 플러그인 개발
$.fn.foo = function() { //doSomething... } //调用(假设拥有一个id为obj的元素) $('#obj').foo(); 有个会问 fn 是什么东东?粘一段别人截取的jQuery源码就明白了: jQuery.fn = jQuery.prototype = { init: function(selector, context) { //.... } }
프로토타입 체인으로 밝혀졌습니다. . .
구성 매개변수 수신
플러그인을 작성할 때 플러그인을 사용하는 사람들이 원하는 대로 플러그인의 일부 속성을 설정할 수 있도록 허용할 수 있습니다. 이를 위해서는 플러그인에 매개변수 수신 기능이 동시에 필요합니다. 플러그인을 사용하는 사람이 매개변수를 전달하지 않으면 플러그인은 내부적으로 기본 구성 매개변수 세트도 있습니다.
$.fn.foo = function(options) { var defaults = { color: '#000', backgroundColor: 'red' }; var opts = $.extend({}, defaults, options); alert(opts.backgroundColor); //yellow } $('#obj').foo({ backgroundColor: 'yellow' })
여기서 핵심은 개체를 병합할 수 있는 $.extend 메서드입니다. 동일한 속성의 경우 후속 개체가 이전 개체를 덮어씁니다. 확장 메소드의 첫 번째 매개변수가 빈 객체인 이유는 무엇입니까? 이 메서드는 후자를 전자에 병합하므로 기본값이 변경되는 것을 방지하기 위해 첫 번째 매개 변수는 빈 개체로 설정됩니다.
플러그인을 사용하는 사람들이 기본 매개변수를 설정하도록 허용하는 경우 해당 매개변수를 노출해야 합니다.
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); alert(opts.backgroundColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
이러한 방식으로 플러그인의 기본 매개변수를 외부에서 수정할 수 있습니다.
몇 가지 메소드를 적절하게 노출하세요
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); $.fn.foo.sayColor(opts.backgroundColor); } $.fn.foo.sayColor = function(bgColor) { alert(bgColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
재작성:
$.fn.foo.sayColor = function(bgColor) { alert('background color is ' + bgColor); }
플러그인에서 일부 메서드를 노출하는 것은 매우 멋진 일입니다. 이를 통해 다른 메서드를 확장하고 재정의할 수 있습니다. 그러나 다른 사람이 매개변수나 메소드를 수정하면 다른 많은 것에 영향을 미칠 가능성이 높습니다. 따라서 메서드를 노출할지 여부를 고려할 때 명확해야 합니다. 확실하지 않으면 노출하지 마세요.
기능을 비공개로 유지
사생활 보호라고 하면 가장 먼저 떠오르는 것은 무엇인가요? 맞습니다. 종료되었습니다.
;(function($) { $.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); debug(opts.backgroundColor); } function debug(bgColors) { console.log(bgColors); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' } })(jQuery)
이것은 jQuery가 제공하는 공식 플러그인 개발 방법입니다. 이점은 다음과 같습니다. 1. 전역 종속성이 없습니다. 2. 다른 사람에 의한 손상을 방지합니다. 3. '$' 및 'jQuery' 연산자와 호환됩니다.
위와 같이 디버그 방법은 플러그인 내부에서 전용 방법이 되며 외부에서 수정할 수 없습니다. 클로저 앞에 ; 를 추가하면 코드 병합 중에 클로저 앞의 코드에 세미콜론이 없을 경우 나중에 오류가 보고되는 것을 방지할 수 있습니다.
병합
;(function($) { //定义插件 $.fn.foo = function(options) { //doSomething... } //私有函数 function debug() { //doSomething... } //定义暴露函数 $.fn.foo.sayColor = function() { //doSomething... } //插件默认参数 $.fn.foo.default = { color: '#000', backgroundColor: 'red' } })(jQuery);
위의 코드는 완전하고 표준화된 플러그인 뼈대를 생성합니다. 매우 간단해 보이지만 실제 개발에는 아직 많은 기술과 주의 사항이 있습니다.
오랜 고민 끝에 팝업창을 예시로 플러그인으로 만드는 게 더 적절할 것 같다는 생각이 들었습니다. 개발하기 전에 먼저 이 팝업 플러그인의 구조와 기능을 상상해 봅시다.
위 사진을 보면 제목, 내용, 버튼 그룹의 세 부분으로 구성되어 있음을 알 수 있습니다. 여기서는 단지 하나의 버튼만 포함하는 브라우저의 기본 경고 상자를 만드는 것이 아니라 사용자가 버튼 수를 사용자 정의하여 팝업 상자가 다음과 유사한 기능을 완료할 수 있도록 할 수 있다는 점을 여기서 명시해야 합니다. 확인 상자.
플러그인 스켈레톤 구축
function SubType($ele, options) { this.$ele = $ele; this.opts = $.extend({}, $.fn.popWin.defaults, options); } SubType.prototype = { createPopWin: function() { } }; $.fn.popWin = function(options) { //this指向被jQuery选择器选中的对象 var superType = new SubType(this, options); superType.createPopWin(); }; $.fn.popWin.defaults = {};
1. popWin이라는 객체 기반 메소드를 생성하고 사용자가 수정할 수 있도록 기본 구성 매개변수를 공개했습니다.
2. 여기서는 개인 기능을 관리하기 위해 객체 지향 방법을 사용합니다. createPopWin 방법은 팝업 창을 만드는 데 사용되는 개인 기능입니다.
3. 플러그인이 호출되면 jq 객체와 맞춤 매개변수를 생성자에 전달하고 인스턴스화합니다.
전화
이 플러그인을 어떻게 부르는지 상상해 보세요. 문서 트리의 적절한 위치에 div 요소를 삽입하고 div를 선택한 다음 jQuery 개체에 정의한 popWin 메서드를 호출할 수 있습니다.
$('#content').popWin({ a: 1, b: 2, callback: function() {} });
popWin을 호출할 때 사용자 정의 구성 매개변수를 전달하면 선택한 div 요소가 마술처럼 팝업 창으로 변환됩니다! 물론 이것은 단지 우리의 상상일 뿐이므로 코딩을 시작해 보겠습니다.
기본 구성 결정
$.fn.popWin.defaults = { width: '600', //弹窗宽 height: '250', //弹窗高 title: '标题', //标题 desc: '描述', //描述 winCssName: 'pop-win', //弹窗的CSS类名 titleCssName: 'pop-title', //标题区域的CSS类名 descCssName: 'pop-desc', //描述区域的CSS类名 btnAreaCssName: 'pop-btn-box', //按钮区域的CSS类名 btnCssName: 'pop-btn', //单个按钮的CSS类名 btnArr: ['确定'], //按钮组 callback: function(){} //点击按钮之后的回调函数 }
我们定义了如上的参数,为什么有要传入这么多的CSS类名呢?1. 为了保证JS与CSS尽可能的解耦。 2. 你的样式有很大可能别人并不适用。所以你需要配置一份样式表文件来对应你的默认类名,当别人需要更改样式时可以传入自己编写的样式。
按钮组为一个数组,我们的弹窗需要根据其传入的数组长度来动态的生成若干个按钮。回调函数的作用是在用户点击了某个按钮时返回他所点击按钮的索引值,方便他进行后续的操作。
弹窗DOM创建
var popWinDom,titleAreaDom,descAreaDom,btnAreaDom; SubType.prototype = { createPopWin: function() { var _this = this; //首次创建弹窗 //背景填充整个窗口 this.$ele.css({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', overflow: 'hidden' }); //窗口区域 popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName); //标题区域 titleAreaDom = popWinDom.find('div:eq(0)') .text(this.opts.title) .attr('class',this.opts.titleCssName); //描述区域 descAreaDom = popWinDom.find('div:eq(1)') .text(this.opts.desc) .attr('class',this.opts.descCssName); //按钮区域 btnAreaDom = popWinDom.find('div:eq(2)') .attr('class',this.opts.btnAreaCssName); //插入按钮 this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr({'data-index':index, 'class':_this.opts.btnCssName}) .on('click', function() { _this.opts.callback($(this).attr('data-index')); })); }); this.$ele.append(popWinDom); } }
1. 首先命名了四个变量用来缓存我们将要创建的四个DOM,将传入的jQuery对象变形成覆盖整个窗口半透明元素;
2. 创建窗口DOM,根据传入的高、宽来设置尺寸并居中,之后另上传入的窗口CSS类名;
3. 创建标题、描述、按钮组区域,并将传入的标题、描述内容配置上去;
4. 动态加入按钮,并为按钮加上data-index的索引值。注册点击事件,点击后调用传入的回调函数,将索引值传回。
好了,我们先看下效果。调用如下:
$('#content').popWin({ width: '500', height: '200', title: '系统提示', desc: '注册成功', btnArr: ['关闭'], callback: function(clickIndex) { console.log(clickIndex); } });
可以看到一个弹窗的DOM已被渲染到页面中了,当点击关闭按钮时控制台会打印出 "0",因为按钮组只有一个值嘛,当然是第0个了。
如果我们需要多次调用这个弹窗,每次都要传入高、宽我会觉得很麻烦。这时我们可以直接在一开始修改插件内部的默认配置,这也是我们将默认配置暴露的好处:
$.fn.popWin.defaults.width = '500'; $.fn.popWin.defaults.height = '200';
要注意的当然是不能直接改变defaults的引用,以免露掉必须的参数。 这样以后的调用都无需传入尺寸了。
我们加一个按钮并且传入一个自定义的样式看看好使不呢?
$('#content').popWin({ title: '系统提示', desc: '是否删除当前内容', btnArr: ['确定','取消'], winCssName: 'pop-win-red', callback: function(clickIndex) { console.log(clickIndex); } });
可以看到都是生效了的,当点击“确定”按钮时回调函数返回 0,点击“取消”按钮时回调函数返回 1。这样使用插件的人就知道自己点击的是哪一个按钮,以完成接下来的操作。
显示&隐藏
接下来要进行打开、关闭弹窗功能的开发。回想上面介绍的概念,我们想让使用该插件的人能够对这两个方法进行扩展或者重写,所以将这两个方法暴露出去:
$.fn.popWin.show = function($ele) { $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.hide(); }
之后在createPopWin方法中需要的地方调用这两个方法。
这里多强调一点,也是做弹窗控件不可避免的一点:只有当我们点击按钮以及灰色背景区域时允许弹窗关闭,点击弹窗其他地方不允许关闭。由于弹窗属于整个灰色区域的子节点,必然牵扯到的就是事件冒泡的问题。
所以在给最外层加上点击关闭的事件时,要在弹窗区域阻止事件冒泡。
popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName).on('click', function(event) { event.stopPropagation(); });
二次打开
我们只需要在第一次调用插件时创建所有创建DOM,第二次调用时只更改其参数即可,所以在createPopWin方法最前面加入如下方法:
if (popWinDom) { //弹窗已创建 popWinDom.css({ width: this.opts.width, height: this.opts.height }).attr('class',this.opts.winCssName); titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName); descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName); btnAreaDom.html('').attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr('data-index',index) .attr('class',_this.opts.btnCssName) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); $.fn.popWin.show(this.$ele); return; }
合并整个插件代码
;(function($) { function SubType(ele, options) { this.$ele = ele; this.opts = $.extend({}, $.fn.popWin.defaults, options); } var popWinDom,titleAreaDom,descAreaDom,btnAreaDom; SubType.prototype = { createPopWin: function() { var _this = this; if (popWinDom) { //弹窗已创建 popWinDom.css({ width: this.opts.width, height: this.opts.height }).attr('class',this.opts.winCssName); titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName); descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName); btnAreaDom.html('').attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr('data-index',index) .attr('class',_this.opts.btnCssName) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); $.fn.popWin.show(this.$ele); return; } //首次创建弹窗 this.$ele.css({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', overflow: 'hidden', display: 'none' }).on('click', function() { $.fn.popWin.hide(_this.$ele); }); popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName).on('click', function(event) { event.stopPropagation(); }); titleAreaDom = popWinDom.find('div:eq(0)') .text(this.opts.title) .attr('class',this.opts.titleCssName); descAreaDom = popWinDom.find('div:eq(1)') .text(this.opts.desc) .attr('class',this.opts.descCssName); btnAreaDom = popWinDom.find('div:eq(2)') .attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr({'data-index':index, 'class':_this.opts.btnCssName}) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); this.$ele.append(popWinDom); $.fn.popWin.show(this.$ele); } } $.fn.popWin = function(options) { var superType = new SubType(this, options); superType.createPopWin(); return this; } $.fn.popWin.show = function($ele) { $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.hide(); } $.fn.popWin.defaults = { width: '600', height: '250', title: 'title', desc: 'description', winCssName: 'pop-win', titleCssName: 'pop-title', descCssName: 'pop-desc', btnAreaCssName: 'pop-btn-box', btnCssName: 'pop-btn', btnArr: ['确定'], callback: function(){} } })(jQuery);
如上,一个完整的弹窗插件就在这里了。
说下这个标红的 return this 是干什么用的,前面已说过 this 在这里是被选中的jQuery对象。将其return就可以在调用完我们的插件方法后可以继续调用jQ对象上的其他方法,也就是jQuery的链式操作,说玄乎点就叫级联函数。
OK!趁热打铁,我们来看看暴露出去的两个方法重写之后效果怎么样,毕竟对插件暴露部分的扩展和重写是很牛逼的一块东西。
想象个情景,你用了这个插件后觉得简单的show和hide效果简直是low爆了,决定重写这个弹出和隐藏的效果:
$.fn.popWin.show = function($ele) { $ele.children().first().css('top','-30%').animate({top:'30%'},500); $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.children().first().animate({top:'-30%'},500,function() { $ele.hide(); }); }
你在自己的代码里加上上面两段,然后发现弹窗有了一个简单的上下滑动进入屏幕的效果,同时又不会影响我们弹窗的创建,证明我们的暴露方法还算合理。
当然你也可以让它竖着进、横着进、翻着跟头进,这就看你自己了。
最后贴上默认的样式表,为了急着想粘回去试试的同学们。
.pop-win { border: 1px solid #fff; padding: 10px; background-color: #fff; -wekbit-border-radius: 6px; border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3); box-shadow: 0 3px 9px rgba(0,0,0,0.3); } .pop-win-red { padding: 10px; background-color: red; -wekbit-border-radius: 6px; border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3); box-shadow: 0 3px 9px rgba(0,0,0,0.3); } .pop-title { width: 100%; height: 20%; line-height: 40px; padding-left: 10px; box-sizing: border-box; border-bottom: 1px solid #eee; font-size: 17px; font-weight: bold; } .pop-desc { width: 100%; height: 60%; box-sizing: border-box; padding: 10px 0 0 10px; border-bottom: 1px solid #eee; } .pop-btn-box { width: 100%; height: 20%; text-align: right; } .pop-btn { margin: 10px 10px 0 0; width: 60px; height: 30px; }
当然这只是个编写插件的例子,如果要拿出去使用还需要仔细打磨。例子虽然简单,旨在抛砖引玉。