Hello World

jQuery의 확장집합(wrapped set)은 Array? 본문

jQuery/Tips

jQuery의 확장집합(wrapped set)은 Array?

EnterKey 2012. 9. 21. 18:02
반응형

jQuery를 사용해 보면 알겠지만, jQuery의 핵심은 확장집합(wrapped set, jQuery Object)이다.

다음처럼 사용하면


var aaa = $('.foo');

aaa는 확장집합을 가리키게 된다.

즉 jQuery에 selector를 넘겨주면 그 selector에 해당하는 객체를 찾아서 모아놓은 확장집합이라는 객체가 리턴된다.


그런데 사용해 보면 알겠지만, 이 확장집합은 Array처럼 작동한다.

즉 aaa[2] 이나, aaa.length, aaa.push(...) 등등을 사용할 수 있다는 말이다. 또한 당연한 이야기지만 aaa.html(...), aaa.each(...) 등등의 확장메소드도 사용할 수 있다.

그러나 이 확장집합에 instanceof Array를 해보면 false가 리턴된다. 즉 Array 객체는 아니라는 말이다.


Array가 아닌 객체가 Array처럼 작동한다는 것인데, 이게 어떻게 가능할까?

만약 javascript가 다중 상속을 지원하는 언어라면, 확장집합을 Array도 상속받고, jQuery도 상속받는 형태로 만들 수도 있을 것이다. 하지만 javascript는 그런 언어가 아니다. 그런데 어떻게 2가지 타입의 기능을 모두 가지게 만들 수 있을까?


그럼 생각을 바꿔보자. javascript에서 Array는 뭐지?


javascript에서 새로운 객체 타입을 정의하는 과정은, 우선 그 객체를 생성할 때 호출하게 될 생성자 함수(constructor)를 정의하고, 이 새로운 타입의 객체가 가지게 될 멤버 함수들을 이 생성자 함수의 prototype에 추가하는 것이라 할 수 있다.

그런데 이 새로운 타입의 인스턴스를 생성할 때의 과정을 생각해 보면, 즉


new NewType(...)

처럼 한다는 것은, Object 객체를 하나를 메모리에 생성해서, 그 생성된 Object 객체를 생성자 함수 내부에서 this로 참조할 수 있도록 해둔다. 그러면 생성자 함수는 이 this를 통해 참조되는 Object 객체에 필요한 property를 추가하는 등의 적당한 조작을 한 다음에, 자신의 prototype을 연결해서 넘겨준다.



그런데 javascript에서 Object는 기본적으로 연관배열이다. 즉 key와 value를 쌍으로 해서 정보를 저장하고 있는 Map같은 것이라는 말이다. 따라서 동적으로

key와 value를 삽입하거나 제거할 수 있다. 따라서 new NewType(...)으로 생성된 객체도 기본적으로 Object이므로 동적으로 key와 value를 추가하거나 제거할 수 있다.

또한 prototype에 추가되어 있는 멤버 함수안에서도 this가 가리키는 것은 기본적으로 생성자 함수를 거친 Object 객체다. 따라서 멤버함수라는 것은 this에 의해 참조되는 Object객체에 포함되어 있는 property를 엑세스 하고, 추가하고, 변경하고, 삭제하는 등의 작업을 하는 함수다.


참고로 한가지 더 알아 둘 것이 있는데, new 연산자가 반환하는 값에 관한 것이다.

보통 생성자 함수는 내부에서 뭔가를 수행하기만 하고 특별히 리턴을 하지 않는 것이 일반적인데, 이때는 this에 담겨있는 객체가 그대로 반환값이 된다.

그러나 만약 생성자 함수가 뭔가 다른 객체를 리턴하게 되면, this에 있는 객체는 무시되고 생성자 함수가 리턴한  객체가 반환값이 된다.


위의 논리를 가지고 Array 타입을 따져보면,

