我已经开始写一些jQuery插件,并且认为使用jQuery插件模板设置IDE会很不错。
我一直在阅读此站点上与插件约定,设计等相关的文章和帖子。我以为我会尝试将所有这些合并。
以下是我的模板,我希望经常使用它,因此希望确保它大体上符合jQuery插件设计约定,以及具有多个内部方法(甚至是其常规设计)的想法是否会影响性能并容易出现内存问题。
(function($)
{
var PLUGIN_NAME = "myPlugin"; // TODO: Plugin name goes here.
var DEFAULT_OPTIONS =
{
// TODO: Default options for plugin.
};
var pluginInstanceIdCount = 0;
var I = function(/*HTMLElement*/ element)
{
return new Internal(element);
};
var Internal = function(/*HTMLElement*/ element)
{
this.$elem = $(element);
this.elem = element;
this.data = this.getData();
// Shorthand accessors to data entries:
this.id = this.data.id;
this.options = this.data.options;
};
/**
* Initialises the plugin.
*/
Internal.prototype.init = function(/*Object*/ customOptions)
{
var data = this.getData();
if (!data.initialised)
{
data.initialised = true;
data.options = $.extend(DEFAULT_OPTIONS, customOptions);
// TODO: Set default data plugin variables.
// TODO: Call custom internal methods to intialise your plugin.
}
};
/**
* Returns the data for relevant for this plugin
* while also setting the ID for this plugin instance
* if this is a new instance.
*/
Internal.prototype.getData = function()
{
if (!this.$elem.data(PLUGIN_NAME))
{
this.$elem.data(PLUGIN_NAME, {
id : pluginInstanceIdCount++,
initialised : false
});
}
return this.$elem.data(PLUGIN_NAME);
};
// TODO: Add additional internal methods here, e.g. Internal.prototype.<myPrivMethod> = function(){...}
/**
* Returns the event namespace for this widget.
* The returned namespace is unique for this widget
* since it could bind listeners to other elements
* on the page or the window.
*/
Internal.prototype.getEventNs = function(/*boolean*/ includeDot)
{
return (includeDot !== false ? "." : "") + PLUGIN_NAME + "_" + this.id;
};
/**
* Removes all event listeners, data and
* HTML elements automatically created.
*/
Internal.prototype.destroy = function()
{
this.$elem.unbind(this.getEventNs());
this.$elem.removeData(PLUGIN_NAME);
// TODO: Unbind listeners attached to other elements of the page and window.
};
var publicMethods =
{
init : function(/*Object*/ customOptions)
{
return this.each(function()
{
I(this).init(customOptions);
});
},
destroy : function()
{
return this.each(function()
{
I(this).destroy();
});
}
// TODO: Add additional public methods here.
};
$.fn[PLUGIN_NAME] = function(/*String|Object*/ methodOrOptions)
{
if (!methodOrOptions || typeof methodOrOptions == "object")
{
return publicMethods.init.call(this, methodOrOptions);
}
else if (publicMethods[methodOrOptions])
{
var args = Array.prototype.slice.call(arguments, 1);
return publicMethods[methodOrOptions].apply(this, args);
}
else
{
$.error("Method '" + methodOrOptions + "' doesn't exist for " + PLUGIN_NAME + " plugin");
}
};
})(jQuery);
提前致谢。
[编辑] 7个月后
引用github项目
jQuery不好,而jQuery插件不是模块化代码。
认真地讲,“ jQuery插件”不是一种可靠的体系结构策略。严格依赖jQuery编写代码也很愚蠢。
[原版的]
由于我对此模板提出了批评,因此我将提出一个替代方案。
为了使生活更轻松,这依赖于jQuery
1.6+和ES5(使用ES5 Shim)。
我花了一些时间重新设计您提供的插件模板,然后推出了自己的插件模板。
链接:
比较:
我已经对模板进行了重构,以便将其分为样板(85%)和脚手架代码(15%)。目的是您只需要编辑脚手架代码,就可以保持样板代码不变。为了达到这个目的,我使用了
- 继承
var self = Object.create(Base)
而不是Internal
直接编辑您拥有的类,则应该编辑子类。您的所有模板/默认功能都应在基类中(Base
在我的代码中称为)。 - 约定
self[PLUGIN_NAME] = main;
按照约定self[PLUGIN_NAME]
,默认情况下,在jQuery上定义的插件将调用方法define 。main
为了清楚起见,这被认为是插件方法,并且具有单独的外部方法。 - 猴子补丁猴子补丁的
$.fn.bind = function _bind ...
使用意味着事件名称间隔是在后台自动为您完成的。此功能是免费的,并且不会以可读性为代价(getEventNS
始终调用)。
面向对象技术
最好坚持使用正确的JavaScript OO,而不是传统的OO仿真。为此,您应该使用Object.create
。(ES5只是使用填充程序来升级旧的浏览器)。
var Base = (function _Base() {
var self = Object.create({});
/* ... */
return self;
})();
var Wrap = (function _Wrap() {
var self = Object.create(Base);
/* ... */
return self;
})();
var w = Object.create(Wrap);
这与标准new
和.prototype
基于OO的人习惯的不同。首选这种方法,因为它加强了JavaScript中只有对象的概念,并且它是一种典型的面向对象方法。
[ getEventNs
]
如前所述,此方法已通过重写重构.bind
并.unbind
自动注入名称空间。这些方法在jQuery的专用版本上被覆盖$.sub()
。被覆盖的方法的行为与命名空间的行为相同。它根据插件和HTMLElement周围的插件包装实例唯一地命名事件(使用.ns
。
[ getData
]
此方法已替换为.data
具有与相同的API的方法jQuery.fn.data
。它是同一API的事实使它更易于使用,它基本上是jQuery.fn.data
带有命名空间的薄包装器。这使您可以设置仅针对该插件立即存储的键/值对数据。多个插件可以并行使用此方法,而不会发生任何冲突。
[ publicMethods
]
publicMethods对象已由在Wrap
自动公开时定义的任何方法替换。您可以直接在Wrapped对象上调用任何方法,但实际上没有访问包装对象的权限。
[ $.fn[PLUGIN_NAME]
]
这已经过重构,因此它公开了更加标准化的API。这个api是
$(selector).PLUGIN_NAME("methodName", {/* object hash */}); // OR
$(selector).PLUGIN_NAME({/* object hash */}); // methodName defaults to PLUGIN_NAME
选择器中的元素会自动包装在Wrap
对象中,方法会被调用,或者从选择器中选择的每个元素以及返回值始终是一个$.Deferred
元素。
这标准化了API和返回类型。然后,您可以调用.then
返回的递延数据,以获取您关心的实际数据。无论插件是同步的还是异步的,在这里使用deferred都很强大,可以进行抽象。
缓存创建功能已添加。这被称为将aHTMLElement
转换为Wrapped元素,每个HTMLElement仅被包装一次。此缓存使您的内存大量减少。
为插件添加了另一个公共方法(总共两个!)。
$.PLUGIN_NAME(elem, "methodName", {/* options */});
$.PLUGIN_NAME([elem, elem2, ...], "methodName", {/* options */});
$.PLUGIN_NAME("methodName", {
elem: elem, /* [elem, elem2, ...] */
cb: function() { /* success callback */ }
/* further options */
});
所有参数都是可选的。elem
默认为<body>
,"methodName"
默认为"PLUGIN_NAME"
,{/* options */}
默认为{}
。
这个API非常灵活(有14个方法重载!)并且足够标准,足以适应您插件公开的每个方法的语法。
的Wrap
,create
而$
对象是全球范围内曝光。这将使高级插件用户可以最大程度地灵活使用您的插件。他们可以在开发中使用create
和修改后的字幕$
,也可以猴子补丁Wrap
。这允许挂钩您的插件方法。这三个_
名称的名称前面都标记有一个,因此它们是内部名称,使用它们将破坏您的插件起作用的保证。
内部defaults
对象也显示为$.PLUGIN_NAME.global
。这使用户可以覆盖您的默认设置并将插件设置为global defaults
。在此插件设置中,随着对象与默认值合并,所有过去散列到方法中的哈希值将因此散布到方法中,因此用户可以为所有方法设置全局默认值。
(function($, jQuery, window, document, undefined) {
var PLUGIN_NAME = "Identity";
// default options hash.
var defaults = {
// TODO: Add defaults
};
// -------------------------------
// -------- BOILERPLATE ----------
// -------------------------------
var toString = Object.prototype.toString,
// uid for elements
uuid = 0,
Wrap, Base, create, main;
(function _boilerplate() {
// over-ride bind so it uses a namespace by default
// namespace is PLUGIN_NAME_<uid>
$.fn.bind = function _bind(type, data, fn, nsKey) {
if (typeof type === "object") {
for (var key in type) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.bind(nsKey, data, type[key], fn);
}
return this;
}
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.bind.call(this, nsKey, data, fn);
};
// override unbind so it uses a namespace by default.
// add new override. .unbind() with 0 arguments unbinds all methods
// for that element for this plugin. i.e. calls .unbind(_ns)
$.fn.unbind = function _unbind(type, fn, nsKey) {
// Handle object literals
if ( typeof type === "object" && !type.preventDefault ) {
for ( var key in type ) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.unbind(nsKey, type[key]);
}
} else if (arguments.length === 0) {
return jQuery.fn.unbind.call(this, this.data(PLUGIN_NAME)._ns);
} else {
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.unbind.call(this, nsKey, fn);
}
return this;
};
// Creates a new Wrapped element. This is cached. One wrapped element
// per HTMLElement. Uses data-PLUGIN_NAME-cache as key and
// creates one if not exists.
create = (function _cache_create() {
function _factory(elem) {
return Object.create(Wrap, {
"elem": {value: elem},
"$elem": {value: $(elem)},
"uid": {value: ++uuid}
});
}
var uid = 0;
var cache = {};
return function _cache(elem) {
var key = "";
for (var k in cache) {
if (cache[k].elem == elem) {
key = k;
break;
}
}
if (key === "") {
cache[PLUGIN_NAME + "_" + ++uid] = _factory(elem);
key = PLUGIN_NAME + "_" + uid;
}
return cache[key]._init();
};
}());
// Base object which every Wrap inherits from
Base = (function _Base() {
var self = Object.create({});
// destroy method. unbinds, removes data
self.destroy = function _destroy() {
if (this._alive) {
this.$elem.unbind();
this.$elem.removeData(PLUGIN_NAME);
this._alive = false;
}
};
// initializes the namespace and stores it on the elem.
self._init = function _init() {
if (!this._alive) {
this._ns = "." + PLUGIN_NAME + "_" + this.uid;
this.data("_ns", this._ns);
this._alive = true;
}
return this;
};
// returns data thats stored on the elem under the plugin.
self.data = function _data(name, value) {
var $elem = this.$elem, data;
if (name === undefined) {
return $elem.data(PLUGIN_NAME);
} else if (typeof name === "object") {
data = $elem.data(PLUGIN_NAME) || {};
for (var k in name) {
data[k] = name[k];
}
$elem.data(PLUGIN_NAME, data);
} else if (arguments.length === 1) {
return ($elem.data(PLUGIN_NAME) || {})[name];
} else {
data = $elem.data(PLUGIN_NAME) || {};
data[name] = value;
$elem.data(PLUGIN_NAME, data);
}
};
return self;
})();
// Call methods directly. $.PLUGIN_NAME(elem, "method", option_hash)
var methods = jQuery[PLUGIN_NAME] = function _methods(elem, op, hash) {
if (typeof elem === "string") {
hash = op || {};
op = elem;
elem = hash.elem;
} else if ((elem && elem.nodeType) || Array.isArray(elem)) {
if (typeof op !== "string") {
hash = op;
op = null;
}
} else {
hash = elem || {};
elem = hash.elem;
}
hash = hash || {}
op = op || PLUGIN_NAME;
elem = elem || document.body;
if (Array.isArray(elem)) {
var defs = elem.map(function(val) {
return create(val)[op](hash);
});
} else {
var defs = [create(elem)[op](hash)];
}
return $.when.apply($, defs).then(hash.cb);
};
// expose publicly.
Object.defineProperties(methods, {
"_Wrap": {
"get": function() { return Wrap; },
"set": function(v) { Wrap = v; }
},
"_create":{
value: create
},
"_$": {
value: $
},
"global": {
"get": function() { return defaults; },
"set": function(v) { defaults = v; }
}
});
// main plugin. $(selector).PLUGIN_NAME("method", option_hash)
jQuery.fn[PLUGIN_NAME] = function _main(op, hash) {
if (typeof op === "object" || !op) {
hash = op;
op = null;
}
op = op || PLUGIN_NAME;
hash = hash || {};
// map the elements to deferreds.
var defs = this.map(function _map() {
return create(this)[op](hash);
}).toArray();
// call the cb when were done and return the deffered.
return $.when.apply($, defs).then(hash.cb);
};
}());
// -------------------------------
// --------- YOUR CODE -----------
// -------------------------------
main = function _main(options) {
this.options = options = $.extend(true, defaults, options);
var def = $.Deferred();
// Identity returns this & the $elem.
// TODO: Replace with custom logic
def.resolve([this, this.elem]);
return def;
}
Wrap = (function() {
var self = Object.create(Base);
var $destroy = self.destroy;
self.destroy = function _destroy() {
delete this.options;
// custom destruction logic
// remove elements and other events / data not stored on .$elem
$destroy.apply(this, arguments);
};
// set the main PLUGIN_NAME method to be main.
self[PLUGIN_NAME] = main;
// TODO: Add custom logic for public methods
return self;
}());
})(jQuery.sub(), jQuery, this, document);
可以看出,您应该编辑的代码在该YOUR CODE
行下方。该Wrap
对象的行为与您的Internal
对象相似。
该函数main
是用$.PLUGIN_NAME()
或调用的主要函数$(selector).PLUGIN_NAME()
,应包含您的主要逻辑。
不久前,我已经根据我读过的博客文章构建了一个插件生成器:http : //jsfiddle.net/KeesCBakker/QkPBF/。它可能有用。这是相当基本和直接的。任何意见将是非常欢迎的。
您可以派生自己的发电机,并根据需要进行更改。
附言 这是生成的主体:
(function($){
//My description
function MyPluginClassName(el, options) {
//Defaults:
this.defaults = {
defaultStringSetting: 'Hello World',
defaultIntSetting: 1
};
//Extending options:
this.opts = $.extend({}, this.defaults, options);
//Privates:
this.$el = $(el);
}
// Separate functionality from object creation
MyPluginClassName.prototype = {
init: function() {
var _this = this;
},
//My method description
myMethod: function() {
var _this = this;
}
};
// The actual plugin
$.fn.myPluginClassName = function(options) {
if(this.length) {
this.each(function() {
var rev = new MyPluginClassName(this, options);
rev.init();
$(this).data('myPluginClassName', rev);
});
}
};
})(jQuery);
我一直在谷歌上搜索并降落在这里,所以,我必须发表一些想法:首先,我同意@Raynos。
实际上,尝试构建jQuery插件的最多代码...不是插件!它只是存储在内存中的一个对象,由节点/元素的data属性引用。这是因为应该将jQuery视为类库的工具,并与之一起使用(以纠正OO体系结构中的js不一致),以构建更好的代码,是的,这一点也不错!
如果您不喜欢经典的OO行为,则可以使用诸如clone这样的原型库。
那么,我们真正的选择是什么?
- 使用JQueryUI / Widget或类似的库隐藏技术并提供抽象
- 不要因为复杂,学习曲线而使用它们,上帝知道未来的变化
- 不要使用它们,因为您要坚持模块化设计,以后再进行小幅扩建
- 不要使用它们,因为您可能希望将代码移植/连接到其他库。
假设以下情况解决了这些问题(请参阅此问题的复杂性:应该使用哪种jQuery插件设计模式?):
我们有节点A,B和C将对象引用存储到其
data
属性中其中一些将信息存储在公共和私有可访问内部对象中,这些对象的某些类与继承相关联,所有这些节点还需要一些私有和公共单例才能发挥最佳性能。
我们会做什么?参见图纸:
classes : | A B C
------------------case 1----------
members | | | |
of | v v v
an object | var a=new A, b=new B, c=new C
at | B extends A
node X : | a, b, c : private
------------------case 2---------
members | | | |
of | v v v
an object | var aa=new A, bb=new B, cc=new C
at | BB extends AA
node Y : | aa, bb, cc : public
-------------------case 3--------
members | | | |
of | v v v
an object | var d= D.getInstance() (private),
at | e= E.getInstance() (public)
node Z : | D, E : Singletons
如您所见,每个节点都引用一个对象-一种jQuery方法-但这些对象发生了疯狂的变化;它们包含的对象属性具有存储在中的不同数据,甚至应该是单个实例,如对象的原型函数一样应该在内存中。我们不希望每个对象的函数在每个节点的对象的内存中class A
重复复制!
在我回答之前,先看看我在jQuery插件中见过的一种通用方法-其中一些非常流行,但我不说名字:
(function($, window, document, undefined){
var x = '...', y = '...', z = '...',
container, $container, options;
var myPlugin = (function(){ //<----the game is lost!
var defaults = {
};
function init(elem, options) {
container = elem;
$container = $(elem);
options = $.extend({}, defaults, options);
}
return {
pluginName: 'superPlugin',
init: function(elem, options) {
init(elem, options);
}
};
})();
//extend jquery
$.fn.superPlugin = function(options) {
return this.each(function() {
var obj = Object.create(myPlugin); //<---lose, lose, lose!
obj.init(this, options);
$(this).data(obj.pluginName, obj);
});
};
}(jQuery, window, document));
我在Ben Alman的以下位置观看了一些幻灯片:http : //www.slideshare.net/benalman/jquery-plugin-creation,他在幻灯片13中将对象文字称为单例,这使我震惊:这就是上面的内容插件确实会创建一个单例,而没有任何机会改变其内部状态!!!
此外,在jQuery部分,它存储了对每个单个节点的公共引用!
我的解决方案使用的工厂,以保持内部状态,并返回一个对象,再加上它可以用一个可扩展的类库,并分成不同的文件:
;(function($, window, document, undefined){
var myPluginFactory = function(elem, options){
........
var modelState = {
options: null //collects data from user + default
};
........
function modeler(elem){
modelState.options.a = new $$.A(elem.href);
modelState.options.b = $$.B.getInstance();
};
........
return {
pluginName: 'myPlugin',
init: function(elem, options) {
init(elem, options);
},
get_a: function(){return modelState.options.a.href;},
get_b: function(){return modelState.options.b.toString();}
};
};
//extend jquery
$.fn.myPlugin = function(options) {
return this.each(function() {
var plugin = myPluginFactory(this, options);
$(this).data(plugin.pluginName, plugin);
});
};
}(jQuery, window, document));
我的项目:https://github.com/centurianii/jsplugin
请参阅:http://jsfiddle.net/centurianii/s4J2H/1/
这样的事情怎么样?它更加清晰,但是如果您能在不使它过于简单的情况下对其进行改进,那么也很高兴听到您的意见。
// jQuery plugin Template
(function($){
$.myPlugin = function(options) { //or use "$.fn.myPlugin" or "$.myPlugin" to call it globaly directly from $.myPlugin();
var defaults = {
target: ".box",
buttons: "li a"
};
options = $.extend(defaults, options);
function logic(){
// ... code goes here
}
//DEFINE WHEN TO RUN THIS PLUGIN
$(window).on('load resize', function () { // Load and resize as example ... use whatever you like
logic();
});
// RETURN OBJECT FOR CHAINING
// return this;
// OR FOR FOR MULTIPLE OBJECTS
// return this.each(function() {
// // Your code ...
// });
};
})(jQuery);
// USE EXAMPLE with default settings
$.myPlugin(); // or run plugin with default settings like so.
// USE EXAMPLE with overwriten settings
var options = {
target: "div.box", // define custom options
buttons: ".something li a" // define custom options
}
$.myPlugin(options); //or run plugin with overwriten default settings
文章标签:javascript , jquery , jquery-plugins
版权声明:本文为原创文章,版权归 admin 所有,欢迎分享本文,转载请保留出处!
评论已关闭!