>  기사  >  웹 프론트엔드  >  JavaScript 모듈성에 대한 간략한 소개

JavaScript 모듈성에 대한 간략한 소개

阿神
阿神원래의
2017-02-03 17:48:011236검색

머리말

모듈화와 관련하여 가장 직접적인 표현은 우리가 작성한 require 및 import 키워드입니다. 관련 정보를 찾아보면 CommonJS, CMD AMD와 같은 용어는 물론 RequireJS, SeaJS와 같은 익숙하지 않은 프레임워크도 만나게 될 것입니다. 예를 들어 SeaJS 공식 웹사이트에서는 "간단하고 친숙한 모듈 정의 사양, Sea.js는 CMD 사양을 따릅니다. 자연스럽고 직관적인 코드 구성, 종속성 자동 로딩..."

과 같이 프론트엔드 초보자인 저는 솔직히 그 사람이 혼란스러워 보였고 이해하지 못하는 모습을 보였습니다. 나의 평소 스타일에 따르면, 무언가를 소개하기 전에 항상 그것이 왜 필요한지 설명해야 합니다.


JavaScript 기초

클라이언트 작업을 하는 학생은 다음과 같습니다. OC에 익숙함 #import "classname", Swift의 모듈 및 파일 수정자, Java의 import package+class 모드에 익숙해야 합니다. 우리는 파일을 참조하는 것이 클래스를 참조하는 패턴에 익숙합니다. 그러나 JavaScript와 같은 동적 언어에서는 상황이 변경되었습니다. 예:

<html>
  <head>
    <script type="text/javascript" src="index.js"></script>
  </head>

  <body>
    <p id="hello"> Hello Wrold </p>
    <input type="button" onclick="onPress()" value="Click me" />
  </body>
 </html>
// index.js
function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = &#39;Hello bestswifter&#39;;
}

HTML의 3f1c4e4b6b16bbbd69b2ee476dc4f83a 태그는 가져오기로 이해될 수 있으므로 버튼의 onclick 이벤트는 The에 정의된 이벤트를 호출할 수 있습니다. index.js의 onPress 함수.

프로젝트가 진행되면서 클릭한 텍스트가 동적으로 생성되고 다른 JS 파일에 의해 생성되어야 한다는 사실을 알게 되면 간단한 index.js로는 해당 작업을 수행할 수 없게 됩니다. 생성된 내용은 math.js 파일에 정의되어 있다고 가정합니다:

// math.js
function add(a, b) {
    return a + b;
}

클라이언트의 생각에 따르면, 이때 index.js 파일은 다음과 같이 작성되어야 합니다:

// index.js
import "math.js"

function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = add(1, 2);
}

안타깝게도 , JavaScript는 이를 지원하지 않습니다. import를 작성하는 이러한 방식은 한 JS 파일의 메소드가 다른 JS 파일의 메소드를 전혀 참조할 수 없음을 의미합니다. 올바른 해결책은 index.js에서 add 메소드를 직접 호출하는 동시에 index.html에서 math.js를 참조하는 것입니다.

<html>
  <head>
    <script type="text/javascript" src="index.js"></script>
    <script type="text/javascript" src="math.js"></script>
  </head>

  <body>
    <p id="hello"> Hello Wrold </p>
    <input type="button" onclick="onPress()" value="Click me" />
  </body>
</html>
// index.js
function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = add(1, 2);
}

이 작성 ​​방법이 우아하지 않다는 것을 알 수 있습니다, index.js 다른 JS와 다릅니다. 파일의 내용을 제어할 수 없습니다. add 메소드를 호출할 수 있는지 여부는 전적으로 HTML 파일이 다른 JS 파일을 올바르게 참조했는지 여부에 따라 달라집니다.


사전 모듈화

방금 언급한 문제점 사실, 두 가지 유형으로 나눌 수 있습니다.

1.index.js는 가져올 수 없으며 HTML 참조에 의존합니다.

2.index.js는 add 메소드의 소스를 구분할 수 없으며 부족합니다. a 네임스페이스

의 개념은 나중에 두 번째 질문을 해결해 보겠습니다. 예를 들어 먼저 함수를 객체에 넣어서 사용자가 이를 호출할 수 있도록 하세요. 여러 메소드:

//index.js 
function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    p.innerHTML = math.add(1, 2);
}

//math.js
var math = {
    base: 0,
    add: function(a, b) {
        return a + b + base;
    },
};

index.js에서 단순화된 버전의 네임스페이스(즉, 수학)를 지정할 수 있음을 알 수 있습니다. 하지만 여전히 작은 문제가 있습니다. 예를 들어 기본 속성이 외부 세계에 노출되고 수정될 수도 있습니다. 따라서 더 좋은 방법은 내부 속성을 숨기기 위해 클로저에서 수학을 정의하는 것입니다.

// math.js
var math = (function() {
    var base = 0;
    return {
        add: function(a, b) {
            return a + b + base;
        },
    };
})();

지금까지 우리는 모듈의 정의와 사용을 구현했습니다. 그러나 모듈화의 본질 중 하나는 네임스페이스에 있습니다. 이는 수학 모듈이 전역적이지 않고 요청 시 가져오기를 희망한다는 것을 의미합니다. 이러한 방식으로 여러 파일이 동일한 이름을 가진 객체를 노출하더라도 아무 일도 일어나지 않을 것입니다. 문제. node.js와 마찬가지로 노출해야 하는 모듈은 자체 내보내기 콘텐츠를 정의한 다음 호출자가 require 메서드를 사용합니다.

