본문으로 바로가기

jQuery 플러그인 개발 및 $.extend()

category Web Tech/jQuery 2015. 3. 17. 16:55

플러그인 개발


서드파티 플러그인이 코딩을 발전시키는 데 큰 역할을 하는 건 사실이지만, 가끔은 한발 더 나아가고 싶을 때도 있다.

다른 개발자가 재사용할 수 있거나 스스로 재사용할 수 있는 필요에 의해 새로운 플러그인으로 제작하고 싶을 수도 있을 것이다.


이 포스팅에서는 일반적인 jQuery 플러그인을 제작하는 방법에 대해 살펴보겠다.




플러그인을 작성할 때에는 jQuery 라이브러리가 로드됐다고 가정해야 한다. 하지만 jQuery 플러그인에서 $ 별칭을 사용할 수 있다고 단정지을 수는 없다. 물론 $.noConflict() 메소드로 $ 별칭에 대한 제어를 양도할 수는 있다.

많은 개발자들은 jQuery 플러그인을 제작하는데 있어서 $를 사용하지 않으면 코드의 가독성이 떨어져 불편하고 다른 라이브러리와의 충돌을 염려한다. 이러한 문제를 극복하기 위해서 함수를 정의하고 바로 실행하는 방법으로 $ 별칭을 플러그인 내부에 지역변수로 정의하여 사용하는 것을 권장한다. 

이렇게 함수를 정의하는 즉시 실행하는 문법을 즉석 호출 함수 표현식(IIFE, Immediately Invoked Function Expression) 이라고 하며 그 형태는 다음과 같다.


javascript
(function($){

    // 플러그인 코드 작성 부분

})(jQuery);


위의 래핑된 함수는 jQuery 객체를 단일 인자로 가지며 호출받는 인자의 이름을 $로 지정했기 때문에 래핑된 함수 내에서 $ 별칭을 다른 라이브러리와의 충돌에 대한 염려없이 사용할 수가 있으며 또한 코드의 가독성도 높일 수가 있다.


jQuery 자체 기능 중 일부는 전역함수를 통해 사용한다. jQuery 전역함수는 실제로 jQuery객체의 메소드지만, 현실적으로 jQuery 네임스페이스(namespace)의 함수이다.


대표적인 jQuery의 전역함수로는 $.each(), $.map(), $.merge(), $.ajax()등등...이 있으며 jQuery 라이브러리에서 이와 같은 전역함수가 유틸리티 메소드(utility method)로 제공된다.


이런 유틸리티 메소드는 자주 사용하는 기능을 간편하게 사용할 수 있도록 도와주지만 이 함수들을 사용하지 않고 사용자 정의의 유틸리티 메소드를 직접 만드는 방법도 어렵지 않다.



다음의 코드는 $.sum() 유틸리티 메소드로 배열을 전달받아 배열에 있는 값들을 더하고 그 결과를 반환한다.


javascript
(function($){
    $.sum = function(array) {
        var total = 0;
        $.each(array, function(idx, value){
            value = $.trim(value);
            value = parseFloat(value) || 0;
            total += value;
        })
        return total;
    }
})(jQuery);

var result = $.sum([20,30,15,45]);
console.log(result); // 110


유틸리티 메소드에 대해 이해했다면 본격적으로 플러그인 제작에 들어가 보자.


서드파티 플러그인을 사용하는 경우에 사용자가 옵션 값을 지정하여 사용하는 경우가 많다.

이는 플러그인 사용자가 다양한 요건, 입맛에 맞게 사용하도록 재정의 하도록 도와준다.(어느 정도의 한계는 있지만..ㅎ)

이런 옵션 값을 지정해주기 위해 jQuery 플러그인에서는 $.extend() 유틸리티 메소드를 많이 사용한다.



$.extend() 를 사용하여 객체 확장하기


$.extend()는 인자로 객체를 넘겨주어 사용한다.

가장 뒷쪽에 있는 객체인 파라미터부터 앞으로 차례대로 병합,확장하여 병합된 결과는 첫번째 객체에서 병합된 값을 반환한다.


아래 코드를 통해 자세히 알아보자.

javascript
var obj1 = {
    apple: 0,
    banana: { weight: 52, price: 100 },
    cherry: 97
};
var obj2 = {
    banana: { price: 200 },
    durian: 100
};

// obj2를 obj1에 병합(merge)한다.
var obj3 = $.extend( obj1, obj2 );
console.log(obj1) // obj2가 obj1으로 병합되어 첫번째 인자의 값이 반환되기 때문에 기존 obj1이 갱신되었다.
console.log(obj3) // obj1과 obj3는 같은 결과를 반환한다.
// weight는 제거되었고 banana의 price값이 200으로 덮어씌여 졌고, durian이 추가된 것을 볼 수 있다.



- obj3 에 대한 콘솔 기록



