模块化的历史
在网页开发的早期,JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的:
这个时候我们只需要讲JavaScript代码写到<script>标签中即可,并没有必要放到多个文件中来编写,甚至流行︰通常说JavaScript程序的长度只有一行。但是随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了∶ajax的出现,前后端开发分离,意味着后端返回数据后,我们需要通过JavaScript进行前端页面的渲染;SPA的出现,前端页面变得更加复杂︰包括前端络由、状态管理等等一系列复杂的需求需要通过JavaScript来实现;包括Node的实现,JavaScript编写复杂的后端程序,没有模块化是致命的硬伤;所以,模块化已经是JavaScript一个非常迫切的需求:但是JavaScript本身,直到ES6才推出了自己的模块化方案,在此之前,为了让JavaScript支持模块化,涌现出了很多不同的模块化规范:AMD、CMD、CommonJS等。
那什么是模块化、模块化开发呢?
- 事实上模块化开发最终的目的是将程序划分成一个个小的结构;这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构。
- 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;也可以通过某种方式,导入另外结构中的变量、函数、对象等。
- 上面说提到的结构,就是模块按照这种结构划分开发程序的过程,就是模块化开发的过程,无论你多么喜欢JavaScript,以及它现在发展的有多好,它都有很多的缺陷︰比如var定义的变量作用域问题,比如JavaScript的面向对象并不能像常规面向对象语言一样使用class,比如JavaScript没有模块化的问题。
模块化开发是一种管理方式,一种生产方式,一种解决问题的方案。一个模块就是实现某个特定功能的文件,我们可以很方便的使用别人的代码,想要什么模块,就引入那个模块。但是模块开发要遵循一定的规范,后面就出现了我们所熟悉的AMD、CMD、CommonJS和ESModule规范。
AMD
AMD主要是应用于浏览器的一种模块化规范,AMD是Asynchronous Module Definition(异步模块定义)的缩写,它采用的是异步加载模块,事实上AMD的规范还要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的较少了。AMD实现的比较常用的库是require.js和curl.js。
foo.js
define(function(){
const name = 'bx'
const age = 10
function sum(num1,num2){
return num1 + num2
}
return {
name,age,sum
}
})
bar.js
define(function(){
require(['foo'],function(foo){
console.log('bar:',foo)
})
})
main.js
require.config({
baseUrl:'./src',
path:{
foo:'./foo',
bar:'./bar'
}
})
require(['foo'],function(foo){
console.log('main:',foo)
})
CMD
CMD规范也是应用于浏览器的一种模块化规范:CMD是Common Module Definition(通用模块定义)的缩写,它也采用了异步加载模块,但是它将CommonJS的优点吸收了过来,但是目前CMD使用也非常少了,CMD也有自己比较优秀的实现方案:SeaJS。
foo.js
define(function(require,exports,module){
const name = 'bx'
function sum(num1,num2){
return num1 + num2
}
module.exports = {
name,sum
}
})
mian.js
define(function(require,exports,module){
const foo = require('./foo')
console.log(foo)
})
CommonJS
CommonJS规范主要应用于Node,每个文件就是一个模块,有自己的作用域,即在一个文件中定义的变量、函数、类都是私有的,对其他文件不可见。
- CommonJs规范规定,require用来加载某个需要的模块。module代表的是当前模块,是一个对象,存储着当前模块的相关联的属性和方法。
- exports是module上的一个属性。该属性表示当前模块对外输出的接口,其它文件加载该模块,实际上就是读取module.exports变量。
CommonJS规定模块可以多次加载,会在第一次加载时运行一次结果就会被缓存下来,以后再加载就直接读取缓存结果,如果想让模块再次运行,必须清除缓存。
ESModule
JavaScript没有模块化一直是它的痛点,所以才会产生这些规范CommonJS、AMD、CMD等。
- ESModule和CommonJS的模块化有一些不同之处:一方面它使用了import和export关键字;另一方面它采用编译期的静态分析,并且也加入了动态引用的方式。
- ESModule模块采用export和import关键字来实现模块化:export负责将模块内的内容导出,import负责从其他模块导入内容。
export const name = 'bx'
export const age = 10
import {name,age} from './foo.js'
console.log(name)
console.log(age)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./main.js" type="module"></script>
</body>
</html>
这里运行要用live server,否则会报错
CommonJS和ESModule区别
语法不同
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
- CommonJS是对模块的浅拷贝,ES6 Module是对模块的引入,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const 。
- import的接口是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向。可以对commonJS对重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。
CommonJS和ESModule共同点
CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进行改变。