실제로 node.js의 작업 모드를 간단하게 시뮬레이션하고 중간 레이어를 추가하여 문제를 해결할 수 있습니다. 먼저 전역 변수를 정의합니다:

// global.js
var module = {
    exports: {}, // 用来存储所有暴露的内容
};

그런 다음 개체를 수학에 노출합니다. js:

var math = (function() {
    var base = 0;
    return {
        add: function(a, b) {
            return a + b + base;
        },
    };
})();

module.exports.math = math;

사용자 index.js는 이제 다음과 같습니다:

var math = module.exports.math;

function onPress() {
    var p = document.getElementById(&#39;hello&#39;);
    // math
    p.innerHTML = math.add(1, 2);
}


기존 모듈식 솔루션

위의 간단한 모듈식 접근 방식에는 몇 가지 사소한 문제가 있습니다. 우선, index.js는 math.js 실행에 엄격하게 의존해야 합니다. 왜냐하면 math.js가 실행된 후에만 전역 module.export에 등록되기 때문입니다. 이를 위해서는 개발자가 js 파일의 로드 순서를 수동으로 관리해야 합니다. 프로젝트가 커질수록 종속성을 유지하는 것이 점점 더 복잡해집니다.

둘째, JS 파일을 로드할 때 브라우저가 웹 페이지 렌더링을 중지하므로 JS 파일의 비동기 온디맨드 로드도 필요합니다.

마지막 문제는 이전에 제공된 모듈화 솔루션의 단순화된 버전이 모듈 네임스페이스를 해결하지 못한다는 것입니다. 동일한 내보내기가 여전히 이전 콘텐츠를 대체하며 해결 방법은 "파일 경로< ;-- > 컨텐츠 내보내기' 테이블을 선택하고 파일 경로에 따라 로드합니다.

위의 요구 사항을 바탕으로 다양한 모듈식 솔루션 세트가 시장에 출시되었습니다. 여러 가지 표준이 존재하는 이유는 사실 프론트엔드의 특성 때문입니다. 통일된 표준이 없기 때문에 많은 경우 위에서 언급한 내보내기 및 요구와 같은 작업을 수행할 때 모든 사람이 규칙에 의존합니다. 코드 제공자가 내보내기 내용을 module.exports에 저장하고 사용자가 module.export를 읽는다면 당연히 헛된 일입니다. 뿐만 아니라 각 사양의 구현 방법과 사용 시나리오도 다릅니다.


CommonJS

比较知名的规范有 CommonJS、AMD 和 CMD。而知名框架 Node.js、RequireJS 和 Seajs 分别实现了上述规范。

最早的规范是 CommonJS,Node.js 使用了这一规范。这一规范和我们之前的做法比较类似,是同步加载 JS 脚本。这么做在服务端毫无问题,因为文件都存在磁盘上,然而浏览器的特性决定了 JS 脚本需要异步加载,否则就会失去响应,因此 CommonJS 规范无法直接在浏览器中使用。


AMD

浏览器端著名的模块管理工具 Require.js 的做法是异步加载,通过 Webworker 的 importScripts(url); 函数加载 JS 脚本,然后执行当初注册的回调。Require.js 的写法是:

require([&#39;myModule1&#39;, &#39;myModule2&#39;], function (m1, m2){
    // 主回调逻辑
    m1.printName();
    m2.printName();
});

由于这两个模块是异步下载,因此哪个模块先被下载、执行并不确定,但可以肯定的是主回调一定在所有依赖都被加载完成后才执行。

Require.js 的这种写法也被称为前置加载,在写主逻辑之前必须指定所有的依赖,同时这些依赖也会立刻被异步加载。

由 Require.js 引申出来的规范被称为 AMD(Asynchronous Module Definition)。


CMD

另一种优秀的模块管理工具是 Sea.js,它的写法是:

define(function(require, exports, module) {
    var foo = require(&#39;foo&#39;); // 同步
    foo.add(1, 2); 
    ...
    require.async(&#39;math&#39;, function(math) { // 异步
        math.add(1, 2);
    });
});

Sea.js 也被称为就近加载,从它的写法上可以很明显的看到和 Require.js 的不同。我们可以在需要用到依赖的时候才申明。

Sea.js 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。

由 Sea.js 引申出来的规范被称为 CMD(Common Module Definition)。


ES 6 模块化

在 ES6 中,我们使用 export 关键字来导出模块,使用 import 关键字引用模块。需要说明的是,ES 6 的这套标准和目前的标准没有直接关系,目前也很少有 JS 引擎能直接支持。因此 Babel 的做法实际上是将不被支持的 import 翻译成目前已被支持的 require。

尽管目前使用 import 和 require 的区别不大(本质上是一回事),但依然强烈推荐使用 import 关键字,因为一旦 JS 引擎能够解析 ES 6 的 import 关键字,整个实现方式就会和目前发生比较大的变化。如果目前就开始使用 import 关键字,将来代码的改动会非常小。


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.