什么是模块化

好的代码模块分割的内容一定是很合理的,便于你增加减少或者修改功能,同时又不会影响整个系统。

 为什么要使用模块

1.可维护性:根据定义,每个模块都是独立的。良好设计的模块会尽量与外部的代码撇清关系,以便于独立对其进行改进和维护。维护一个独立的模块比起一团凌乱的代码来说要轻松很多。

2.命名空间:在JavaScript中,最高级别的函数外定义的变量都是全局变量(这意味着所有人都可以访问到它们)。也正因如此,当一些无关的代码碰巧使用到同名变量的时候,我们就会遇到“命名空间污染”的问题。

模块模式

模块模式一般用来模拟类的概念(因为原生JavaScript并不支持类,虽然最新的ES6里引入了Class不过还不普及)这样我们就能把公有和私有方法还有变量存储在一个对象中——这就和我们在Java或Python里使用类的感觉一样。这样我们就能在公开调用API的同时,仍然在一个闭包范围内封装私有变量和方法。

实现模块模式的方法有很多种,下面的例子是通过匿名闭包函数的方法。(在JavaScript中,函数是创建作用域的唯一方式。)

例1:匿名闭包函数

通过这种构造,我们的匿名函数有了自己的作用域或“闭包”。 这允许我们从父(全局)命名空间隐藏变量。

这种方法的好处在于,你可以在函数内部使用局部变量,而不会意外覆盖同名全局变量,但仍然能够访问到全局变量,如下所示:

要注意的是,一定要用括号把匿名函数包起来,以关键词function开头的语句总是会被解释成函数声明(JS中不允许没有命名的函数声明),而加上括号后,内部的代码就会被识别为函数表达式。其实这个也叫作立即执行函数(IIFE)感兴趣的同学可以在这里了解更多

例2:全局引入

另一种比较受欢迎的方法是一些诸如jQuery的库使用的全局引入。和我们刚才举例的匿名闭包函数很相似,只是传入全局变量的方法不同:

在这个例子中,globalVariable 是唯一的全局变量。这种方法的好处是可以预先声明好全局变量,让你的代码更加清晰可读。

例3:对象接口

像下面这样,还有一种创建模块的方法是使用独立的对象接口:

例4:揭示模块模式 Revealing module pattern

这和我们之前的实现方法非常相近,除了它会确保,在所有的变量和方法暴露之前都会保持私有:

到这里,其实我们只聊了模块模式的冰山一角。

 CommonJS & AMD

上述的所有解决方案都有一个共同点:使用单个全局变量来把所有的代码包含在一个函数内,由此来创建私有的命名空间和闭包作用域。

接下来介绍两种广受欢迎的解决方案:CommonJS 和 AMD.

CommonJS

CommonJS 扩展了JavaScript声明模块的API.

CommonJS模块可以很方便得将某个对象导出,让他们能够被其他模块通过 require 语句来引入。要是你写过 Node.js 应该很熟悉这些语法。

通过CommonJS,每个JS文件独立地存储它模块的内容(就像一个被括起来的闭包一样)。在这种作用域中,我们通过 module.exports 语句来导出对象为模块,再通过 require 语句来引入。

还是举个直观的例子吧:

过指定导出的对象名称,CommonJS模块系统可以识别在其他文件引入这个模块时应该如何解释。

然后在某个人想要调用 myMoudle 的时候,只需要 require 一下:

这种实现比起模块模式有两点好处:

  • 避免全局命名空间污染

  • 明确代码之间的依赖关系

需要注意的一点是,CommonJS以服务器优先的方式来同步载入模块,假使我们引入三个模块的话,他们会一个个地被载入。

它在服务器端用起来很爽,可是在浏览器里就不会那么高效了。毕竟读取网络的文件要比本地耗费更多时间。只要它还在读取模块,浏览器载入的页面就会一直卡着不动。

Asynchronous Module Definition(异步模块定义规范),简称AMD.

通过AMD载入模块的代码一般这么写:

这里我们使用 define 方法,第一个参数是依赖的模块,这些模块都会在后台无阻塞地加载,第二个参数则作为加载完毕的回调函数。

回调函数将会使用载入的模块作为参数。在这个例子里就是 myMoudle 和 myOtherModule.最后,这些模块本身也需要通过 define 关键词来定义。

