Ответ 1
[Изменить] 7 месяцев спустя
Цитата из проекта github
jQuery не годится, а плагины jQuery - это не как модульный код.
Серьезно "плагины jQuery" не являются звуковой архитектурой. Написание кода с жесткой зависимостью от jQuery также глупо.
[Оригинал]
Поскольку я дал критику в отношении этого шаблона, я предложу альтернативу.
Чтобы сделать жизнь проще, это зависит от jQuery
1.6+ и ES5 (используйте ES5 Shim).
Я потратил некоторое время на повторное проектирование шаблона плагина, который вы предоставили, и вывел мой собственный.
Ссылки:
- Github
- Документация
- Модульные тесты Подтверждено пройти в FF4, Chrome и IE9 (версии IE8 и OP11 известны ошибка).
- Аннотированный исходный код
- Плагин примера PlaceKitten
Сравнение:
Я реорганизовал шаблон так, чтобы он разделился на шаблон (85%) и код леса (15%). Цель состоит в том, что вам нужно только отредактировать код леса, и вы можете оставить шаблонный шаблон без изменений. Для этого я использовал
- inheritance
var self = Object.create(Base)
Вместо того, чтобы редактировать классInternal
, который у вас есть, вы должны отредактировать подкласс. Все ваши шаблоны/функции по умолчанию должны быть в базовом классе (называемомBase
в моем коде). - convention
self[PLUGIN_NAME] = main;
По соглашению плагин, определенный в jQuery, по умолчанию определяет метод define наself[PLUGIN_NAME]
. Это считается модулем плагинаmain
и для ясности имеет отдельный внешний метод. - Патч обезьяны
$.fn.bind = function _bind ...
Использование патчей для обезьян означает, что пространство имен событий выполняется автоматически для вас под капотом. Эта функциональность бесплатна и не требует затрат на чтение (вызываетсяgetEventNS
все время).
Методы OO
Лучше придерживаться надлежащего 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 есть только объекты и это прототипный подход OO.
[getEventNS
]
Как упоминалось, этот метод был реорганизован путем переопределения .bind
и .unbind
для автоматического ввода пространств имен. Эти методы перезаписываются в частной версии jQuery $.sub()
. Переписанные методы ведут себя так же, как это делает пространство имен. Он namespaces события уникально основаны на плагине и экземпляре оболочки плагина вокруг HTMLElement (используя .ns
.
[getData
]
Этот метод был заменен на метод .data
, который имеет тот же API, что и jQuery.fn.data
. Тот факт, что он тот же API упрощает его использование, в основном представляет собой тонкую оболочку вокруг jQuery.fn.data
с пространством имен. Это позволяет вам устанавливать данные пары ключ/значение, которые немедленно сохраняются только для этого плагина. Несколько плагинов могут использовать этот метод параллельно без конфликтов.
[publicMethods
]
Объект publicMethods заменен любым способом, который определен на Wrap
, автоматически публичным. Вы можете вызвать любой метод на обернутом объекте напрямую, но на самом деле у вас нет доступа к обернутому объекту.
[$.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
по возвращенному отсроченному, чтобы получить фактические данные, о которых вы заботитесь. Использование отложенных здесь очень мощно для абстрагирования от того, является ли плагин синхронным или асинхронным.
Добавлена функция создания кеширования. Это вызвано, чтобы превратить HTMLElement
в обернутый элемент, и каждый элемент HTMLE будет только один раз обернут. Это кэширование дает вам значительное сокращение памяти.
Добавлен еще один общедоступный метод для плагина (всего два!).
$.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
и модифицированный subbed $
в своей разработке, а также патч обезьяны Wrap
. Это позволяет, например, подключаться к вашим методам плагинов. Все три из них отмечены знаком _
перед их именем, поэтому они являются внутренними, и их использование нарушает гарантию, что ваш плагин работает.
Внутренний объект defaults
также отображается как $.PLUGIN_NAME.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()
и должна содержать основную логику.