>  기사  >  웹 프론트엔드  >  JavaScript 모듈식 프로그래밍에 대한 자세한 소개(코드 예제)

JavaScript 모듈식 프로그래밍에 대한 자세한 소개(코드 예제)

不言
不言앞으로
2019-03-11 17:07:082854검색

이 글은 JavaScript 모듈러 프로그래밍(코드 예제)에 대한 자세한 소개를 제공합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

모듈화란 무엇인가요?

모듈은 특정 기능을 구현하기 위한 메소드의 집합이고, 모듈화는 모듈 코드에 대한 자체 범위를 생성하여 공개 메소드와 변수만 외부에 노출시키는 것이며 이러한 메소드는 서로 고도로 분리되어 있습니다.

JS를 작성할 때 왜 모듈형 프로그래밍이 필요한가요?
프런트엔드가 웹페이지에서 일부 양식 제출과 클릭 상호작용만 처리할 때 JS 모듈화 개념이 강화되지 않았을 때 프런트엔드 로직이 복잡해지기 시작하면서 상호작용이 더 많아지고 양이 늘어났습니다. 데이터의 양이 점점 더 많아지면서 프런트엔드 JS 모듈형 프로그래밍에 대한 수요가 점점 더 강해지고 있습니다.

많은 시나리오에서 모듈성을 고려해야 합니다.

  1. 여러 사람이 팀으로 공동 작업할 때 다른 사람의 코드를 참조해야 합니다.
  2. 프로젝트 핸드오버, 다른 사람의 코드를 읽고 리팩터링할 때
  3. 코드 작성 중에 코드 확인 review 표준화되어 있는지, 문제는 없는지 코드를 작성한 후 작성한 코드가 아름다운지 검토해 보세요 :)
  4. 환경마다 환경 변수가 다릅니다
  5. 위의 시나리오를 기반으로 현재 JS 모듈화는 주로 서비스를 제공합니다. 이러한 목적은 다음과 같습니다.

코드 재사용성
  1. 기능 코드의 느슨한 결합
  2. 이름 지정 충돌 해결
  3. 코드 유지 관리성
  4. 코드 가독성
  5. 먼저 결론을 내리자면 JS의 모듈형 프로그래밍은 여러 단계를 거쳤습니다.

네임스페이스 형태의 코드 캡슐화
  1. 즉시 실행 기능(IIFE)을 통해 생성된 네임스페이스
  2. 서버 측 런타임 중 Nodejs의 CommonJS 사양
  3. 브라우저 측에서 모듈식으로 실행되는 AMD/CMD 사양
  4. CMD 및 AMD와 호환 가능 UMD 사양
  5. 언어 표준에서 지원되는 ES 모듈
  6. 먼저 결론 다이어그램을 제공합니다.


JavaScript 모듈식 프로그래밍에 대한 자세한 소개(코드 예제) 1. 네임스페이스

ES6 이전에는 JS에 블록 범위, 개인 변수 및 격리가 없었습니다. 메소드는 주로 함수 범위에 의존하며 공용 변수와 메소드의 격리는 주로 객체 속성 참조에 의존합니다.

캡슐화 기능

JS에 아직 모듈식 사양이 없었을 때 일부 공통 및 하위 수준 기능이 추상화되어 모듈성을 달성하기 위한 기능으로 분리되었습니다.

예를 들어 utils.js 도구 기능 파일을 작성하세요

//  utils.js
function add(x, y) {
    if(typeof x !== "number" || typeof y !== "number") return;
    return x + y;
}

function square(x) {
    if(typeof x !== "number") return;
    return x * x;
}

<script></script>
<script>
    add(2, 3);
    square(4);
</script>

By js 함수 파일을 분할하면 이때 공용 함수는 실제로 전역 개체 창 아래에 마운트됩니다. 다른 사람이 add라는 함수를 정의하려고 하거나 여러 js 파일을 병합하여 압축하면 이름 충돌 문제가 발생합니다.

전역 변수 아래에 마운트됨:

나중에 우리는 이름 충돌의 심각성을 줄이기 위해 전역 객체 리터럴 아래에 함수를 마운트하여 JAVA 패키지 개념을 사용하는 것을 고려했습니다.

var mathUtils1 = {
    add: function(x, y) {
        return x + y;
    },
}

var mathUtils2 = {
    add: function(x, y, z) {
        return x + y + z;
    },
}

mathUtils.add();

mathUtils.square();

이 메서드는 여전히 전역 변수를 생성하지만 패키지 경로가 길면 참조 메서드가

방식으로 코드를 참조하게 될 수 있습니다.

module1.subModule.subSubModule.add

IIFE