拿 myModule 来举个例子:

UMD

在一些同时需要AMD和CommonJS功能的项目中,你需要使用另一种规范:Universal Module Definition(通用模块定义规范)。

UMD创造了一种同时使用两种规范的方法,并且也支持全局变量定义。所以UMD的模块可以同时在客户端和服务端使用。

为什么要打包模块?

一般来讲,我们用模块化组织代码的时候,都会把模块划分在不同的文件和文件夹里,也可能会包含一些诸如React和Underscore一类的第三方库。

而后,所有的这些模块都需要通过<script>标签引入到你的HTML文件中,然后用户在访问你网页的时候它才能正常显示和工作。每个独立的<script>标签都意味着,它们要被浏览器分别一个个地加载。

为了解决这个问题,我们就需要进行模块打包,把所有的模块合并到一个或几个文件中,以此来减少HTTP请求数。这也可以被称作是从开发到上线前的构建环节。

还有一种提升加载速度的做法叫做代码压缩(混淆)。其实就是去除代码中不必要的空格、注释、换行符一类的字符,来保证在不影响代码正常工作的情况下压缩其体积。

更小的文件体积也就意味着更短的加载时间。要是你仔细对比过带有 .min后缀的例如 jquery.min.js和jquery.js的话,应该会发现压缩版的文件相较之下要小很多。

Gulp和Grunt一类的构建工具可以很方便地解决上述的需求,在开发的时候通过模块来组织代码,上线时再合并压缩提供给浏览器。

打包模块的方法有哪些?

如果你的代码是通过之前介绍过的模块模式来组织的,合并和压缩它们其实就只是把一些原生的JS代码合在一起而已。

但如果你使用的是一些浏览器原生不支持的模块系统(例如CommonJS 或 AMD,以及ES6 模块的支持现在也不完整),你就需要使用一些专门的构建工具来把它们转换成浏览器支持的代码。这类工具就是我们最近经常听说的Browserify, RequireJS, Webpack等等模块化构建、模块化加载工具了。

为了实现模块化构建或载入的功能,这类工具提供许多诸如在你改动源代码后自动重新构建(文件监听)等一系列的功能。

下面我们就一起来看一些实际的例子吧:

打包 CommonJS

在上一篇教程中我们了解到, CommonJS是同步载入模块的,这对浏览器来说不是很理想。其实下面介绍的模块化构建工具Browserify在上一篇也提到过。它是一个专门用来打包CommonJS模块以便在浏览器里运行的构建工具。

举个例子,假如你在 main.js 文件中引入了一个用来计算平均数的功能模块

在这个示例中,我们只有一个名为 myDependency 的模块依赖。通过下面的命令,Browserify会依次把main.js里引入的所有模块一同打包到一个名为 bundle.js 的文件里:

Browserify 首先会通过抽象语法树(AST)来解析代码中的每一个 require 语句,在分析完所有模块的依赖和结构之后,就会把所有的代码合并到一个文件中。然后你在HTML文件里引入一个bundle.js就够啦。

多个文件和多个依赖也只需要再稍微配置一下就能正常工作了。

之后你也可以使用一些例如Minify-JS的工具来压缩代码。

打包 AMD

假若你使用的是AMD,你会需要一些例如RequireJS 或 Curl的AMD加载器。模块化加载工具可以在你的应用中按需加载模块代码。

需要再次提醒一下,AMD 和 CommonJS 的最主要区别是AMD是异步加载模块的。这也就意味着你不是必须把所有的代码打包到一个文件里,模块加载不影响后续语句执行,逐步加载的的模块也不会导致页面阻塞无法响应。

不过在实际应用中,为了避免用户过多的请求对服务器造成压力。大多数的开发者还是选择用RequireJS optimizer, r.js一类的构建工具来合并和压缩AMD的模块。

总的来说,AMD 和 CommonJS 在构建中最大的区别是,在开发过程中,采用AMD的应用直到正式上线发布之前都不需要构建。

Webpack

Webpack 是新推出的构建工具里最受欢迎的。它兼容CommonJS, AMD, ES6各类规范。

也许你会质疑,我们已经有这么多诸如Browserify 或 RequireJS 的工具了,为什么还需要 Webpack 呢?究其原因之一,Webpack 提供许多例如 code splitting(代码分割) 的有用功能,它可以把你的代码分割成一个个的 chunk 然后按需加载优化性能。

