[Javascript] Jquery module 구조 살펴보기

BBinss·2020년 12월 22일
0
post-thumbnail

javascript module 간략 개요.

javascript는 브라우저에서 동작되는 언어이기 때문에, 서버에서 동작되는 언어들과 같이 커널을 통해 직접적으로 다른 파일을 읽어서 로딩하는 등의 import 형태의 모듈화 방식을 쓸 수가 없다.

물론 nodeJs를 통해 서버에서 javascript를 사용할 수 있지만, c++ core 적인 부분 위에 함수 매핑 형태로 구현되어 있는 것이고
ES6(ECMA2015)에서 제공하는 import/export 함수 역시 서버 환경이 아닌 브라우저에서 사용하려면 해당 기능을 사용 가능하도록 하는 Babel과 같은 트랜스파일러가 필요하다.

간단히 생각해서 브라우저에서 javascript를 호출하게 되면 클라이언트(사용자 PC)에 캐시로 저장은 하겠지만, 해당 javascript 파일에서 클라이언트(사용자 PC)에 접근해서 다른 javascript 모듈을 import 하는 건 불가능하다는 것이다.

그럼 module 형태로 쓰기 위해선 어떻게 해야 할까?

  • 위에 언급한 대로 트랜스파일러를 이용하여 ES6 import/export 를 사용하거나 commonJs 형태로 export/require()를 통해 모듈화 및 로딩.
  • 브라우저에서 동적으로 로딩되는 js 파일들을 모듈화, 의존성 관리를 해주는 requireJs 같은 라이브러리.
  • js 파일 하나에서 모듈화를 내부 구현하고 전역(window 객체)으로 공유.

이 포스트에서 얘기할 부분은 세 번째 js 파일 하나에서 모듈화를 구현하고 전역으로 공유하는 부분이다.
가장 오래된 형태이며, 그 가운데 가장 공용적으로 사용해 왔던 jquery가 어떤 구조를 가지고 있는지 살펴보려고 한다.

소스 분석

(function (global, factory) {
  'use strict';

  /* tag.1 */
  if (typeof module === "object" && typeof module.exports === "object") {
    module.exports = global.document ? factory(global, true) :
      function(w) {
        if (!w.document) {
          throw new Error("jQuery requires a window with a document");
        }
        return factory(w);
      };
  } else {
    factory(global);
  }

})(typeof window !== "undefined" ? window : this, function (window, noGlobal) {
  /* tag.2 */
  var jQuery = function (selector, context) {
    return new jQuery.fn.init(selector, context);
  };

  /* tag.3 */
  jQuery.fn = jQuery.prototype = {
    each: function (callback) {},
    map: function (callback) {}
    /* ... */
  }

  /* tag.4 */
  if (typeof define === "function" && define.amd) {
    define('jquery', [], function () {
      return jQuery;
    });
  }

  /* tag.5 */
  var init = jQuery.fn.init = function (selector, context, root) {
    /* ... */
  }

  init.prototype = jQuery.fn;

  /* tag.6 */
  if (!noGlobal) {
    window.jQuery = window.$ = jQuery;
  }

  return jQuery;
});

tag.1 익명 함수 형태를 통해서 factory 인자 값에 global 객체를 전달하고 있다. 간단하게 구현체를 인자로 전달받아 모듈로 선언하는 부분으로 보면 된다.
commonJs 모듈 지원을 위한 module.exports 처리 부분이 존재한다.

tag.2 첫번째 핵심부분이다. 사용자한테 jQuery 객체에 모든 기능을 담아서 공유해주고 싶다.
또한 내부 구현 부분에 대해서는 직접적으로 접근을 차단하는 캡슐화 형태를 가져가고 싶다. 실제 구현부는 jQuery.fn.init() 내부에 있으며 사용자는 jQuery 함수를 통해 접근하도록 jQuery 객체를 선언.

tag.3, tag.5 prototype으로 선언된 내용은 new를 통해서 생성했을 때 인스턴스를 통해 접근 가능하다. (OOP 언어의 class 멤버변수 형태의 구현이 가능)
실제 기능들은 jQuery.prototype에 정의를 해두고, init.prototype에 jQuery.fn의 참조를 가지게 해서
jQuery.fn.init.prototype = jQuery.prototype 참조를 가지게 한다.

이를 통해 tag.2 에서 new jQuery.fn.init()가 실행되면 jQuery.prototype 의 모든 메소드에 접근하게 된다.

tag.4 requireJs AMD 모듈 지원 부분.

tag.6 jQuery 객체는 위에서 설명한대로 실행시 모든 기능을 담은 인스턴스를 리턴해 준다. 이제 사용자에게 jQuery 객체만 전달하면 되는데 이미 requireJs define 이라든지
!noGlobal 일 때 전역으로 공유하였다. 그외 tag.1의 factory 함수로 jQuery를 리턴해 준다.

결론

jQuery는 prototype을 통해 내부 기능을 정의하고 new를 통해 생성한 객체를 전역으로 공유하였다.
이러한 모듈 형태의 핵심은 익명 함수를 사용하여 내가 구현하고자 하는 모듈을 캡슐화하고 사용자가 접근하는 모듈 객체만을 전역으로 공유한다.

간단한 prototype module 뼈대 공유하면서 글을 마무리합니다.

(function () {
  "use strict";

  var mod = function () {}

  mod.prototype = {
    func1: function () {},
    func2: function () {}
  }

  var _mod = window.mod = new mod();
})();
profile
궁빈

1개의 댓글

comment-user-thumbnail
2022년 7월 14일

감사합니다.!! " prototype module " 뼈대 공유해주셔서 큰 문제 해결했습니다.~

답글 달기