乐趣区

关于javascript:一文弄懂JS模块模块格式模块加载器和模块打包器

目前现代化的 JS 开发方式大行其道。撸代码时,你可能也会不了解为啥要应用这些简约的现代化工具。

接下来咱们就来一起学习下 js 模块、模块化解决方案、模块加载器和模块打包器的区别。

本文的次要用意是帮大家疾速了解古代前端 JS 开发的概念,并不会深刻去探讨每种工具和模式。

什么是模块?

模块是一块可复用的代码。它封装了实现细节,对外提供公开的 API 以便其余代码能够轻松加载和应用。

为啥要应用模块?

从技术利用的角度来说,模块并不是必须的。

模块化是一种代码组织形式。开发者们从六七十年代开始在各种编程语言中以多种形式应用了。

在 JS 中,模块化现实状况下应该容许咱们:

  • 形象代码:将性能实现抽离到特定的库,使用者无需去关注简单的外部实现逻辑。
  • 封装代码:如果不心愿代码被批改,则将代码暗藏在模块中。
  • 复用代码:以防止反复写雷同的代码。
  • 治理依赖:轻松扭转依赖而无需重写咱们的代码。

ES5 中的模块模式

ES5 级更早之前的版本,在设计时并没有思考到模块机制。随着工夫的推移,开发者想出了多种形式在 JS 中模仿实现模块机制。

这里咱们介绍 2 个简略的实现形式。

Immediately Invoked Function Expression(IIFE)

