封面图说明:© Michael J. Kochniss | mjk-photo.de | instagram.com/mjk_photo

在早期,JavaScript 程序主要用来实现一些页面上的动画或者简单的交互,所以程序不会太复杂,页面也不会有太多的 JavaScript 代码,前端在 JavaScript 程序中还没有模块化开发的需要。但是随着应用的复杂度增加,为增强代码的可维护性,提升应用的加载性能,前端开发对 JavaScript 模块化开发的需求愈发强烈。本文将按照 JavaScript 模块化开发的演进历程逐一介绍在社区被采用过的不同模块化方案。

什么是模块化

模块化是一种将系统分离成独立功能部分的方法,可将系统分割成独立的功能部分,严格定义模块接口、模块间具有透明性。
简单来说就是我们在开发应用的时候,通常会根据不同的功能分割成不同的模块来开发,开发完成后再按照特定的方式组合成一个整体,最终完成整个应用系统功能的开发。
JavaScript 的模块化开发,能帮助我们解决在开发过程中遇到的全局变量污染问题,函数命名冲突问题,以及依赖关系不好处理的问题,还有代码复用性问题。

无模块化开发

最原始的 JavaScript 程序开发就是没有模块化的概念的,在一个页面中,不管有多少功能,要写多少代码,我们都把代码写到一个或者几个JS 文件中,然后页面通过 script 标签引入相关 JS 文件。当页面功能比较复杂的时候,或者需要不同开发者协同进行同一功能开发的时候,就难免会遇到全局变量污染和命名冲突的问题,再者就是功能非常复杂的时候,写的代码量也会比较多,从外部引入到页面的 JS 文件数量也越来越多,如果没有管理和组织好代码以及代码的依赖关系,后期维护将变得非常被动。

立即执行函数(IIFE)

为防止 JavaScript 代码中的全局变量污染和命名冲突的问题,开发人员在 JavaScript 中引入了自执行函数,也就是大家经常都能在面试中都会被问到的闭包。什么是闭包呢?一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

通过闭包可以形成一个独立的作用域,其里面声明的变量仅在该作用域内,在闭包外部是不能访问和操作的,从而达到了私有变量的目的,也就解决了前文提到的全局变量污染和命名冲突的问题,但是闭包内部可以通过向外暴露一些方法,以让闭包外部能访问到闭包内的变量。另外,如果闭包内部想要访问全局变量或者其他模块里面的变量,这时可以通过立即执行函数的参数传递到闭包内部。

( function( global, factory ) {if ( typeof module === "object" && typeof module.exports === "object" ) {module.exports = global.document ?factory( global, true ) :function( w ) {if ( !w.document ) {throw new Error( "jQuery requires a window with a document" );}return factory( w );};} else {factory( global );}
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
} );

看上面一段代码,是我们熟悉的 jQuery 源码中的一部分,这个闭包函数有两个行参,第一个参数是当 typeof window !== “undefined” 的时候将 window 作为实参传递进去,否则将 this 作为实参传递进去,第二个参数则是一个匿名函数,可见通过闭包来隔离变量的使用是非常广泛的。

命名空间

除了使用立即执行函数可以解决变量污染的问题,还可以使用命名空间的方式来解决,简单来说,就是我们可以通过创建一个简单的对象字面量来保存所有的相关变量和函数,利用对象字面量模拟命名空间的作用。

var NAMESPACE = {person: function(name) {this.name = name;this.getName = function() {return this.name;}}
};

CommonJS

CommonJS规范加载模块是同步的,也就是说,加载完成才执行后面的操作。Node.js主要用于服务器编程,模块都是存在本地硬盘中,加载比较快,所以Node.js采用CommonJS规范。

CommonJS规范分为三部分:module(模块标识)、require(模块引用)、exports(模块定义)。 module变量在每个模块内部,就代表当前模块; exports属性是对外的接口,用于导出当前模块的方法或变量;require()用来加载外部模块,读取并执行js文件,返回该模块的exports对象。

CommonJS 有以下几个特点:

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,再次加载时,会直接读取缓存结果。如果想让模块再次运行,则必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

定义模块:

// config.js
var api = 'https://github.com/trending?since=weekly&_pjax=%23js-pjax-container';
var config = {api: api,
};
module.exports = config;

引用模块:

// utils.js
var config = require('./config');
var utils = {request() {console.log(config.api);}
};
module.exports = utils;

AMD 模块化方案

随着前端业务的发展,代码也变得越来越复杂,通过简单的立即执行函数或者命名空间的方式来组织代码已经不太适用了。前端需要有一种更清晰、简单的方式处理最开始遇到的全局变量命名冲突的问题以及 JS 代码间的依赖关系。社区中不同的 JS 模块化规范陆续出现,其中使用比较流行的是 AMD 和 CMD,先来介绍一下 AMD 模块化方案。

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。它是一个在浏览器端模块化开发的规范,由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数(RequireJS库),实际上AMD 是 RequireJS 在推广过程中对模块定义的规范化的产出。

AMD采用require()语句加载模块,它要求两个参数:

require([module], callback);

第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。

定义模块:

// a.js
define(function (){return {a:'hello world'}
});

引用模块:

// b.js
require(['./a.js'], function (moduleA){console.log(moduleA.a); // 打印出:hello world
});

CMD 模块化方案

CMD是 ”Common Module Definition”,称为 通用模块加载规范 。一般也是用在浏览器端。浏览器端异步加载库 Sea.js 实现的就是CMD规范。它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。

定义模块:

define(function(require, exports, module){//引入依赖模块(同步)var module2 = require('./module2')//引入依赖模块(异步)require.async('./module3', function (m3) {})//暴露模块exports.xxx = value
})