Array 타입이라는 말은, 'Array'라는 이름을 가진 함수(function)가 있고, 즉 생성자 함수가 Array이고, 이 Array 함수 객체의 prototype에는 Array타입이 가지게 될 멤버 혹은 property가 추가되어 있다는 의미다.

new Array()를 한다는 것도, Object객체를 하나 생성해서 Array 함수를 거치게 만든다음, 그 객체에 Array의 prototype을 연결하는 과정이라는 말이다.


그리고 또 한가지.

Object는 연관배열이라고 했는데, 이때 key와 value는 어떤 타입이든 관계없다. 보통 key는 String으로 하지만, 여기에 숫자(Number)를 사용하더라도 이상할게 없다.


var obj = {};

obj["aaa"] = "abcd";   //ok

obj[2] = "defg";          //ok

즉 Array 타입은 기본적으로 key를 정수 형태로 사용하는 연관배열이라고 말할 수 있는 것이다.


결국 가만히 따져보면 Object 객체에 length라는 property만 추가하고, key를 정수로 하는 연관배열이면 Array와 다를바가 없어진다. w3schools에서 Array타입을 살펴보면 Object타입과 length만 다른 뿐이다.

또한 Array의 멤버함수라는 것도 Array.prototype에 추가되어 있는 함수들이다. javascript에서 함수는 완전히 독립적으로 존재할 수 있는 객체로서, 이 함수가 안에서 참조하는 this는 실행시에 결정된다. 즉 이 함수들을 참조해서 가져다가 this만 적당히 넣어줄 수 있으면 다른곳에서도 충분히 사용할 수 있다는 말이된다.


var push = Array.prototype.push;


var obj = {};

obj.length = 0;


push.call( obj, "123" );

push.call( obj, "456" );


alert( obj[1] );  // 456 출력


or


var push = Array.prototype.push;


var obj = {};

obj.length = 0;

obj.push = push;


obj.push( "123" );

obj.push( "456" );


alert( obj[1] ); //456 출력

위처럼 하면 obj는 Array는 아니지만 Array 처럼 사용할 수 있게 된다.


jQuery의 소스를 보면 다음과 같이 되어 있다.


...

push = Array.prototype.push,

slice = Array.prototype.slice,

indexOf = Array.prototype.indexOf,

...


jQuery.fn = jQuery.prototype = {

        ...,

        init : function( ... ) {

                ...

                this.length = ...;

                ...

        },

        ...

        push : push

        ...

};

...

즉 jQuery에서 new init( selector... )에 의해 반환되는 확장집합은, 기본적으로 length와 push를 가지게 된다. Array는 아니지만 Array처럼 작동할 수 있는 property와 멤버 변수를 가진다는 말이다. 또한 [0]같이 정수로 엑세스하는 것은 굳이 Array가 아니어도 가능하다.

결국 jQuery의 확장집합은 Array는 아니지만 Array처럼 작동될 수 있는 것이다.



ps)

사실 생각해 보면, push같은 함수는 굳이 Array.prototype에 있는 것을 사용하지 않고 직접 새로 작성해도 될 것 같다.

그런데 jQuery에서는 굳이 Array.prototype에 있는 함수를 사용하는데 바로 최적화 때문이라고 한다.


이 Array는 javascript의 기본 타입이며 아주 자주 사용된다. 보통 이 정도 되면 javascript엔진에서는 이런 것들을 특별히 좀더 빠르게 실행되도록 최적화를 해 두었을 가능성이 크다. 순수 javascript로 만드는 것만으로는 이룰 수 없는 내부적이 최적화 말이다. 그렇기 때문에 이렇게 만들어둔 함수를 사용하는 것은 당연히 빠르게 작동할 확률이 크며, jQuery제작자 들은 이것을 이용한 것이다.

반응형

'jQuery > Tips' 카테고리의 다른 글

Javascript Tips  (0) 2016.01.25
Comments