简介

在很久以前,js只是简单的作为浏览器的交互操作而存在,一般都是非常短小的脚本,所以都是独立存在的。

但是随着现代浏览器的发展,特别是nodejs的出现,js可以做的事情变得越来越多也越来越复杂。于是我们就需要模块系统来组织不同用途的脚本,进行逻辑的区分和引用。

今天将会给大家介绍一下js中的模块系统。

CommonJS和Nodejs

CommonJS是由Mozilla公司在2009年1月份提出来了。没错,就是那个firfox的公司。

最初的名字叫做ServerJS,在2009年8月的时候为了表示这个标准的通用性,改名为CommonJS。

CommonJS最主要的应用就是服务端的nodejs了。浏览器端是不直接支持CommonJS的,如果要在浏览器端使用,则需要进行转换。

CommonJS使用require()来引入模块,使用module.exports来导出模块。

我们看一个CommonJS的例子:

  1. require("module");
  2. require("../file.js");
  3. exports.doStuff = function() {};
  4. module.exports = someValue;

注意,CommonJS是同步加载的。

AMD异步模块加载

AMD的全称是Asynchronous Module Definition 。它提供了一个异步加载模块的模式。

AMD是RequireJS在推广过程中对模块定义的规范化产出。

异步加载的好处就是可以在需要使用模块的时候再进行加载,从而减少了一次性全部加载的时间,尤其是在浏览器端,可以提升用户的体验。

看下AMD加载模块的定义:

  1. define(id?, dependencies?, factory);

AMD是通过define来定义和加载依赖模块的。

其中id表示要定义的模块的名字,dependencies表示这个模块的依赖模块,factory是一个函数,用来初始化模块或者对象。

我们看一个例子:

  1. define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
  2. exports.verb = function() {
  3. return beta.verb();
  4. //Or:
  5. return require("beta").verb();
  6. }
  7. });

这个例子中,我们定义了一个alpha模块,这个模块需要依赖"require", "exports", "beta"三个模块。

并且在factory中导出了beta模块的verb方法。

define中id和dependencies都不是必须的:

  1. //无id
  2. define(["alpha"], function (alpha) {
  3. return {
  4. verb: function(){
  5. return alpha.verb() + 2;
  6. }
  7. };
  8. });
  9. //无依赖
  10. define({
  11. add: function(x, y){
  12. return x + y;
  13. }
  14. });

甚至我们可以在AMD中使用CommonJS:

  1. define(function (require, exports, module) {
  2. var a = require('a'),
  3. b = require('b');
  4. exports.action = function () {};
  5. });

定义完之后,AMD使用require来加载模块:

  1. require([dependencies], function(){});

第一个参数是依赖模块,第二个参数是回调函数,会在前面的依赖模块都加载完毕之后进行调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。

  1. require(["module", "../file"], function(module, file) { /* ... */ });

require加载模块是异步加载的,但是后面的回调函数只会在所有的模块都加载完毕之后才运行。

CMD

CMD是SeaJS在推广过程中对模块定义的规范化产出。它的全称是Common Module Definition。

CMD也是使用define来定义模块的,CMD推崇一个文件作为一个模块:

  1. define(id?, deps?, factory)

看起来和AMD的define很类似,都有id,依赖模块和factory。

这里的factory是一个函数,带有三个参数,function(require, exports, module)

我们可以在factory中通过require来加载需要使用的模块,通过exports来导出对外暴露的模块,module表示的是当前模块。

我们看一个例子:

  1. // 定义模块 myModule.js
  2. define(function(require, exports, module) {
  3. var $ = require('jquery.js')
  4. $('div').addClass('active');
  5. });
  6. // 加载模块
  7. seajs.use(['myModule.js'], function(my){
  8. });

所以总结下AMD和CMD的区别就是,AMD前置要加载的依赖模块,在定义模块的时候就要声明其依赖的模块。

而CMD加载完某个依赖模块后并不执行,只是下载而已,只有在用到的时候才使用require进行执行。

ES modules和现代浏览器

ES6和现代浏览器对模块化的支持是通过import和export来实现的。