引用模块:

define(function (require) {var m1 = require('./module1')var m4 = require('./module4')m1.foo()m4.bar()
})

ES6 Module

在ES6没有引入模块化概念之前,为了解决js模块化开发的问题,社区提出了CommonJS,AMD,CMD模块化开发方案;ES6模块化汲取CommonJS和AMD的优点,语法简洁,支持异步加载,旨在为浏览器和服务器提供通用的模块化解决方案,但从长远来看,未来无论是在web端还是基于node的服务端或者桌面端,模块化开发规范都将统一使用 ES6 Module。

ES6中模块的定义:ES6新增了两个关键字:export和import。export用于把模块里的内容暴露出来,import用于引入模块提供的功能。

// test.js
let name = '前端农民工';
function getName() {return name;
}function setName(n) {name = n;
}export {name,getName,setName,
}

ES6中模块的加载:使用 import 语法加载模块,如下:

import{ getName } from './test'
getName();

注意:可以使用export default命令,为模块指定默认输出,一个模块只能有一个默认输出,所以export default只能使用一次。

ES6模块运行机制:ES6模块是动态引用,如果使用import从一个模块加载变量(即import foo from ‘foo’),变量不会被缓存,而是成为一个指向被加载模块的引用。等脚本执行时,根据只读引用,到被加载的那个模块中去取值。

水平有限,文中难免有不足之处,欢迎大家关注我的微信公众号。(前端民工)

JavaScript 中的模块化相关推荐

  1. javascript中的模块化

    javascript中的模块化 文章目录 javascript中的模块化 ES6模块化 导出 转译工具 babel 预设 离线转译安装配置 1.初始化npm 2.设置镜像 3.安装 4.修改packa ...

  2. JavaScript中的模块化之AMD和CMD

    前言:   为什么我们需要模块化开发,模块化开发的好处有哪些? 首先我们先说一下非模块化的开发方式带来的弊端. 非模块化开发中会导致一些问题的出现,变量和函数命名可能相同,会造成变量污染和冲突,并且出 ...

  3. JavaScript中的load事件的作用_史上最全的web前端面试题汇总及答案JavaScript之二(二)...

    作者:樱桃小丸子儿 链接:https://www.jianshu.com/p/abadcc84e2a4 JavaScript JS的基本数据类型 number,string,boolean,objec ...

  4. 理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被"存储"在变量中,能作为函数参数被传递,能在函数中被创建, ...

  5. Javascript中闭包的作用域链

    作用域定义了在当前上下文中能够被访问到的成员,在Javascript中分为全局作用域和函数作用域,通过函数嵌套可以实现嵌套作用域. 闭包一般发生在嵌套作用域中.闭包是JavaScript最强大的特性之 ...

  6. 理解javascript中的回调函数(callback)【转】

    在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...

  7. 【JavaScript】理解与使用Javascript中的回调函数

    在Javascript中,函数是第一类对象,这意味着函数可以像对象一样按照第一类管理被使用.既然函数实际上是对象:它们能被"存储"在变量中,能作为函数参数被传递,能在函数中被创建, ...

  8. javascript函数式_如何以及为什么在现代JavaScript中使用函数式编程

    javascript函数式 by PALAKOLLU SRI MANIKANTA 通过PALAKOLLU SRI MANIKANTA In this article, you will get a d ...

  9. JavaScript中的回调函数(callback)

    前言 callback,大家都知道是回调函数的意思.如果让你举些callback的例子,我相信你可以举出一堆.但callback的概念你知道吗?你自己在实际应用中能不能合理利用回调实现功能? 我们在平 ...

最新文章

  1. zookeeper集群环境搭建
  2. 解决使用mybatis分页插件PageHelper的一个报错问题
  3. Python中Numpy(2,numpy的基本操作(级联,维度转换,切分,副本))
  4. javase基础复习攻略《七》
  5. java(14) - HashMap类
  6. java-设计模式(行为型)-【命令模式】
  7. Spark之UpdateStateByKey算子
  8. IEEE802.15.4、ZigBee、ZigBee协议栈、Zstack、ZigBee联盟、CC2530、IAR的关系?
  9. 向量对矩阵列空间的投影_向量(阵列)处理和超标量处理器
  10. 30 道 MySQL 基础知识
  11. 软考 软件设计师考试经验分享、题型分析
  12. 开源一个安信可A9g小项目微信小程序定位器项目①如何在windows10上环境搭建,编译烧录代码固件,查看运行日志;
  13. bilibili哔哩哔哩网页版中视频资源下载方法(无需任何工具)
  14. PCB设计中常见的错误与解决方法
  15. java调用dll实例_如何用java调用dll,详细图解
  16. protobuf repeated string 赋值
  17. XDOJ1184 - 贪心的小白羊
  18. 评:日本的“泡沫”代价
  19. Win10下C:\Users\***修改用户名(完全修改)
  20. LINUX下的makefile学习(此文是我学习过程遇到问题时找到的所有回答,感谢其它大佬的回答,各个文章我都标明了原文链接)

热门文章

  1. AI 工具 22个使用场景、500个通用提问技巧说明
  2. 【每天一个Python小知识】NumPy中的np.where
  3. AD22 去哪里下载元器件的3D模型?
  4. 设计模式之命令模式(java)实例——电视机遥控器
  5. vue项目 百度地图离线开发
  6. html中自动换行的代码,css之自动换行实现代码
  7. 吉林四平市中心医院将引进RFID新生儿定位系统
  8. 企业即时通讯软件FreeEIM飞鸽传书
  9. 在vue里使用Lottie动画(实现 json 格式的动画)
  10. 网络安全工程师常用的威胁情报分析平台有哪些?