(function(){// ...dosomething})()

IIFE实质上定义一个立刻执行的匿名函数。

要留神的是匿名函数是被括号围起来的,在 JS 中,如果代码行是以关键字 function 开始,示意申明一个函数。

// 函数申明
function(){console.log('test');
}

间接调用函数申明会报错:

// 间接调用函数申明
function(){console.log('test');
}()

// => Uncaught SyntaxError: Unexpected token )

通过给函数加上圆括号,使其成为一个函数表达式:

// 函数表达式
(function() {console.log("test");
});

// => 返回 function(){ console.log('test') }

函数表达式返回一个函数,因而咱们能够间接调用它:

// IIFE:立刻执行函数表达式
(function() {console.log("test");
})();

// => console 中打印字符串 'test'

IIFE 带来的益处:

  • 将代码的复杂性封装到 IIFE 中,咱们无需关注其实现。
  • IIFE 中定义变量,防止对全局作用域造成净化。

不过,IIFE 没有提供依赖管理机制。

Revealing Module partten

Revealing Module parttenIIFE 相似,不同之处是它把 IIFE 的返回值赋给一个变量。

// 将模块导出为一个全局变量
var singleton = (function() {
  // 外部逻辑
  function sayHello() {console.log("Hello");
  }

  // 对外裸露的 API
  return {sayHello: sayHello,};
})();

这里咱们没有把匿名函数放在括号内,因为 function 关键字没有在行首。

// 拜访模块的办法
singleton.sayHello();
// => Hello

除了导出单例对象,模块还能够导出一个构造函数:

// 将模块裸露为全局变量
var Module = function() {
  // 外部逻辑
  function sayHello() {console.log("Hello");
  }

  // 导出 API
  return {sayHello: sayHello,};
};

留神,咱们没有在申明时立刻执行函数。

相同,咱们应用模块的构造函数来实例化一个模块:

var module = new Module();

拜访模块的公共 API:

module.sayHello();
// => Hello

这种模式的益处与 IIFE 雷同,不过同样它也不能给开发者提供依赖治理的机制。

随着 JS 的倒退,衍生了很多种模块定义语法,每种语法都有各自的长处和毛病。咱们称之为模块格局。

模块格局

模块格局是咱们用来定义一个模块的语法。

ES6 呈现之前,JS 没有提供官网的模块定义语法。因而,聪慧的开发者们提出了多种定义模块的形式。

一些广为应用的模块格局有:

  • Asynchronous Module Definition(AMD)
  • CommonJS
  • Universal Module Definition(UMD)
  • System.register
  • ES6 Module

上面咱们疾速过一下每种模块定义的形式。

AMD

AMD 模块运行在浏览器环境下,它应用 define 函数来定义模块。

define(['dep1', 'dep2'], function (dep1, dep2) {return function () {};});

CommonJS

CommonJS 模块运行在 nodejs 环境下,它应用 require 治理依赖,module.exports 来定义模块。

var dep1 = require('./dep1');  
var dep2 = require('./dep2');

module.exports = function(){// ...}

UMD

UMD 模块能够同时运行在浏览器和 Nodejs 环境下。

(function (root, factory) {if (typeof define === 'function' && define.amd) {define(['b'], factory);
  } else if (typeof module === 'object' && module.exports) {module.exports = factory(require('b'));
  } else {root.returnExports = factory(root.b);
  }
}(this, function (b) {return {};
}));

System.register

System.register 模块的设计用意是在 ES5 中反对 ES6 模块语法:

import {p as q} from './dep';

var s = 'local';

export function func() {return q;}

export class C {}

ES6 module

从 ES6 开始,js 提供了原生的模块机制。它应用 export 向外裸露 API:

// lib.js

// 对外导出 sayHello
export function sayHello(){console.log('Hello');
}

// 不对外导出
function somePrivateFunction(){// ...}

应用 import 导入另一个模块中的 API:

import {sayHello} from './lib';
sayHello();

为导入的 API 应用别名:

import {sayHello as say} from './lib';

say();  
// => Hello

导入整个模块:

import * as lib from './lib';

lib.sayHello();  
// => Hello

模块中定义默认导出项:

// lib.js

// 导出默认函数
export default function sayHello(){console.log('Hello');
}

// 导出非默认函数
export function sayGoodbye(){console.log('Goodbye');
}

而后应用如下形式导入:

import sayHello, {sayGoodbye} from './lib';

sayHello();  
// => Hello

sayGoodbye();  
// => Goodbye

导出所有你想导出的内容:

// lib.js

// 导出默认函数
export default function sayHello(){console.log('Hello');
}

// 导出非默认函数
export function sayGoodbye(){console.log('Goodbye');
}

// 导出简略值
export const apiUrl = '...';

// 导出对象
export const settings = {debug: true}

遗憾的是,不是所有的浏览器都反对了原生的模块语法。不过咱们在代码中应用原生的模块语法,而后借助 babel 等转译工具将 ES6 模块语法转译成 ES5 所反对的 AMD 或 CommonJS 等模块语法。

模块加载器

模块加载器负责解析和加载以特定模块语法定义的模块。模块加载器在运行时执行:

  • 首先在浏览器中加载模块加载器
  • 通知模块加载器利用的 js 入口文件
  • 加载器去下载并解析 js 入口文件
  • 加载器按需下载所有的 js 文件

关上浏览器的调试面板,你会看到加载器按需加载的 js 文件。

以下是两个常见的模块加载器:

  • RequireJS:AMD 模块加载器
  • SystemJS:CommonJS、AMD、UMD 以及 System.register 模块加载器

模块打包器

打包器能够替换加载器。不过与加载器不同,打包器是在构建时运行:

  • 应用打包器生成一个 js 文件(例如 app.js)
  • 在浏览器中加载该文件

如果你在浏览器的调试工具的网络面板中,只会看到浏览器只加载了一个文件。浏览器中不须要模块加载器。所有的代码都被打包在了一个 js 文件中。

在按需加载的场景下,打包器通常也会提供模块加载器。以按需申请对应的 js 文件。

列举几个常见打包器:

  • webpack
  • rollup
  • browserify

总结

为了更好地了解古代 JS 开发环境中的各种工具,了解模块、模块格局、模块加载器和模块打包器之间的区别很重要。

模块 是可复用的代码片段,它封装了实现细节,并对外裸露一个公开 API,以便其余代码加载应用。

模块格局 是用来定义模块的语法。很早之前就曾经呈现了各种模块格局,如 AMD,CommonJS,UMD 和 System.register。从 ES6 开始原生提供了模块定义语法。

模块加载器 在运行时解释并加载以特定模块格局编写的模块,常见的加载器有 RequireJS 和 SystemJS。

模块打包器 能够代替模块加载器。它在构建时生成一个 蕴含所有代码的 JS 包。常见的打包器有 webpack、rollup 和 browserify。

好啦,读到这里置信你曾经对古代 JS 开发的常识有了更好地了解了。

退出移动版