javascript
// $.extend() 첫번째 인자값으로 Boolean값인 true를 넘겨줄 경
var obj4 = {
    foo: {bar: '123', baz: '456'},
    hello : 'world'
};
var obj5 = {
    foo: {car: '789'}
};
var obj6 = $.extend(obj4, obj5);
console.log(obj6);

var obj7 = {
    foo: {bar: '123', baz: '456'},
    hello : 'world'
};
var obj8 = {
    foo: {car: '789'}
};

var obj9 = $.extend(true, obj7, obj8);
console.log(obj7); // car:'789'가 추가되었다.
console.log(obj9);
// true를 인자값으로 넘겨주게 되면 extend 메소드는 깊은 복사(병합)를 하는데 같은 값은 병합을 하지만 원본 값을 그대로 유지한다.
// 하여 car가 기존 obj4에 있는 bar,baz를 제거하지 않고 그대로 복사를 한다.


- obj7 에 대한 콘솔 기록




- obj6 에 대한 콘솔 기록




다음의 코드에서 $.extend()에 첫번째 인자로 {}인 빈 객체를 정의하는 것에 주목하자. 


javascript
var object1 = {
    apple: 0,
    banana: { weight: 52, price: 100 },
    cherry: 97
};
var object2 = {
    banana: { price: 200 },
    durian: 100
};
var object = $.extend({}, object1, object2);
console.log(object1, object2);
console.log(object);
// object2를 object1에 병합한 결과를 빈 객체 {}에서 반환하고
// object1, object2의 원본값들 또한 그대로 유지한다.


- object1 에 대한 콘솔 기록


- object2에 대한 콘솔 기록



위와 같이 빈 객체를 첫번째 인자값으로 정의하는 방법은 플러그인 작성시에 default인 옵션값들을 정의해 두고 사용자가 임의로 옵션값을 주었을때 default로 정의된 옵션값을 사용자 정의의 옵션값으로 merge하는데 용이하게 사용된다.


javascript
var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };
var settings = $.extend({}, defaults, options);
// settings --> { validate: true, limit: 5, name: bar }
// options --> { validate: true, name: bar }


지금까지 네임스페이스 보호와 $.extend()의 사용법을 살펴보았다.


지금까지는 jQuery 플러그인에서의 기본 구조적인 코딩패턴일 뿐 jQuery 플러그인의 강력함을 누리려면 새로운 플러그인 메소드를 jQuery 객체에 추가하는 방법을 알아봐야 한다.


앞에서 전역함수를 추가할 때 jQuery 객체를 확장했다. ($.sum = function(){}... 과 같이)


이와 비슷하게 jQuery.fn 객체를 확장하면된다.


javascript
jQuery.fn.myMethod = methodDefinition;

// - myMethod : 새로 추가할 메소드 이름(플러그인 메소드로 사용할 메소드 이름)
// - methodDefinition : jQuery 객체에서 .myMethod()를 호출했을 때 실행되는 함수객체.

(function($){

    jQuery.fn.myMethod = function(){

        alert('플러그인 실행');

    }

})(jQuery)


jQuery.fn 객체는  jQuery.prototype 의 별칭이며 좀 더 간결하게 표현하려고 만들어 졌으며, jQuery.fn 으로 선언한 함수는 jQuery 객체의 prototype 프로퍼티에 새로운 메소드로 추가된다.


자바스크립트에서 제공하는 prototype 상속을 이용해 prototype 프로퍼티에 메소드를 추가하게 되면 새로 생성된 jQuery 객체나 기존 jQuery 긱체에서나 모두 새롭게 추가된 메소드를 사용할 수 있게 된다.


프로토타입에 관한 자세한 사항은 다루지 않겠다. 

여기서 프로토타입은 객체가 공통으로 가지는 공간 정도로만 이해하도록 하자.

프로토타입을 정확히 몰라도 플러그인을 제작하는데는 무리가 없으니까..^^;



다음으로 내부 코드를 작성해 보도록 하자.

플러그인 메소드를 설계할 때에는 jQuery 선택자 표현식이 0개, 또는 1개이상의 여러 개의 요소와 매치될 수 있음을 감안하여설계해야 한다.


예를 들어, 메인 페이지에 슬라이딩 갤러리를 플러그인으로 제작했다고 가정해보자.


헌데 메인 페이지에 슬라이딩 갤러리가 하나만 존재하는 것이 아니라 여러 개가 존재할 경우를 고려해야 한다는 것이다.

플러그인 메소드 만들어서 $('.slide-wrap').slidingPlugIn(); 이처럼 호출했는데 하나만 실행되고 나머지는 실행이 되지 않을 것이기 때문이다. 아니면 코드에 따라서 전혀 실행되지 않거나 모두 같은 모션으로 동작할 수도 있다는 것이다.


이런 시나리오를 감안하여 매치되는 요소의 개수와 관계없이 모든 요소에 적용하는 가장 간단한 방법은  .each()를 호출하는 것이다.


