개요
jQuery 기반 웹 애플리케이션 개발 시 반복적인 기능을 플러그인으로 캡슐화하면 코드 재사용성과 유지보수성을 크게 향상시킬 수 있다. 본 문서에서는 jQuery 플러그인의 기본 프레임워크 구조와 개발 패턴을 정리한다.
jQuery 플러그인 개발 패턴
클로저(Closures) 활용
jQuery 플러그인은 클로저를 사용하여 작성하는 것이 표준 관행이다. 다음과 같은 구조로 즉시 실행 함수를 정의한다.
// 즉시 실행 함수 (IIFE)
(function($) {
// 플러그인 코드 작성 영역
})(jQuery);
위 패턴에서 클로저의 주요 역할은 다음과 같다:
- 전역 네임스페이스 오염 방지
- 타 라이브러리와의 충돌 회피
- jQuery의 '$' 심볼과 'jQuery' 객체 간 호환성 보장
개발 방식의 분류
클래스 레벨 컴포넌트 개발
클래스 레벨 개발은 jQuery의 네임스페이스에 새로운 정적 함수를 추가하는 방식이다.
// 정적 메서드 정의
$.myUtility = function(config) {
// 로직 구현
return config;
};
// 사용 예시
$.myUtility({ mode: 'expand' });
$.ajax()나 $.extend()가 대표적인 클래스 레벨开发的 예시이다.
인스턴스 레벨 컴포넌트 개발
인스턴스 레벨开发는 jQuery 프로토타입에 메서드를 추가하여 선택자로 얻은 객체가 해당 메서드를 공유하도록 한다.
// 프로토타입에 메서드 추가
$.fn.myPlugin = function(options) {
// 플러그인 로직
return this; // 체이닝 지원
};
// 사용 예시
$('.element').myPlugin({ speed: 300 });
$.fn은 $.prototype과 동일한 참조이므로 addClass(), attr() 등의 메서드와 유사하게 동작한다.
체이닝(Chainable Calls) 구현
jQuery의 유연성을 유지하기 위해 체이닝을 지원하도록 구현한다.
$.fn.customSlider = function() {
return this.each(function(index, element) {
// 각 요소에 대한 처리 로직
$(element).data('initialized', true);
});
};
// 체이닝 사용 가능
$('.slider').customSlider().eq(0).addClass('first-item');
this.each()를 사용하여 컬렉션의 각 요소를 순회하고, this를 반환하여 체이닝을 유지한다.
싱글톤 패턴 적용
플러그인 인스턴스를 한 번만 생성하고 재사용하려면 data() 메서드를 활용한다.
$.fn.customSlider = function() {
var $element = $(this),
instance = $element.data('customSlider');
if (!instance) {
instance = new SliderCore($element);
$element.data('customSlider', instance);
}
return instance;
};
data() 메서드에 인스턴스를 저장하면 동일한 요소에 대해 중복 생성을 방지할 수 있다.
플러그인 기본 구조
완전한 jQuery 플러그인의 구조는 다음 요소들로 구성된다.
1단계: 클로저 정의
(function($) {
// 플러그인 코드 영역
})(jQuery);
2단계: 플러그인 객체 정의
var sliderPlugin = (function() {
function SliderCore(element, options) {
this.element = element;
this.settings = $.extend(true, {}, $.fn.sliderPlugin.defaults, options);
this.init();
}
SliderCore.prototype = {
init: function() {
// 초기화 로직
},
render: function() {
// 렌더링 로직
}
};
return SliderCore;
})();
3단계: 기본 옵션 정의
$.fn.sliderPlugin.defaults = {
selectors: {
container: '.slider-container',
item: '.slider-item',
pagination: '.pagination',
activeClass: 'active'
},
startIndex: 0,
animationEasing: 'swing',
animationDuration: 400,
autoPlay: false,
showPagination: true,
keyboardSupport: true,
direction: 'horizontal',
onTransition: null
};
4단계: 플러그인 등록 및 인스턴스 관리
$.fn.sliderPlugin = function(options) {
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var $element = $(this),
instance = $element.data('sliderPlugin');
if (!instance) {
instance = new sliderPlugin($element, options);
$element.data('sliderPlugin', instance);
}
// 문자열 메서드 호출 지원
if (typeof options === 'string') {
instance[options].apply(instance, args);
}
});
};
5단계: 자동 초기화
(function($) {
$(function() {
$('[data-slider-plugin]').sliderPlugin();
});
})(jQuery);
구현 예시: 이미지 슬라이더 플러그인
(function($) {
// 비공개 메서드
var privateHelper = function(element) {
return element.children().first();
};
// 플러그인 코어
var imageSlider = (function() {
function ImageSlider(element, options) {
this.settings = $.extend(true, {}, $.fn.imageSlider.defaults, options);
this.element = element;
this.currentIndex = this.settings.startIndex;
this.init();
}
ImageSlider.prototype = {
init: function() {
this.setupDOM();
this.bindEvents();
if (this.settings.autoPlay) {
this.startAutoPlay();
}
},
setupDOM: function() {
this.sections = this.element.find(this.settings.selectors.sections);
},
bindEvents: function() {
var self = this;
if (this.settings.keyboardSupport) {
$(document).on('keydown', function(e) {
self.handleKeyboard(e);
});
}
},
handleKeyboard: function(event) {
switch(event.keyCode) {
case 37:
this.prev();
break;
case 39:
this.next();
break;
}
},
next: function() {
var max = this.sections.length - 1;
this.currentIndex = this.settings.loop
? (this.currentIndex + 1) % (max + 1)
: Math.min(this.currentIndex + 1, max);
this.animate();
},
prev: function() {
var max = this.sections.length - 1;
this.currentIndex = this.settings.loop
? (this.currentIndex - 1 + max + 1) % (max + 1)
: Math.max(this.currentIndex - 1, 0);
this.animate();
},
animate: function() {
var self = this;
this.sections.eq(this.currentIndex)
.animate({ opacity: 1 }, self.settings.duration, self.settings.easing);
},
startAutoPlay: function() {
var self = this;
this.timer = setInterval(function() {
self.next();
}, 3000);
},
destroy: function() {
if (this.timer) {
clearInterval(this.timer);
}
this.element.removeData('imageSlider');
}
};
return ImageSlider;
})();
// 플러그인 등록
$.fn.imageSlider = function(options) {
if (typeof options === 'string') {
var args = Array.prototype.slice.call(arguments, 1);
return this.each(function() {
var instance = $(this).data('imageSlider');
if (instance && instance[options]) {
instance[options].apply(instance, args);
}
});
}
return this.each(function() {
var $this = $(this),
instance = $this.data('imageSlider');
if (!instance) {
instance = new imageSlider($this, options);
$this.data('imageSlider', instance);
}
});
};
// 기본 설정값
$.fn.imageSlider.defaults = {
selectors: {
sections: '.slide-section',
page: '.slide-page',
active: 'active'
},
startIndex: 0,
easing: 'swing',
duration: 500,
loop: false,
pagination: true,
keyboardSupport: true,
direction: 'horizontal',
autoPlay: false,
callback: ''
};
// data属性를 통한 자동 초기화
$(function() {
$('[data-image-slider]').imageSlider();
});
})(jQuery);
사용 방법
방법 1: JavaScript로 초기화
$('#slider-container').imageSlider({
duration: 600,
loop: true,
autoPlay: true,
direction: 'vertical'
});
// 메서드 호출
$('#slider-container').imageSlider('next');
$('#slider-container').imageSlider('destroy');
방법 2: data属性 활용
<div id="slider-container" data-image-slider>
<div class="slide-section">
<div class="slide-item" id="slide0">첫 번째 슬라이드</div>
<div class="slide-item" id="slide1">두 번째 슬라이드</div>
<div class="slide-item" id="slide2">세 번째 슬라이드</div>
</div>
</div>