举个例子,要是你的Web应用中的一些代码只在很少的情况下才会被用到,把它们全都打包到一个文件里是很低效的做法。所以我们就需要 code splitting 这样的功能来实现按需加载。而不是把那些很少人才会用到的代码一股脑儿全都下载到客户端去。

code splitting 只是 Webpack 提供的众多强大功能之一。当然,网上也为这些模块化构建工具吵得不可开交

ES6 模块

ES6模块和CommonJS, AMD一类规范最主要的区别是,当你载入一个模块时,载入的操作实际实在编译时执行的——也就是在代码执行之前。所以去掉那些不必要的exports导出语句可以优化我们应用的性能。

假设我们有如下一个使用ES6语法,名为 utils.js 的函数:

现在我们也不清楚到底需要这个函数的哪些功能,所以先全部引入到 main.js 中:

之后我们再调用一下 each 函数:

通过 "tree shaken" 之后的 main.js 看起来就像下面这样:

注意到这里只导出了我们调用过的 each 方法。

再如果我们只调用 filter 方法的话:

你也可以自己在Rollup.js的实时预览编辑器里做做试验:live demo and editor

构建ES6模块

现在我们已经了解到ES6模块载入的与众不同了,但我们还没有聊到底该怎么构建ES6模块。

因为浏览器对ES6模块的原生支持还不够完善,所以现阶段还需要我们做一些补充工作。

让ES6模块在浏览器中顺利运行的常用方法有以下几种:

1.使用语法编译器(Babel或Traceur)来把ES6语法的代码编译成ES5或者CommonJS, AMD, UMD等其他形式。然后再通过Browserify 或 Webpack 一类的构建工具来进行构建。

2.使用Rollup.js,这其实和上面差不多,只是Rollup还会捎带的利用“tree shaking”技术来优化你的代码。在构建ES6模块时Rollup优于Browserify或Webpack的也正是这一点,它打包出来的文件体积会更小。Rollup也可以把你的代码转换成包括ES6, CommonJS, AMD, UMD, IIFE在内的各种格式。其中IIFE和UMD可以直接在浏览器里运行,AMD, CommonJS, ES6等还需要你通过Browserify, Webpack, RequireJS一类的工具才能在浏览器中使用。

myModule.js

main.js

同样,你可以在script标签上设置type=module的属性来直接定义模块:

HTTP/2 出现之后,模块化构建工具是不是都该被淘汰了?

在HTTP/1中,一次TCP连接只允许一个请求,所以我们需要通过减少载入的文件数来优化性能。而HTTP/2改变了这一切,请求和响应可以并行,一次连接也允许多个请求。

每次请求的消耗也会远远小于HTTP/1,所以载入一堆模块就不再是一个影响性能的问题了。所以许多人认为打包模块完全就是多余的了。这听起来很合理,但我们也需要具体情况具体分析。

其中有一条,模块化构建解决了一些HTTP/2解决不了的问题。例如去除冗余的代码以压缩体积。要是你开发的是一个对性能要求很高的网站,模块化构建从长远上考虑会给你带来更多好处。当然,要是你不那么在意性能问题,以后完全就可以省却这些烦人的步骤了。

总之,我们离所有的网站都采用HTTP/2传输还有相当一段时间。短期内模块化构建还是很有必要的。