首先看下import和export在浏览器中支持的情况:

首先我们看下怎么使用export导出要暴露的变量或者方法:


  1. export const name = 'square';
  2. export function draw(ctx, length, x, y, color) {
  3. ctx.fillStyle = color;
  4. ctx.fillRect(x, y, length, length);
  5. return {
  6. length: length,
  7. x: x,
  8. y: y,
  9. color: color
  10. };
  11. }

基本上,我们可以使用export导出var, let, const变量或者function甚至class。前提是这些变量或者函数处于top-level。

更简单的办法就是将所有要export的放在一行表示:

  1. export { name, draw, reportArea, reportPerimeter };

export实际上有两种方式,named和default。上面的例子中的export是named格式,因为都有自己的名字。

下面看下怎么使用export导出默认的值:

  1. // export feature declared earlier as default
  2. export { myFunction as default };
  3. // export individual features as default
  4. export default function () { ... }
  5. export default class { .. }

named可以导出多个对象,而default只可以导出一个对象。

导出之后,我们就可以使用import来导入了:

  1. import { name, draw, reportArea, reportPerimeter } from './modules/square.js';

如果导出的时候选择的是default,那么我们在import的时候可以使用任何名字:

  1. // file test.js
  2. let k; export default k = 12;
  3. // some other file
  4. import m from './test'; // 因为导出的是default,所以这里我们可以使用import m来引入
  5. console.log(m); // will log 12

我们可以在一个module中使用import和export从不同的模块中导入,然后在同一个模块中导出,这样第三方程序只需要导入这一个模块即可。

  1. export { default as function1,
  2. function2 } from 'bar.js';

上面的export from 等价于:

  1. import { default as function1,
  2. function2 } from 'bar.js';
  3. export { function1, function2 };

上面的例子中,我们需要分别import function1 function2才能够使用,实际上,我们可以使用下面的方式将所有的import作为Module对象的属性:

  1. import * as Module from './modules/module.js';
  2. Module.function1()
  3. Module.function2()

然后function1,function2就变成了Module的属性,直接使用即可。

在HTML中使用module和要注意的问题

怎么在HTML中引入module呢?我们有两种方式,第一种是使用src选项:

  1. <script type="module" src="main.js"></script>

第二种直接把module的内容放到script标签中。

  1. <script type="module">
  2. /* JavaScript module code here */
  3. </script>

注意,两种script标签的类型都是module。

在使用script来加载module的时候,默认就是defer的,所以不需要显示加上defer属性。

如果你在测试的时候使用file:// 来加载本地文件的话,因为JS模块安全性的要求,很有可能得到一个CORS错误。

最后,import() 还可以作为函数使用,来动态加载模块:

  1. squareBtn.addEventListener('click', () => {
  2. import('./modules/square.js').then((Module) => {
  3. let square1 = new Module.Square(myCanvas.ctx, myCanvas.listId, 50, 50, 100, 'blue');
  4. square1.draw();
  5. square1.reportArea();
  6. square1.reportPerimeter();
  7. })
  8. });

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/js-modules/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