.each() 메소드는 묵시적인 반복을 수행하며, 이는 플러그인과 jQuery의 기본 메소드 사이의 일관성을 유지하는데 중요하다.



위 내용은 다음 코드 작성한다.

javascript
(function($){

    jQuery.fn.myMethod = function(){

        // return : return 문을 사용하게 되면 이 플러그인을 다른 jQuery 메소드와 함께 자유롭게 체이닝을 사용할 수 있다.
        return this.each(function(){  // this 키워드 : jQuery 객체 자체를 나타낸다.

            var $el = $(this);  // each 내부에서 사용되는 this는 DOM요소를 차례로 가리킨다.

        });

    }

})(jQuery);


위의 코드는 플러그인을 제작하는데 있어서 거의 규격과 같은 플러그인 패턴이라고 이해해도 좋다.

코드를 유심히 보면 첫번째에 나오는 this, 다음 라인의 this 그리고 return 문를 확인할 수 있을 것이다.



먼저 this는 무엇일까?

일단 this 키워드는 상황에 따라 각각 다른 데이터를 참조한다는 것을 알아야 한다.


플러그인 함수 내부에서의 this는 jQuery 객체 자체를 가리킨다. 이 this에 대한 프로퍼티를 콘솔로 확인해보면, 할당된 메소드들이 있고, 참조된 this인 요소에 대한 프로퍼티를 담고있는 배열이 있는데 이것은 this에 대한 엘리먼트가 하나라고 단정지을 수가 없기 때문에 배열로 엘리먼트를 구분하는 것이며 이 요소들은 배열이기 때문에 each와 같은 순환문을 통해 사용하는 것이다.


그리고 each 내부의 this 키워드는 DOM 요소를 가리키기 때문에 jQuery 래퍼객체($())로 감싸서 this를 참조해야 jQuery 메소드 사용이 가능해진다. 또한, jQuery의 강점 중 하나가 메소드 체인이 가능하다는 것인데, 예를 들어서 $('p').find().sibling()... 과 같은 코드를 사용할 수 있다는 것이다.


그렇기 때문에 모든 플러그인 메소드는 묵시적인 반복 each()문 외에도 jQuery 사용자는 체이닝 기능을 사용할 수 있어야 한다.


체이닝을 가능하기 위해서는 jQuery 객체를 반환해야 한다는 뜻으로 this에 의해 제공되는 jQuery 객체를 반환하면 되는 것이다.


즉, 메소드 체이닝이 끊어지지 않게 하기 위해서 this(jQuery객체)가 리턴되어 유지되는 것이 중요하다.


다음으로 플러그인 메소드에 유연성을 더해 보자. 플러그인 메소드는 사용자가 변경하고 싶을 만한 것을 파라미터로 지정할 수 있게 하여 필요에 따라 변경할 수 있게 해야한다.


이렇게 사용자 지정의 옵션값을 제공하기 전에 플러그인에 적당한 기본값을 제공해야 한다.


플러그인의 defaults값과 options값을 지정하는 방법을 알아보도록 하자.

javascript
function($){

    $.fn.myMethod = function(opts){

        return this.each(function(){

            var options = $.extend({}, $.fn.myMethod.defaults, opts || {});
            // $.extend() 를 공부한 것을 상기시켜보자.
            // 첫번째 인자값이 {} 빈 객체이므로 defaults 객체의 멤버와 opts(사용자정의 옵션값)이 merge되어 options에 담겨진다.

            var $el = $(this);
            // 플러그인 코드 작성

        });

    };

    // 기본값을 변경할 수 있게 하려면 다음과 같이 기본값에 대한 정의를 플러그인 메소드 밖에서 작성하여
    // 플러그인 밖에서도 접근할 수 있는 곳에 위치시켜야 한다.
    $.fn.myMethod.defaults = {
        active : 'on',
        selector : 'a',
        counter : 1
    }

})(jQuery);

$(function(){

    // 플러그인의 defaults 값을 외부에서 변경할 수 있다.
    $.fn.myMethod.defaults.active = 'current';

    // 사용자 정의의 옵션값을 정의하여 플러그인 메소드를 호출한다.
    $('h1').myMethod({
        selector : 'span',
        counter : 2
    })
});


지금까지의 내용이 일반적인 플러그인을 제작할 때의 규격, 일종의 패턴이라고 할 수 있다.

플러그인을 제작할 때의 방법론을 알아본 것이기 때문에 이를 이용한 플러그인 제작 코드는 본인의 몫이다.

하지만 플러그인이 왜 이런 패턴을 사용하는지를 알아야 하기 때문에 위의 내용들을 반드시 숙지하도록 하자.


개인적으로 매우 간단한 탭메뉴 타입을 플러그인으로 끄적여 보았는데 해당 코드는 카테고리 Code Factory의 탭메뉴 플러그인 소스를 참고..^^;




Jaehee's WebClub