모듈에 프라이빗 변수가 있다는 점을 고려하여 IIFE(즉시 실행 표현식)를 사용하여 프라이빗 변수를 캡슐화하는 클로저를 만듭니다.

var module = (function(){
    var count = 0;
    return {
        inc: function(){
            count += 1;
        },
        dec: function(){
            count += -1;
        }
    }
})()

module.inc();
module.dec();

이렇게 하면 프라이빗 변수는 외부에서 접근할 수 없습니다. 모듈을 도입해야 합니다. 다른 종속성은 어떻습니까?
var utils = (function ($) {
    var $body = $("body"); 
    var _private = 0;
    var foo = function() {
        ...
    }
    var bar = function () {
        ...
    }
    
    return {
        foo: foo,
        bar: bar
    }
})(jQuery);

위의 모듈 캡슐화 방법을 모듈 모드라고 합니다. jQuery 시대에는 모듈 모드가 널리 사용되었습니다.

<script></script>
<script></script>
<script></script>
<script></script>
<script></script>

jQuery 플러그인은 JQuery.js 파일 뒤에 와야 하며, 로드 순서는 다음과 같습니다. 파일이 엄격하게 제한될수록 종속성이 많아지고, 종속성이 혼란스러워질수록 오류가 발생하기 쉽습니다.

2. CommonJS

Nodejs의 등장으로 인해 JavaScript가 서버 측 환경에서 실행될 수 있게 되었으며, 현재 CommonJS가 되는 통합 모듈 시스템을 구현하기 위한 표준 확립이 시급합니다.

// math.js
exports.add = function(x, y) {
    return x + y;
}

// base.js
var math = require("./math.js");
math.add(2, 3);  // 5

// 引用核心模块
var http = require('http');
http.createServer(...).listen(3000);

CommonJS는 각 모듈 내부에서 모듈이 현재 모듈을 나타낸다고 규정합니다. 이 모듈은 id, filename, added, parent, children,exports 등과 같은 속성을 가진 객체입니다. module.exports 속성은 모듈의 외부 출력 인터페이스를 나타냅니다. 현재 모듈 및 기타 파일을 로드하면 실제로 module.exports 변수를 읽습니다.

// utils.js
// 直接赋值给 module.exports 变量
module.exports = function () {
    console.log("I'm utils.js module");
}

// base.js
var util = require("./utils.js")
util();  // I'm utils.js module

或者挂载到 module.exports 对象下
module.exports.say = function () {
    console.log("I'm utils.js module");
}

// base.js
var util = require("./utils.js")
util.say();

편의를 위해 Node는 각 모듈에 대해 module.exports를 가리키는 내보내기 자유 변수를 제공합니다. 이는 각 모듈의 헤드에 이와 같은 줄을 두는 것과 같습니다.

var exports = module.exports;

exports와 module.exports는 동일한 참조 주소를 공유합니다. 내보내기에 값을 직접 할당하면 둘이 더 이상 동일한 메모리 주소를 가리키지 않게 되지만 결국 module.exports에는 적용되지 않습니다.

// module.exports 可以直接赋值
module.exports = 'Hello world';  

// exports 不能直接赋值
exports = 'Hello world';

CommonJS 요약:

CommonJS 사양 로딩 모듈은 동기식이며 서버 측에서 사용됩니다. CommonJS는 시작 시 내장 모듈을 메모리에 로드하므로 로드된 모듈도 메모리에 넣습니다. 따라서 Node 환경에서 동기 로딩을 사용하는데 큰 문제는 없을 것입니다.
또한 CommonJS 모듈은 출력 값의 복사본을 로드합니다. 즉, 외부 모듈의 출력 값이 변경되더라도 현재 모듈의 가져오기 값은 변경되지 않습니다.

三、AMD

CommonJS 规范的出现,使得 JS 模块化在 NodeJS 环境中得到了施展机会。但 CommonJS 如果应用在浏览器端,同步加载的机制会使得 JS 阻塞 UI 线程,造成页面卡顿。

利用模块加载后执行回调的机制,有了后面的 RequireJS 模块加载器, 由于加载机制不同,我们称这种模块规范为 AMD(Asynchromous Module Definition 异步模块定义)规范, 异步模块定义诞生于使用 XHR + eval 的开发经验,是 RequireJS 模块加载器对模块定义的规范化产出。

AMD 的模块写法:

// 模块名 utils
// 依赖 jQuery, underscore
// 模块导出 foo, bar 属性
<script></script>

// main.js
require.config({
  baseUrl: "script",
  paths: {
    "jquery": "jquery.min",
    "underscore": "underscore.min",
  }
});

