个人主页:爱吃炫迈
系列专栏:前端工程化
座右铭:道阻且长,行则将至
模块化的最终目的是将程序划分成一个个小的结构,这个结构中编写属于自己的逻辑代码,有自己的作用域,定义变量名词时不会影响到其他的结构,这个结构可以将自己希望暴露的变量,函数,对象等导出给其他结构使用,也可以通过某种方式,导入另外结构中的变量,函数,对象等
上面提到的结构,就是模块;按照这样结构划分开发程序的过程,就是模块化开发的过程
举个栗子:
//A.js
var name = "jack"
console.log(name)
//B.js
var name = "belle"
console.log(name)
//index.html
<body>
<script src="./A.js"></script>
<script src="./B.js"></script>
</body>
解决方案:立即函数调用表达式
因为是函数,函数有自己的作用域。两个name没有关系,就不会产生命名冲突的问题
//A.js
const moduleA = (function() {
var name = "jack"
console.log(name)
return {
name,
}
}())
//B.js
const moduleB = (function() {
var name = "belle"
console.log(name)
return {
name,
}
}())
这又带来了新的问题:
我必须记得每一个模块中返回对象的命名,才能在其他模块使用过程中正确的使用
代码写起来混乱不堪,每个文件中的代码都需要包裹在一个匿名函数中来编写
在没有合适的规范情况下,每个人、每个公司都可能会任意命名,甚至出现模块名称相同的情况
CommonJS是一个规范,最初提出来是在浏览器以外的地方使用
Node是CommonJS在服务器端一个具有代表性的实现
Browserify是CommonJS在浏览器中的一种实现
webpack打包工具具备对CommonJS的支持和转换
Node中对CommonJS进行支持和实现,让我们在开发node的过程中可以方便的进行模块化开发
exports
、module.exports
、require
,我们可以使用这些变量来方便的进行模块化开发。exports
和module.exports
可以负责对模块中的内容进行导出require
函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容exports
是一个对象,我们可以在这个对象中添加很多个属性,添加的属性会导出
exports.name = name
exports.age = age
exports.sayHello = sayHello
main.js文件中可以导入:
const bar = require('./bar')
解释:
这行代码使得main中的bar变量等于exports对象,也就是require通过各种查找方式,最终找到了exports这个对象,并且将这个exports对象赋值给了bar变量,即bar变量就是exports对象了。
内存图:
写法:
module.exports.name = name
module.exports.age = age
module.exports.sayHello = sayHello
在Node中我们经常用module.exports
导出东西,那为什么exports
对象也可以导出呢?
CommonJS中是没有
module.exports
概念的,但是为了实现模块的导出,Node中使用的是Module的类,每一个模块都是Module的一个实例,也就是module对象,所以在Node中真正用于导出的根本不是exports
,而是module.exports
,因为module才是导出的真正实现者。那么为什么exports
也可以导出呢,这是因为module对象的exports属性
是exports
对象的一个引用,也就是说,module.exports = exports = main中的bar
开发中常用的写法:
module.exports = {
name,
age,
sayHello
}
此时module对象指向一个新的对象,此时如果再使用exports
导出是没有用的。
require
是一个函数,可以帮助我们引入一个文件(模块)中导出的对象
require
查找规则:
导入格式如下:require(X)
情况一:X是一个Node核心模块,比如path、http
情况二:X是以./ 或 …/ 或 /(根目录)开头的
第一步:将X当做一个文件在对应的目录下查找
第二步:没有找到对应的文件,将X作为一个目录
如果没有找到,那么报错:not found
情况三:直接是一个X(没有路径),并且X不是一个核心模块
同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行,这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快。
浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行,那么采用同步就意味着后序的js代码都无法正常运行,即使是一些简单的DOM操作。
当然在webpack中使用CommonJS是另外一回事,因为它会将我们的代码转成浏览器可以直接执行的代码。
但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助webpack等工具可以实现对CommonJS或者ES Modules代码的转换,所以AMD和CMD已经使用非常少了。
因为每个模块对象module都有一个loaded属性,为false表示还没有加载,为true表示已经加载
Node采用的是深度优先算法:main -> aaa -> ccc -> ddd -> eee ->bbb
ES Module和CommonJS的不同:
一方面它使用了import和export关键字
另一方面它采用编译期的静态分析,并且也加入了动态引用的方式
ES Module模块采用export和import关键字来实现模块化:
export负责将模块内的内容导出;
import负责从其他模块导入内容;
export关键字将一个模块中的变量、函数、类等导出
export {
name,
age,
sayHello
}
export {
name as myname,
age,
sayHello
}
export const name = "kobe"
export const age = 18
export function sayHello() {
console.log("sayHello")
}
import关键字负责从另外一个模块中导入内容
import {name, age, sayHello} from "./bar.js"
import {name as myname, age, sayHello} from "./bar.js"
import * as bar from "./bar.js"
在开发和封装一个功能库时,通过我们希望将暴露的所有接口放到一个文件中,这样方便指定统一的接口规范,也方便阅读,这个时候我们就可以使用export和import结合使用。
import {sayHello, sayNo} from './bar.js'
import {sayHi} from './foo.js'
export {
sayHello,
sayNo,
sayHi
}
优化:
export {sayHello, sayNo} from './bar.js'
export {sayHi} from './foo.js'
默认导出export时可以不需要指定名字;
在导入时不需要使用 {},并且可以自己来指定名字;
它也方便我们和现有的CommonJS等规范相互操作;
在一个模块中,只能有一个默认导出(default export)
export default sayHello
export default function() {
return ["hello"]
}
导入
import abc from './bar.js'