对JavaScript 模块化的深入-----------------引用的更多相关文章

  1. Javascript模块化开发,使用模块化脚本加载工具RequireJS,提高你代码的速度和质量。

    随着前端JavaScript代码越来越重,如何组织JavaScript代码变得非常重要,好的组织方式,可以让别人和自己很好的理解代码,也便于维护和测试.模块化是一种非常好的代码组织方式,本文试着对Ja ...

  2. JavaScript模块化---AMD规范

    JavaSript模块化 在了解AMD,CMD规范前,还是需要先来简单地了解下什么是模块化,模块化开发?     模块化是指在解决某一个复杂问题或者一系列的杂糅问题时,依照一种分类的思维把问 题进行系 ...

  3. JavaScript模块化 --- Commonjs、AMD、CMD、es6 modules

    随着前端js代码复杂度的提高,JavaScript模块化这个概念便被提出来,前端社区也不断地实现前端模块化,直到es6对其进行了规范,下面就介绍JavaScript模块化. 这篇文章还是希望能给大家一 ...

  4. 《前端之路》之 Javascript 模块化管理的来世今生

    目录 第二章 - 04: Javascript 模块化管理的来世今生 一.什么是模块化开发 1-1.模块化第一阶段 1-2.封装到对象 1-3. 对象的优化 二.模块化管理的发展历程 2-1.Comm ...

  5. JavaScript模块化CommonJS/AMD/CMD/UMD/ES6Module的区别

    目录 JS-模块化进程 原始的开发方式 CommonJS && node.js AMD && Require.js CMD && Sea.js UMD ...

  6. 造轮子和用轮子:快速入门JavaScript模块化

    造轮子和用轮子:快速入门JavaScript模块化 前言 都说“不重复造轮子”,就像iPhone——它除了打电话还可以播放音乐——但是工程师不用从零开始做一个音乐播放功能,也许只要在iPhone的系统 ...

  7. JavaScript模块化思想之入门篇

    在写正文之前先写一点废话,从我大三下学期正式接触前端到现在,已经六个月了.自己从HTML,CSS,简单的JS验证开始,一点点开始走入前端的世界.越发的感觉前端这一领域散发着无穷的魅力,也许这和我真心喜 ...

  8. 知识点【JavaScript模块化】

    JavaScript模块化历程 JavaScript发展变迁大概是一下几个步骤: 工具(浏览器兼容) 组件(功能模块) 框架(功能模块组织) 应用(业务模块组织) 但是经过了长长的后天努力过程Java ...

  9. JavaScript 模块化简述

    JavaScript 模块化简述 前言 关于模块化,最直接的表现就是我们写的 require 和 import 关键字,如果查阅相关资料,就一定会遇到 CommonJS .CMD AMD 这些名词,以 ...

随机推荐

  1. 删除重复信息且要保留一条的(roacle的rowid另类用法)

    由于表的主键失效了(disable),导致导入了一些主键重复的数据,想保留唯一的一条, 最后发现其实可以用rowid来实现,不知道算不算是rowid的另类用法. delete /*+ parallel ...

  2. JAVA开发者大会:拍拍贷MQ系统原理与应用

    --喜欢记得关注我哟[shoshana]-- 前记: 5月12号参加了JAVA开发者大会,就<拍拍贷消息系统原理及应用> 作者:李乘胜老师 关于PMQ的分享整理一下笔记以及笔记的思考 和复 ...

  3. 创建web服务器

    用node创建本地web服务 1,创建本地文件server.js var http = require('http'); var url=require('url'); var fs=require( ...

  4. cygwin gcc 编译windowsAPI 报错的一个解决方案

    一开始按照linux的习惯去编译一个使用了windowsAPI的程序 结果提示: $ i686-pc-cygwin-g++ screen_catch.cscreen_catch.c: In funct ...

  5. react的状态管理

    近两年前端技术的发展如火如荼,大量的前端项目都在使用或转向 Vue 和 React 的阵营, 由前端渲染页面的单页应用占比也越来越高,这就代表前端工作的复杂度也在直线上升,前端页面上展示的信息越来越多 ...

  6. 怎样使用yum安装nginx

    yum install -y nginx 以上.

  7. Web API 自动生成接口文档

    1.添加NuGet程序包 Microsoft ASP.NET Web API 2.2 Help Page      (这是微软官方的) A Simple Test Client for ASP.NET ...

  8. 微信公众号支付备忘及填坑之路-java

    一.背景 最近公司给第三方开发了一个公众号,其中最重要的功能是支付,由于是第一次开发,遇到的坑特别的多,截止我写博客时,支付已经完成,在这里我把遇到的坑记录一下(不涉及退款).不得不吐槽一下,腾讯这么 ...

  9. wrbstrom使用

    使用webstrom时遇到Firefox浏览器打不开问题,是webstrom未找到你Firefox的安装路径下面为大家提供解决方法: 文件--->设置--->工具--->web浏览器 ...

  10. java代码实现mock数据

    废话不多说,直接上代码. /** * 发get请求,获取文本 * * @param getUrl * @return 网页context */ public static String sendGet ...