// 定义 utils 模块,使用 jQuery 模块
define("utils", ["jQuery", "underscore"], function($, _) {
    var body = $("body");
    var deepClone = _.deepClone({...});
    return {
        foo: "hello",
        bar: "world"
    }
})

AMD 的特点在于:

  1. 延迟加载
  2. 依赖前置

AMD 支持兼容 CommonJS 写法:

define(function (require, exports, module){
  var someModule = require("someModule");
  var anotherModule = require("anotherModule");

  someModule.sayHi();
  anotherModule.sayBye();

  exports.asplode = function (){
    someModule.eat();
    anotherModule.play();
  };
});

四、CMD

SeaJS 是国内 JS 大神玉伯开发的模块加载器,基于 SeaJS 的模块机制,所有 JavaScript 模块都遵循 CMD(Common Module Definition) 模块定义规范.

CMD 模块的写法:

<script></script>
<script>
// seajs 的简单配置
seajs.config({
  base: "./script/",
  alias: {
    "jquery": "script/jquery/3.3.1/jquery.js"
  }
})

// 加载入口模块
seajs.use("./main")
</script>

// 定义模块
// utils.js
define(function(require, exports, module) {
  exports.each = function (arr) {
    // 实现代码 
  };

  exports.log = function (str) {
    // 实现代码
  };
});

// 输出模块
define(function(require, exports, module) {
  var util = require('./util.js');
  
  var a = require('./a'); //在需要时申明,依赖就近
  a.doSomething();
  
  exports.init = function() {
    // 实现代码
    util.log();
  };
});

CMD 和 AMD 规范的区别:  
AMD推崇依赖前置,CMD推崇依赖就近:  
AMD 的依赖需要提前定义,加载完后就会执行。
CMD 依赖可以就近书写,只有在用到某个模块的时候再去执行相应模块。
举个例子:

// main.js
define(function(require, exports, module) {
  console.log("I'm main");
  var mod1 = require("./mod1");
  mod1.say();
  var mod2 = require("./mod2");
  mod2.say();

  return {
    hello: function() {
      console.log("hello main");
    }
  };
});

// mod1.js
define(function() {
  console.log("I'm mod1");
  return {
    say: function() {
      console.log("say: I'm mod1");
    }
  };
});

// mod2.js
define(function() {
  console.log("I'm mod2");
  return {
    say: function() {
      console.log("say: I'm mod2");
    }
  };
});

以上代码分别用 Require.js 和 Sea.js 执行,打印结果如下:  
Require.js:  
先执行所有依赖中的代码

I'm mod1
I'm mod2
I'm main
say: I'm mod1
say: I'm mod2

Sea.js:  
用到依赖时,再执行依赖中的代码

I'm main

I'm mod1
say: I'm mod1
I'm mod2
say: I'm mod2

五、UMD

umd(Universal Module Definition) 是 AMD 和 CommonJS 的兼容性处理,提出了跨平台的解决方案。

(function (root, factory) {
    if (typeof exports === 'object') {
        // commonJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // 挂载到全局
        root.eventUtil = factory();
    }
})(this, function () {
    function myFunc(){};

    return {
        foo: myFunc
    };
});

应用 UMD 规范的 JS 文件其实就是一个立即执行函数,通过检验 JS 环境是否支持 CommonJS 或 AMD 再进行模块化定义。

六、ES6 Module

CommonJS 和 AMD 规范都只能在运行时确定依赖。而 ES6 在语言层面提出了模块化方案, ES6 module 模块编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 模块化这种加载称为“编译时加载”或者静态加载。

JavaScript 모듈식 프로그래밍에 대한 자세한 소개(코드 예제)

写法:

// math.js
// 命名导出
export function add(a, b){
    return a + b;
}
export function sub(a, b){
    return a - b;
}
// 命名导入
import { add, sub } from "./math.js";
add(2, 3);
sub(7, 2);

// 默认导出
export default function foo() {
  console.log('foo');
}
// 默认导入
import someModule from "./utils.js";
ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

另,在 webpack 对 ES Module 打包, ES Module 会编译成 require/exports 来执行的。

总结

JS 的模块化规范经过了模块模式、CommonJS、AMD/CMD、ES6 的演进,利用现在常用的 gulp、webpack 打包工具,非常方便我们编写模块化代码。掌握这几种模块化规范的区别和联系有助于提高代码的模块化质量,比如,CommonJS 输出的是值拷贝,ES6 Module 在静态代码解析时输出只读接口,AMD 是异步加载,推崇依赖前置,CMD 是依赖就近,延迟执行,在使用到模块时才去加载相应的依赖。

위 내용은 JavaScript 모듈식 프로그래밍에 대한 자세한 소개(코드 예제)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제