前端模块化IIFE,commonjs,AMD,UMD,ES6 Module规范超详细讲解
为什么前端需要模块化
在没有模块化的时候,多个脚本引入页面,会造成诸多问题,比如:
- 多人协同开发的时候,系统中可能会引入很多js脚本,这些js会定义诸多全局变量,这时候很容易出现变量名覆盖的问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
var info = "这是功能A";
</script>
<script type="text/javascript">
var info = "这是功能B";
</script>
<script>
console.log(info); // 这是功能B
</script>
</body>
</html>
上面的例子中可以看到 第一个js中定义的变量info的值被第二个js中的变量所覆盖
- 当脚本之间存在依赖关系的时候,单纯的引用script看不出js之间的依赖,可读性很差
<html>
<!-- 此处省略head -->
<body>
<script type="text/javascript">
function getMessage(){
return "这是一条message"
}
</script>
<script type="text/javascript">
function showMessage(){
console.log(getMessage());
}
</script>
<script>
showMessage(); // 这是一条message
</script>
</body>
</html>
如果第一个脚本没有引入,那么执行就会抛错,也就是说第二个脚本是依赖第一个脚本的,但是这个依赖关系这里看不出来
什么是模块
模块我理解为就是一个局部作用域,这个局部作用域内部定义了一些局部变量和方法,然后对外提供接口供外部调用,比如:
var moduleA = {
name : "A"
}
var moduleB = {
name : "B"
}
console.log(moduleA.name); // A
这里就可以看成是定义了两个最简单的模块,我们可以通过模块去访问各自的变量
是什么IIFE
IIFE(Immediately Ivoked Function Expression),即立即执行函数表达式,所谓立即执行,就是声明一个函数,声明完了立即执行
var IIFE = function(){
// ...
}
IIFE();
这样是立即执行但是肯定会有一个问题,函数名冲突了怎么办?所以有了我们最常见的写法,声明一个自执行匿名函数
(function(){
// ...
})()
如果看过jquery的一些插件的源码的话经常能看到这样的代码
(function($){
// ...
})(jQuery)
这里其实就是表明 这个模块依赖了jquery
举个栗子
定义模块A和模块B,模块B依赖模块A
- js文件
// 模块A moduleA.js
(function(window){
var name = "module A";
// 对外暴露对象moduleA
window.moduleA = {
getName(){
return name;
}
}
})(window)
// 模块B moduleB.js
(function(window, moduleA){
// 对外暴露对象moduleB
window.moduleB = {
showFirstModuleName(){
console.log(moduleA.getName());
}
}
})(window, moduleA)
// main.js
(function(moduleB){
console.log(moduleB.showFirstModuleName());
})(moduleB)
- html文件中
<html>
<!-- 此处省略head -->
<body>
<script type="text/javascript" type="./moduleA.js"></script>
<script type="text/javascript" type="./moduleB.js"></script>
<script type="text/javascript" type="./main.js"></script>
</body>
</html>
上述例子展示了如何用IIFE来定义模块,这样写有几个缺点:
- 定义了3个模块,那么就引入了3个js脚本,那如果有更多模块呢,那就意味着很页面加载时会像服务器发起多次http请求,这是不好的
- html中script的标签顺序是固定的,因为模块main依赖moduleB,moduleB依赖moduleA,所以moduleA必须先声明,这样在moduleB的IIFE执行时候才能正常,不然会抛处ReferenceError
模块化标准
Commonjs
nodejs采用的模块化标准,commonjs使用方法require来引入模块,这里require()接收的参数是模块名或者是模块文件的路径,如果是模块名的话,require会到node_modules中去找对应名称的模块来加载
const _ = require("lodash");
这里就引入了一个名为lodash的模块,那么一个模块应该如何对外提供接口呢?
commonjs提供两种方式对外暴露接口
// 第一种module.exports
const name = "张三";
module.exports = {
getName(){
return name
}
}
// 第二种
const name = "张三"
exports.getName = function(){
return name;
}
其实本质上,模块对外暴露的就是exports这个对象,module.exports =这种写法,相当于直接给exports对象赋值,而export. name这种写法其实就是给exports对象上添加了一个名为"name"的方法
特征
- 在node运行时执行
- require是对值的拷贝
// moduleA.js
let count = 1;
// 异步让count++
setTimeout(()=>{
count++;
});
exports.count = count;
// main.js
const {count} = require("./moduleA.js");
// 同步打印count
console.log(count); // 打印值为1
// 异步打印count
setTimeout(()=>{
console.log(count); // 打印值为1
});
可见改变了moduleA中的count,并不影响main.js中引入的值
- 不做特殊处理(webpack打包)commonjs只能运行在node环境,浏览器环境不能直接使用,window上没有定义require这个方法,所以解释脚本的时候就会抛处ReferenceError
- commonjs是同步加载模块,在node环境中require引入一个模块的时候,这个过程是同步的,必须等模块加载完才能继续后续操作
IIFE中的例子用commonjs实现
上述IIFE中的例子,用commonjs来实现就看起来就更清晰:
// 模块A moduleA.js
const name = "module A"
module.exports = {
getName(){
return name;
}
}
// 模块B moduleB.js
const {getName} = require("./moduleA.js"); // 引入moduleA
exports.showFirstModuleName = function(){
console.log(getName());
}
// main.js
const moduleB = require("./moduleB.js");
moduleB.showFirstModuleName(); // module A
上文中讲commonjs的特性的时候提到过,不能直接在浏览器中运行,所以我们需要先使用打包用具(webpack等工具,以后的文章中会写)把js打包处理成浏览器能直接运行的bundle.js,在引入到html中
<html>
<!-- 此处省略head -->
<body>
<script type="text/javascript" type="./dist/bundle.js"></script>
</body>
</html>
或者直接在用node运行main:
-> node main.js
AMD和RequireJS
全称Asynchronous Module Definition异步模块定义,与commonjs不同AMD是完全针对浏览器的模块化定义,AMD加载模块是异步的
如何定义一个模块
AMD规范中定义模块用到方法define,还是以之前的例子来举例,先来定义一个没有依赖的模块moduleA
// 定义一个moduleA.js
define(function(){
var name = "module A"
return {
getName(){
return name
}
}
})
这里define只接受了一个回调函数作为参数,这个回调是不是与IIFE有点相似,再来定义一个依赖moduleA的moduleB
// 定义一个moduleB.js
define(["moduleA"], function(moduleA){
return {
showFirstModuleName(){
console.log(moduleA.getName());
}
}
});
这里define的第一个参数是一个数组,数组里面放的是当前定义的模块所依赖的模块的名字,而后面回调函数接收的参数就是对应的模块了,也许看到这里你会想问,为什么这里只写一个模块名“moduleA”就能找到对应的moduleA.js的文件了呢?后面会讲
如何在入口文件引入模块
我们已经实现了moduleA.js和moduleB.js接下来要实现入口main.js,AMD的标准中,引入模块需要用到方法require,看到这你可能会有疑问,前面说commonjs的时候,不是说了window对象上没定义require吗?这里就不得不提到一个库,那就是RequireJS
RequireJS is a JavaScript file and module loader.
官网介绍RequireJS是一个js文件和模块的加载器,提供了加载和定义模块的api,当在页面中引入了RequireJS之后,我们便能够在全局调用define和require,下面来实现main.js
// 实现main.js
require(["moduleB"], function(moduleB){
moduleB.showFirstModuleName();
});
三个js文件都写好了,我们该如何引入到页面中呢?查看RequireJS官网已经给出了答案
<html>
<!-- 此处省略head -->
<body>
<!--引入requirejs并且在这里指定入口文件的地址-->
<script data-main="js/main.js" src="js/require.js"></script>
</body>
</html>
要通过script引入requirejs,然后需要为标签加一个属性data-main来指定入口文件
使用RequireJS需要的配置
前面介绍用define来定义一个模块的时候,直接传“模块名”似乎就能找到对应的文件,这一块是在哪实现的呢?其实在使用RequireJS之前还需要为它做一个配置
// main.js
require.config({
paths : {
// key为模块名称, value为模块的路径
"moduleA" : "./moduleA",
"moduleB" : "./moduleB"
}
});
require(["moduleB"], function(moduleB){
moduleB.showFirstModuleName();
});
这个配置中的属性paths应该说是一目了然,看了就能明白,为什么引入的时候只写模块名就能找到对应路径了吧,不过这里有一项要注意的是,路径后面不能跟.js文件后缀名,更多的配置项请参考RequireJS官网
ES6 module
es6提出了新的模块化方案,这个方案应该也是现在最流行的。通过关键字export value来暴露模块,通过import moduleName from path来引入模块,是不是看起来很简单?但是其实这里还有很多细节
如何运行
- 浏览器端是不能直接运行的,需要先用babel将es6语法转译成es5(把import转译成了require),然后再使用打包工具打包,最后在页面中引入
- node端在某个版本后有办法直接运行了(抱歉没查是哪个版本),首先js文件的后缀名都要改成.mjs,然后再命令行直接运行node --experimental-modules main.mjs
多次暴露
模块可以多次调用export来暴露任何值
// moduleA.mjs
// 暴露一个变量
export let name = "张三"
// 暴露一个方法
export function getName(){
return name;
}
export function setName(newName){
name = newName;
}
// main.mjs
import {name, getName, setName} from "./moduleA";
console.log(name); // 张三
setName("李四");
console.log(getName()); // 李四
这里import后面必须跟结构赋值如果写成下面这样,会输出undefined
import moduleA from "./moduleA"
console.log(moduleA); // undefined;在node环境下运行会报错
那如果模块分别暴露的方法有很多怎么办呢,这时候结构赋值不是要写很多个方法?其实还可以这样引入
import * as moduleA from "./moduleA";
console.log(moduleA.name); // 张三
moduleA.setName("李四");
console.log(moduleA.getName()); // 李四
默认暴露
es6还提供了一种暴露方法叫默认暴露,默认暴露即export default value这里的value可以是任何值,为什么上面举得import的反例,引入结果会是undefined呢,再看一个例子
// moduleA.mjs
export default {
name : 张三,
setName(newName){
this.name = newName;
},
getName(){
return this.name;
}
}
// main.mjs
import moduleA from "./moduleA"
console.log(moduleA); // { name: '张三', setName: [Function: setName], getName: [Function: getName] }
这里其实就能看出来,直接引入给moduleA赋值的其实是export default value后面的value
UMD
UMD全称为Universal Module Definition,也就是通用模块定义,为什么叫通用呢,我们怎么描述一个模块是通用的呢?举个例子,假如现在我的项目使用的是amd模块规范,那么现在我引入了一个用commonjs规范写的模块,能正常运行吗?肯定不行的,而UMD就是解决了这个问题。
特点
umd所谓的通用,就是兼容了commonjs和amd规范,这意味着无论是在commonjs规范的项目中,还是amd规范的项目中,都可以直接引用umd规范的模块使用(牛逼!)
原理
原理其实就是在模块中去判断全局是否存在exports和define,如果存在exports,那么以commonjs的方式暴露模块,如果存在define那么以amd的方式暴露模块
(function(window, factory){
if(typeof exports === "objects"){
// commonjs
module.exports = factory();
}else if(typeof define === "function"){
// amd
define(factory);
}else{
window.moduleA = factory();
}
})(window, function(){
// 返回module
let modlueA = {
name : "张三",
setName(newName){
thie.name = newName;
},
getName(){
return this.name;
}
}
return modlueA;s
})
前端模块化IIFE,commonjs,AMD,UMD,ES6 Module规范超详细讲解的更多相关文章
- (转) 前端模块化:CommonJS,AMD,CMD,ES6
模块化的开发方式可以提高代码复用率,方便进行代码的管理.通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数.目前流行的js模块化规范有CommonJS.AMD.CMD以及ES6的模块 ...
- 前端模块化(CommonJs,AMD和CMD)
前端模块规范有三种:CommonJs,AMD和CMD. CommonJs用在服务器端,AMD和CMD用在浏览器环境 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出. CMD 是 S ...
- 前端模块化小总结—commonJs,AMD,CMD, ES6 的Module
随着前端快速发展,需要使用javascript处理越来越多的事情,不在局限页面的交互,项目的需求越来越多,更多的逻辑需要在前端完成,这时需要一种新的模式 --模块化编程 模块化的理解:模块化是一种处理 ...
- 前端模块化之CommonJS,ES6,AMD,CMD
最近在搞跨平台解决方案,讨论关于模块划分的问题以及如何尽量多的复用逻辑代码.于是就有了此文章,之前的博客也写过,不过由于主机商跑路,宝贵的资源也就没了,说多了都是泪~ 这里按模块化发展的历史回溯的时间 ...
- 前端模块化方案全解(CommonJS/AMD/CMD/ES6)
模块化的开发方式可以提高代码复用率,方便进行代码的管理.通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数.目前流行的js模块化规范有CommonJS.AMD.CMD以及ES6的模块 ...
- JavaScript模块化演变 CommonJs,AMD, CMD, UMD(一)
原文链接:https://www.jianshu.com/p/33d53cce8237 原文系列2链接:https://www.jianshu.com/p/ad427d8879cb 前端完全手册: h ...
- 【JavaScript】JavaScript模块化编程 - CommonJS, AMD 和 RequireJS之间的关系
通行的Javascript模块规范共有两种:CommonJS和AMD 先说说CommonJS CommonJS - 大家是不是觉得JavaScript仅仅是一个客户端的编译语言,其实JavaScr ...
- JavaScript模块化编程 - CommonJS, AMD 和 RequireJS之间的关系
这几天在学习CommonJS的时候突然在StackOverflow上搜索到一个非常好的一个帖子,是关于CommonJS, AMD和RequireJS之间的关系的问答贴.我感觉写的非常好,鉴于没有找到相 ...
- 模块commonjs AMD UMD
commonjs是用在服务器端的,同步的,如nodejs amd, cmd是用在浏览器端的,异步的,如requirejs和seajs 其中,amd先提出,cmd是根据commonjs和amd基础上提出 ...
随机推荐
- 把项目从码云上clone到IntelliJ IDEA
前期工作:安装并已配置好git,并且IDEA已经配置好git了 操作如下: 1) 在IDEA启动页面选择Get from Version Control 2) 打开码云上想要clon ...
- Elasticsearch和Scala类型转换
Scala Type ES Unit null None null Nil empty array Some[T] according to the table Map object Traver ...
- Redis好文章推荐
文章来源:掘金 作者:敖丙 Redis-避免缓存穿透的利器之BloomFilter <我们一起进大厂>系列- Redis基础 <我们一起进大厂>系列-缓存雪崩.击穿.穿透 ...
- C语言学习笔记之数组与指针的关系
首先,大家先需知道一个关于基类型的概念 基类型:组成一个新类型的基础类型 这句话是什么意思呢?举个例子: int a[3] = {1,2,3}; 上面是由三个int类型的数组成一个新的类型也就是数组, ...
- Linux学习笔记之如何设置vim中的格式如行号等
在我们编写代码程序时,我们时常想追求更好的格式,下面写一下我认为挺实用的格式命令以及如何更改 如果我们打开vim在其命令模式中输入格式命令时,下一次重新打开vim还是会和原先一样,所以我们需更改其配置 ...
- Who Am I? Personality Detection based on Deep Learning for Texts 阅读笔记
文章目录 源代码github地址 摘要 2CLSTM 过程 1. 词嵌入 2. 2LSTM处理 3. CNN学习LSGCNN学习LSG 4. Softmax分类 源代码github地址 https:/ ...
- MD5算法——C++实现
MD5算法原理 MD5消息摘要算法,属Hash算法一类.MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要. 具体实现可参考博客 https://blog.csdn.net/sinat ...
- X86汇编——计算斐波那契数列程序(详细注释和流程图说明)
X86汇编实现斐波那契数列 程序说明: 输入斐波那契数列的项数, 然后依次输出斐波那契数列, 输入的项数小于256且为数字, 计算的项数不能超过2^16次方, 输入失败是 不会回显数字 因为存结果是A ...
- Dubbo系列之 (二)Registry注册中心-注册(2)
引导 本章主要介绍下AbstractRegistry.FailbackRegistry的作用和源码. AbstractRegistry 首先,直接引出这个类的作用,该类主要把服务提供者信息缓存本地文件 ...
- 土地购买 (斜率优化dp)
土地购买 (斜率优化dp) 题目描述 农夫 \(John\) 准备扩大他的农场,他正在考虑$ N(1 \leqslant N \leqslant 50,000)$ 块长方形的土地. 每块土地的长宽满足 ...