In diesem Artikel werden einige Details der jQuery-Plug-in-Entwicklung anhand eines Beispiels vorgestellt. Zunächst stellen wir einige Grundkenntnisse der jQuery-Plug-in-Entwicklung vor.
Die Entwicklung von jQuery-Plug-ins ist hauptsächlich in zwei Kategorien unterteilt:
1. Klassenebene, d. h. Erweiterung der Methoden auf der jQuery-Klasse selbst, ähnlich wie $.ajax, $.get usw.
2. Das sogenannte Objekt bezieht sich hier auf das jQuery-Objekt, das über den jQuery-Selektor ausgewählt wurde. Zum Beispiel: $('div').css(), $('div').show() usw.
In der tatsächlichen Entwicklung verwenden wir normalerweise die Methode auf Objektebene, um Plug-Ins zu entwickeln. Die leistungsstarken Selektoren und Objektoperationen von jQuery sind ein wichtiger Grund, warum wir diese Methode wählen.
Als nächstes schauen wir uns die spezifischen Schreibmethoden der beiden Methoden an:
Plug-in-Entwicklung auf Klassenebene
$.extend({ foo: function() { //... }, bar: function() { //... } }) //调用 $.foo();
Hier muss die Benennung der Erweiterungsmethode sorgfältiger erfolgen, damit sie nicht denselben Namen wie die ursprüngliche Methode in der jQuery-Klasse hat. Dennoch können Namenskonflikte auftreten, wenn wir mehrere Methoden für eine Klasse erweitern müssen, für die wir einen benutzerdefinierten Namespace erstellen können:
$.myPlugin = { foo: function() { //... }, bar: function() { //... } } //调用 $.myPulgin.foo();
Plug-in-Entwicklung auf Objektebene
$.fn.foo = function() { //doSomething... } //调用(假设拥有一个id为obj的元素) $('#obj').foo(); 有个会问 fn 是什么东东?粘一段别人截取的jQuery源码就明白了: jQuery.fn = jQuery.prototype = { init: function(selector, context) { //.... } }
Es stellt sich heraus, dass es sich um eine Prototypenkette handelt. . .
Konfigurationsparameter empfangen
Beim Schreiben eines Plug-Ins können wir Benutzern erlauben, einige Eigenschaften des Plug-Ins nach ihren Wünschen festzulegen. Dies erfordert, dass das Plug-In gleichzeitig über die Funktion verfügt, Parameter zu empfangen Wenn die Person, die das Plug-in verwendet, keine Parameter übergibt, wird das Plug-in verwendet. Es gibt auch intern einen Satz Standardkonfigurationsparameter.
$.fn.foo = function(options) { var defaults = { color: '#000', backgroundColor: 'red' }; var opts = $.extend({}, defaults, options); alert(opts.backgroundColor); //yellow } $('#obj').foo({ backgroundColor: 'yellow' })
Der Schlüssel hier ist die Methode $.extend, die Objekte zusammenführen kann. Bei gleichen Eigenschaften überschreiben nachfolgende Objekte vorherige Objekte. Warum ist der erste Parameter der Extend-Methode ein leeres Objekt? Da diese Methode Letzteres mit Ersterem zusammenführt, wird der erste Parameter auf ein leeres Objekt gesetzt, um zu verhindern, dass Standardeinstellungen geändert werden.
Wenn wir Benutzern, die Plugins verwenden, erlauben, Standardparameter festzulegen, müssen wir sie offenlegen:
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); alert(opts.backgroundColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
Auf diese Weise können die Standardparameter des Plug-Ins extern geändert werden.
Stellen Sie einige Methoden angemessen dar
$.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' }
Umgeschrieben:
$.fn.foo.sayColor = function(bgColor) { alert('background color is ' + bgColor); }
Das Offenlegen einiger Methoden im Plug-in ist sehr cool, es ermöglicht anderen, Ihre Methoden zu erweitern und zu überschreiben. Aber wenn andere Ihre Parameter oder Methoden ändern, hat dies wahrscheinlich Auswirkungen auf viele andere Dinge. Daher sollten Sie einen klaren Kopf haben, wenn Sie darüber nachdenken, ob Sie die Methode offenlegen. Wenn Sie sich nicht sicher sind, legen Sie sie nicht offen.
Funktionen privat halten
Was fällt Ihnen als Erstes ein, wenn es darum geht, die Privatsphäre zu wahren? Genau, es ist ein Abschluss:
;(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)
Dies ist die offizielle Plug-in-Entwicklungsmethode von jQuery. Zu den Vorteilen gehören: 1. Keine globalen Abhängigkeiten 2. Vermeidung von Schäden durch andere 3. Kompatibel mit den Operatoren „$“ und „jQuery“.
Wie oben wird die Debug-Methode zu einer privaten Methode innerhalb des Plug-Ins und kann nicht extern geändert werden. Das Hinzufügen von ; vor dem Abschluss verhindert, dass später Fehler gemeldet werden, wenn im Code vor dem Abschluss beim Zusammenführen des Codes ein Semikolon fehlt.
Zusammenführen
;(function($) { //定义插件 $.fn.foo = function(options) { //doSomething... } //私有函数 function debug() { //doSomething... } //定义暴露函数 $.fn.foo.sayColor = function() { //doSomething... } //插件默认参数 $.fn.foo.default = { color: '#000', backgroundColor: 'red' } })(jQuery);
Der obige Code erstellt ein vollständiges und standardisiertes Plug-in-Skelett. Obwohl es sehr einfach erscheint, gibt es in der tatsächlichen Entwicklung noch viele Fähigkeiten und Vorsichtsmaßnahmen.
Nachdem ich lange darüber nachgedacht hatte, kam ich zu dem Schluss, dass es angemessener wäre, das Popup-Fenster als Beispiel in ein Plug-In umzuwandeln. Stellen wir uns vor der Entwicklung zunächst die Struktur und Funktionen dieses Popup-Plug-Ins vor:
Auf dem Bild oben können wir sehen, dass es aus drei Teilen besteht: Titel, Inhalt und Schaltflächengruppe. An dieser Stelle muss darauf hingewiesen werden, dass wir die Standard-Alarmbox im Browser nicht nur mit nur einer Schaltfläche versehen möchten, sondern der Benutzer die Anzahl der Schaltflächen anpassen kann, sodass die Popup-Box auch ähnliche Funktionen ausführen kann Bestätigen Sie das Feld.
Erstellen Sie das Plug-in-Skelett
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. Wir haben eine objektbasierte Methode namens popWin erstellt und die Standardkonfigurationsparameter verfügbar gemacht, damit Benutzer sie ändern können
2. Hier verwenden wir eine objektorientierte Methode, um unsere privaten Funktionen zu verwalten. Die createPopWin-Methode ist unsere private Funktion, die zum Erstellen von Popup-Fenstern verwendet wird.3. Wenn das Plug-in aufgerufen wird, übergeben Sie das jq-Objekt und die benutzerdefinierten Parameter an den Konstruktor und instanziieren es.
Anrufe
Stellen Sie sich vor, wie wir dieses Plug-in nennen? Wir können ein div-Element an der entsprechenden Stelle in unserem Dokumentbaum einfügen, das div auswählen und die popWin-Methode aufrufen, die wir für das jQuery-Objekt definiert haben.
$('#content').popWin({ a: 1, b: 2, callback: function() {} });
Bestimmen Sie die Standardkonfiguration
$.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; }
当然这只是个编写插件的例子,如果要拿出去使用还需要仔细打磨。例子虽然简单,旨在抛砖引玉。