jQuery 플러그인 템플릿-모범 사례, 규칙, 성능 및 메모리 영향
나는 몇 가지 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 사용 ).
저는 여러분이 제공 한 플러그인 템플릿을 다시 디자인하고 제 자신의 템플릿을 배포하는 데 시간을 할애했습니다.
연결:
- Github
- 선적 서류 비치
- 단위 테스트 FF4, Chrome 및 IE9에서 통과하는 것으로 확인되었습니다 (IE8 및 OP11 사망. 알려진 버그 ).
- 주석이 달린 소스 코드
- PlaceKitten 예제 플러그인
비교:
템플릿을 리팩토링하여 상용구 (85 %)와 스캐 폴딩 코드 (15 %)로 분할했습니다. 의도는 스캐 폴딩 코드를 편집하기 만하면되고 상용구 코드를 그대로 유지할 수 있다는 것입니다. 이것을 달성하기 위해 나는
- 상속
var self = Object.create(Base)
받은Internal
클래스를 직접 편집하는 대신 하위 클래스를 편집해야합니다. 모든 템플릿 / 기본 기능은 기본 클래스 (Base
내 코드에서 호출 됨) 에 있어야합니다 . - 규칙
self[PLUGIN_NAME] = main;
에 따라 jQuery에 정의 된 플러그인self[PLUGIN_NAME]
은 기본적으로 define on 메소드를 호출합니다 . 이것은main
플러그인 방법 으로 간주되며 명확성을 위해 별도의 외부 방법이 있습니다. - monkey patching monkey patching을
$.fn.bind = function _bind ...
사용한다는 것은 이벤트 네임 스페이스가 내부적으로 자동으로 수행된다는 것을 의미합니다. 이 기능은 무료이며 가독성을 희생하지 않습니다 (getEventNS
항상 호출 ).
OO 기법
고전적인 OO 에뮬레이션보다는 적절한 JavaScript OO를 고수하는 것이 좋습니다. 이를 위해서는 Object.create
. (ES5는 이전 브라우저를 업그레이드하기 위해 shim을 사용합니다).
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()
. 덮어 쓴 메서드는 네임 스페이스와 동일한 방식으로 작동합니다. 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를 사용하면 플러그인이 동기식이든 비동기식이든 추상화를 위해 매우 강력합니다.
캐싱 생성 기능이 추가되었습니다. 를 HTMLElement
Wrapped 요소로 변환 하기 위해 호출 되며 각 HTMLElement는 한 번만 래핑됩니다. 이 캐싱은 메모리를 크게 줄입니다.
플러그인에 대한 또 다른 공개 방법을 추가했습니다 (총 2 개!).
$.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 개의 메서드 오버로드 포함!) 플러그인이 노출 할 모든 메서드의 syntnax에 익숙해지기에 충분히 표준입니다.
Wrap
, create
및 $
개체가 전 세계적으로 노출되어있다. 이렇게하면 고급 플러그인 사용자가 플러그인을 최대한 유연하게 사용할 수 있습니다. 그들은 개발에 사용 create
하고 수정 된 서브 베드 $
를 사용할 수 있으며 원숭이 패치도 할 수 있습니다 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()
이며 주 논리를 포함해야합니다.
얼마 전 내가 읽은 블로그 기사를 기반으로 플러그인 생성기를 빌드했습니다 : 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 플러그인을 빌드하려는 대부분의 코드는 플러그인이 아닙니다! 노드 / 요소의 데이터 속성이 참조하는 메모리에 저장된 객체 일뿐입니다. 더 나은 코드를 빌드하기 위해 jQuery를 클래스 라이브러리 (OO 아키텍처의 js 불일치를 해결하기 위해)와 나란히 도구로보고 사용해야하기 때문입니다. 그렇습니다. 이것은 전혀 나쁘지 않습니다!
고전적인 OO 동작이 마음에 들지 않으면 clone 과 같은 프로토 타입 라이브러리를 사용하십시오 .
그래서 우리의 선택은 정말로 무엇입니까?
- 기술을 숨기고 추상화를 제공하는 JQueryUI / Widget 또는 유사한 라이브러리 사용
- 복잡성, 학습 곡선 및 신은 미래의 변화를 알고 있으므로 사용하지 마십시오.
- 모듈 식 디자인을 고집하고 싶기 때문에 사용하지 말고 나중에 조금씩 늘리십시오.
- 다른 라이브러리로 코드를 이식 / 연결하고 싶을 수 있으므로 사용하지 마십시오.
다음 시나리오에서 해결 된 문제를 가정합니다 (이 질문의 복잡성 참조 : 어떤 jQuery 플러그인 디자인 패턴을 사용해야합니까? ).
객체 참조를
data
속성에 저장하는 노드 A, B 및 C가 있습니다.그들 중 일부는 공개 및 비공개 액세스 가능한 내부 객체에 정보를 저장 하고 , 이러한 객체의 일부 클래스는 상속 과 연결되며 , 이러한 모든 노드는 최상의 작동을 위해 일부 비공개 및 공개 싱글 톤 이 필요합니다 .
우리는 무엇을할까요? drawing 참조 :
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));
저는 다음에서 몇 가지 슬라이드를보고있었습니다. http://www.slideshare.net/benalman/jquery-plugin-creation from Ben Alman 여기서 그는 슬라이드 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
See: http://jsfiddle.net/centurianii/s4J2H/1/
How about something like this ? It's much clearer but again it would be nice to hear from you if you can improve it without overcomplicating its simplicity.
// 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
'developer tip' 카테고리의 다른 글
복잡한 데이터 구조 Redis (0) | 2020.12.09 |
---|---|
Apple clang 버전 및 해당 업스트림 LLVM 버전 가져 오기 (0) | 2020.12.09 |
애플리케이션 백그라운드에서 동영상을 녹화하는 방법 : Android (0) | 2020.12.09 |
C ++ 11 람다에서 참조로 참조 캡처 (0) | 2020.12.09 |
Chrome 개발자 도구에서 디버깅하는 동안 줄 건너 뛰기 (0) | 2020.12.09 |