2026년 3월 어느 날 이력서를 5군데 정도 더 집어넣어 놓고 잠을 자려는데 잠이 안 오더랬다. 억울해서. jQuery와 순수PHP를 쓰던 직전 직장에서도 그나마 배운 게 없지 않은데, 닥쳐오는 AI 시대 모든 것이 Typescript React로 수렴되면서 이 잔재주는 인류사에서 통째로 외면 망각될 것 같은 기분이 든다. 그 사실에 울화가 치밀어, 나라도 뭐라도 남겨놓고자 이 시리즈를 시작한다.
혹시 여러분이 운영 중인 레거시 프로젝트에서 jQuery가 골치를 썩이고 있는가?
탈출할 방법을 같이 생각하고 탈출하자.
일단 내가 시도했던 것들을 먼저 공유한다.
레거시 프로젝트에서 jQuery가 골칫거리가 되는 데는 여러 맥락이 있지만, 의외로 현실적으로 고통스러운 지점 중 하나가 "재사용성"의 부재다. 다른 곳에서는 캡슐화니 컴포넌트화니 복잡한 말이 많은데 jQuery가 짱 먹은 코드베이스에서 재사용성이란 종종 "복붙 편의성"을 의미하곤 하는 것이다.
실제로 있었던 사례 중에는 이런 것이 있다.
화면상의 어느 (아무것도 없는 듯한) 특정 부분을 5번 누르면 "숨겨진" 관리화면으로 넘어갈 수 있어야 한다고 해 보자.
왕년의 jQuery는 늠름하게 다음과 같이 코딩하고 보람차게 퇴근했다.
<style>
#gotoadmin {
padding: 2em;
}
</style>
<button id="gotoadmin"></button>
<script>
var adminclick = 0;
$('#gotoadmin').click(function() {
adminclick++;
if (adminclick == 5) {
window.navigation.href = '/admin';
}
});
</script>
그리고 1년쯤 지나서 관리자 화면에 "슈퍼관리기능"을 노출하는 "비슷한" 버튼이 하나 더 생겨야 한다고 해 보자.
후임자는 기존 코드를 복사해 쓰기로 결정한다.
조금 귀찮지만 재사용성이 없진 않군, 야근하지 않아도 돼서 다행이야, 하면서.
<style>
#showsuperadmin {
padding: 2em;
}
</style>
<button id="showsuperadmin"></button>
<div id="superadmin" style="display: none;">슈퍼관리기능</div>
<script>
var superadminclick = 0;
$('#showsuperadmin').click(function() {
superadminclick++;
if (adminclick == 5) {
$('#superadmin').show();
}
});
</script>
그런데 작동을 안 한다. 이런! 복붙하는 과정에서 한 군데 고쳐야 할 곳을 덜 고쳤다. (위의 코드 예제에 숨겨 놓았으니 한번 심심풀이로 찾아보시라!)
이 정도 실수/버그는 차라리 애교다. 담당자가 여럿 교체되면서 이런 버튼을 여기저기 복붙해서 달아 놨는데, 나중에 언젠가 "이 버튼들 5번 클릭 말고 10번 클릭으로 다 바꿔 주세요" 같은 요청이 들어온다면, 그땐 어떻게 할 것인가? 소스 코드 전체를 == 5로 검색해야겠지? 그렇게 고칠 것은 놓치고 안 망가진 것은 망가뜨리며 앞전 사람들이 안 한 몫까지 야근하는, 뒤늦은 기술 부채 상환이 벌어지는 것이다.
위 사례의 명세는 다음과 같이 일반화 가능하다.
let count = 0;
let threshold = 5; // 달라질 수도 있으므로 변수화
let callback = function () { /* 달라질 수도 있으므로 변수화 */ };
$(btn).click(function () {
count++;
if (count == threshold) {
callback();
}
});
이 프로시저를 함수로 감싸서 $.fn의 프로퍼티로 추가할 수 있다. 현재로서는 이게 곧 jQuery Plugin이라고 생각해도 무방하다.
$.fn.useInvisibleNavigation = function () { // 추가
let count = 0;
let threshold = 5;
let callback = function () { ... };
const btn = $(this); // 수정
btn.click(function () { // 수정
count++;
if (count == threshold) {
callback();
}
});
}; // 추가
어이쿠! jQuery 플러그인을 하나 만들어 버렸네요!!
- jQuery 동작 원리상, 여기서의
$(this)는.useInvisibleNavigation()메소드가 호출되는/될 대상 엘리먼트의 jQuery 인스턴스다.
- 그래서
.click()이 존재한다.
위 코드는 어차피 $ === window.jQuery가 호이스팅돼 있어야 작동하는 코드다. 그러므로 IIEF로 감싸두는 것도 가능하다. 랄까 그게 권장된다. 그래서 아래 코드와 같이 "어디서 많이 본" 형태의 코드가 작성된다.
(function($) {
$.fn.useInvisibleNavigation = function () {
const btn = $(this);
let count = 0;
let threshold = 5;
let callback = function () { ... };
btn.click(function () {
count++;
if (count == threshold) {
callback();
}
});
};
})(jQuery);
이제 이것을 invisible-navigation.js로 저장해 두면, 최소한으로나마 진정한 의미에서 재사용이 가능해진다.
<button class="invisble-nav"></button>
<button class="invisble-nav"></button>
<script src="/invisible-navigation.js"></script>
<script>
$('.invisible-nav').each(function() {
$(this).useInvisibleNavigation();
});
</script>
$.fn.useInvisibleNavigation은 단지 함수일 뿐이므로, 인자를 넘길 수 있다.
(function($) {
$.fn.useInvisibleNavigation = function (
threshold = 5,
callback = undefined // 이 2개는 원래 변수였음
) {
const btn = $(this);
let count = 0;
btn.click(function () {
count++;
if (count == threshold && typeof callback == 'function') {
callback();
}
});
};
})(jQuery);
이제 #gotoadmin 버튼과 #showsuperadmin 버튼 두 가지를 하나의 코드로 분리 운용할 수 있다.
<button id="gotoadmin"></button>
<button id="showsuperadmin"></button>
<div id="superadmin" style="display: none;">슈퍼관리기능</div>
<script src="/invisible-navigation.js"></script>
<script>
$('#gotoadmin').useInvisibleNavigation(5, function () {
window.navigation.href = '/admin';
});
$('#showsuperadmin').useInvisibleNavigation(10, function () {
$('#superadmin').show();
});
</script>
그렇다. 사실은 이게 재사용성이다.
'다른 곳에 여러 번 써넣기 좋은 코드'가 아니라, 한 번 써넣은 걸 여러 곳에서 쓸 수 있는 코드 말씀이지.
하지만 jQuery에 길들여진 코드베이스에서는 이런 기초적인 데까지조차 가지 못할 정도로 복사-붙여넣기-조금고치기 의 타성에 젖어 있으므로, 이토록 간단한 플러그인조차도 굉장한 심화 주제로 기능한다.
기왕 옵션화해 놓고 보니 이런 부분도 거슬린다.
threshold 인자에 부여돼 있는 기본값 5가 별 의미가 없다. 이 인자에 값을 주는 부분을 생략하고 지나갈 수가 없기 때문이다. 그냥 기본값을 쓰라고 넘기고 싶을 땐 어떻게 해야 할까? 인자 순서를 뒤집으면 되겠지만, 그게 정말 해결책일까?.useInvisibleNavigation 사용사례를 뒤져서 전수검사해야 한다. 이보다 더 유지보수성이 좋을 수 없을까?이런 부분도 해결 가능하다. 옵션들을 객체로 싸서 넘기면 된다.
(function($) {
$.fn.useInvisibleNavigation = function (options = {}) {
const btn = $(this);
const defaultOptions = {
threshold: 5,
callback: function () {}
};
const option = $.extend(defaultOptions, options);
let count = 0;
btn.click(function () {
count++;
if (count == option.threshold && typeof option.callback == 'function') {
option.callback();
}
});
};
})(jQuery);
이것만으로도 훨씬 그럴듯해진다.
$('#gotoadmin').useInvisibleNavigation({
callback: function () { window.navigation.href = '/admin'; }
});
$('#showsuperadmin').useInvisibleNavigation({
threshold: 10,
callback: function () { $('#superadmin').show(); }
});
$.extend는 jQuery 내장 메소드로서 객체를 확장해 준다. 짐작하다시피 앞의 것에 뒤의 것을 덧붙여 준다.true를 넘기면 recursive merge를 해주므로, 2단계 이상의 복잡한 defaultOptions 설계도 가능하다.option.callback이 더 이상 undefined가 아니어도 된다.마지막으로 한 가지 주제만 더 생각해 보자.
개인적으로는 DOM 안에
<button>이 일일이 준비되어 있어야 한다는 부분이 이상하다. 어차피 투명하고 크기 있는 버튼이다. 이걸 플러그인이 알아서 만들어서 (내가 지정한 위치에) 알아서 삽입하게 바꾸고 싶다.
그렇다면 혹시 .useInvisibleNavigation() 플러그인이 button을 DOM에 삽입하는 것까지도 할 수 있을까?
당연히 된다.
jQuery니까.
// 이름 약간 바꿈
$.fn.addInvisibleNavigation = function (options = {}) {
const container = $(this); // 상수명에 주목
const defaultOptions = {
threshold: 5,
callback: function () {}
};
const option = $.extend(defaultOptions, options);
let count = 0;
// 여기서부터 주목
const btn = $('<button></button>');
btn.css({
'min-width': '4em',
'min-height': '4em'
});
btn.click(function () {
count++;
if (count == option.threshold && typeof option.callback == 'function') {
option.callback();
}
});
container.append(btn);
};
$(HTMLString) 형태로 새 jQuery 인스턴스를 생성하는 것이 가능하다..css(), .click() 등)가 모두 살아서 삽입된다.그리하여 다음과 같은 변경이 정상 작동하게 된다.
<nav>
<div class="left">
<a href="/bookings">예약관리</a>
</div>
<div class="right" id="has-admin-nav"></div>
</nav>
<script src="/invisible-navigation.js"></script>
<script>
$('#has-admin-nav').addInvisibleNavigation({
callback: function () { window.navigation.href = '/admin'; }
});
</script>
이제 맨 처음의 2개 코드와 이 코드를 비교해 보면, 생각보다 먼 길을 왔음을 알 수 있다.
과연 둘 중 어느 쪽이:
더 유리한 전략일까?
여러분의 jQuery 기반 코드를 덜 개판 만들 방법에 대해서 부디 현명한 판단 하길,,
직전의 사례는 솔직히 조금 억지다.
jQuery 플러그인이 임의의 DOM을 만들고 직접 통제/삽입할 수 있다는 사실이 빛을 발하는 사용사례는 다른 데 있다.
그 사례를 소개하며, jQuery 플러그인에서 최소한의 템플리팅을 하는 방법들에 대해 생각해 보고자 한다.