javascript中的模块系统的更多相关文章

  1. Nodejs中的模块系统

    一.模块化的定义 ①具有文件作用域 ②具有通信规则:加载和导出规则 二.CommonJS模块规范 1.nodejs中的模块系统,具有文件作用域,也具有通信规则,使用require方法加载模块,使用ex ...

  2. Node中的模块系统

    加载require var 自定义变量名称 = require('模块') 两个作用: 执行被加载模块的代码 得到被加载模块中的exports导出接口对象 导出exports node中是模块作用域, ...

  3. 第九章:Javascript类和模块

    (过年了,祝大家新年好!) 第6章详细介绍了javascript对象,每个javascript对象都是一个属性集合,相互之间没有任何联系.在javascript中也可以定义对象的类,让每个对象都共享某 ...

  4. 实现javascript下的模块组织

    前面的话 java有类文件.Python有import关键词.Ruby有require关键词.C#有using关键词.PHP有include和require.CSS有@import关键词,但是对ES5 ...

  5. 深入ES6 模块系统

    深入ES6 模块系统 本文转载自:众成翻译 译者:neck 链接:http://www.zcfy.cc/article/4436 原文:https://ponyfoo.com/articles/es6 ...

  6. 彻底搞清楚javascript中的require、import和export(js模块加载规范的前世今生)

    为什么有模块概念 理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. 但是,Javascript不是一种模块化编程语言,在es6以前,它是不支持”类”(class),所以也 ...

  7. JavaScript中模块“写法”

    在JavaScript模块到底是什么 event = function() { // do more return { bind: function() {}, unbind: function() ...

  8. 如何设计一个高内聚低耦合的模块——MegEngine 中自定义 Op 系统的实践经验

    作者:褚超群 | 旷视科技 MegEngine 架构师 背景介绍 在算法研究的过程中,算法同学们可能经常会尝试定义各种新的神经网络层(neural network layer),比如 Layer No ...

  9. JavaScript中为什么使用立即执行函数来封装模块?

    最近在学习JavaScript基础,在学习到面向对象编程时,学习到在JavaScript中实现模块化的方法,其中一个重要的点是如何封装私有变量. 实现封装私有变量的方法主要是: 使用构造函数 func ...

随机推荐

  1. shell脚本的使用该熟练起来了,你说呢?(篇三)

    继续前一篇的文章: shell脚本的使用该熟练起来了,你说呢?(篇一) shell脚本的使用该熟练起来了,你说呢?(篇二) 文章里面测试的命令脚本文件,大家关注我公众号后,可以私信我领取文件. 作者: ...

  2. 从问题入手,深入了解JavaScript中原型与原型链

    从问题入手,深入了解JavaScript中原型与原型链 前言 开篇之前,我想提出3个问题: 新建一个不添加任何属性的对象为何能调用toString方法? 如何让拥有相同构造函数的不同对象都具备相同的行 ...

  3. 为何 JVM TLAB 在线程退还给堆的时候需要填充 dummy object

    TLAB 全网最硬核的解析,请参考:全网最硬核 JVM TLAB 分析 TLAB 在何时退还给堆? 有两种情况: 当前 TLAB 不足分配,并且剩余空间小于当前线程最大浪费空间限制时. 发生 GC 时 ...

  4. mysql创建和使用数据库

    mysql连接和断开 mysql -h host -u user -p******** /*建议不要在命令行中输入密码,因为这样做会使其暴露给在您的计算机上登录的其他用户窥探*/ mysql -u u ...

  5. fiddler抓包+雷电模拟器 完成手机app抓包的配置

    1.下载最新版Fiddler,强烈建议在官网下载:https://www.telerik.com/download/fiddler 不下载最新版的话,配置起来会遇到很多问题,弄太麻烦了.因为我下载的是 ...

  6. JavaScript——七(继承)

    一. 这个样子这个student的类型是person,这个样子写虽然继承了,但是是把父类的属性继承在了student的原型上 为了使student的类型改成他自己就需要加一句"student ...

  7. .net webapi 中使用session是出错 HttpContext.Current.Session==null

    最近在写.net webapi时发现 HttpContext.Current.Session==null  ,导致报错,后来查资料发现webapi中使用session时首先需要开启session功能, ...

  8. CentOS6下mysql的安装与配置

    CentOS是免费的.开源的.可以重新分发的开源操作系统,CentOS(Community Enterprise Operating System,中文意思是社区企业操作系统)是Linux发行版之一. ...

  9. Chapter Zero 0.1.4 计算机上常用的计算单位

    0.1 计算机硬件 计算机上常用的计算单位 容量单位: 计算机对于数据的判断依据有没有通电来记录信息,对于每个记录而言, 他只认识0或1,而0/1这个二进制单位我们成为bit. 因为bit太小,所以存 ...

  10. Kubernetes安装EFK教程(非存储持久化方式部署)

    1.简介 这里所指的EFK是指:ElasticSearch,Fluentd,Kibana ElasticSearch Elasticsearch是一个基于Apache Lucene的开源搜索和数据分析 ...