什么是模块化
- 模块化开发最终的目的是将程序划分成一个个小的结构
- 这个结构中编写属于自己的逻辑代码,有自己的作用域,不会影响到其他的结构
- 这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用
- 上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程
我着重学习的是ESmodule跟commonJS,这也是现在主流使用的模块化开发。
CommonJS
- Node中对CommonJS进行了支持和实现,在Node中每一个js文件都是一个单独的模块。
- 模块化的核心就是导入和导出
- exports和module.exports可以负责对模块中的内容进行导出
- require函数可以帮助我们导入其他模块(自定义模块、系统模块、第三方库模块)中的内容
- 细节:exports是一个对象,我们可以在这个对象中添加许多属性:
let name = 'xxx'
let age = '18'
function sayhello (){}
exports.name = name;
exports.age= age;
exports.sayhello = sayhello
- 另一个文件中(假定为main.js)可引入
const bar = require('./bar')
CommonJS的理解重点 - 意味着main中的bar变量等于exports对象,即bar变量与exports对象是同一个引用地址。
module.exports我们在node中经常用的模块化导出的方法
module.exports的引用地址与exports也是一样的,所以:module.exports = exports = main中的bar
CJS中的模块在被第一次引入时,模块中的js代码会被运行一次
-
CommonJS规范缺点
CommonJS加载模块是同步的:同步的意味着只有等到请求的模块加载完,当前模块的内容才会运行
这在服务器没什么问题,服务器加载的js文件都是本地文件,加载速度非常快,但是在浏览器中我们后面的一些简单的操作都无法运行
当然,webpack中会把CommonJS转换成浏览器可以执行的代码,所以我们也可以在Vue、React中的模块化开发中使用CommonJS
ESModule
- ES Module模块采用export和import关键字来实现模块化
- export负责将模块内的内容导出
- import负责从其他模块导入内容
- 采用ES Module将自动采用严格模式:use strict
- 需要注意的是:如果通过本地加载Html 文件 (比如一个 file:// 路径的文件), 将会遇到 CORS 错误,因 为Javascript 模块安全性需要。
- 解决方案是在VScode中安装Live Server插件
-
exports关键字:
方式一:在语句声明的前面直接加上export关键字
-
方式二:将所有需要导出的标识符,放到export后面的 {}中
- 这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的;
- 所以: export {name: name},是错误的写法
方式三:导出时取别名
-
方式四:默认导出,即export default {}的写法
我们可以在每一个模块中定义多个命名导出,但是只允许有一个默认导出。如果有多个默认导出,则后一个覆盖前一个
由命名导出的模块,在导入时必须要用相同的名称
由默认导出的模块,可以用任意的名称导入
方式一、方式二、方式三都是命名导出
// 导出事先定义的特性
export { myFunction,myVariable };
// 导出单个特性(可以导出var,let,
//const,function,class)
export let myVariable = Math.sqrt(2);
export function myFunction() { ... };
export {
name as fName,
age as fAge,
message as fMessage,
sayHello as fSayHello
}
方式四:默认导出
// 导出事先定义的特性作为默认值
export { myFunction as default };
// 导出单个特性作为默认值
export default function () { ... }
export default class { .. }
// 每个导出都覆盖前一个导出
-
import关键字
- 方式一:import {标识符列表} from '模块'
- 这里的{}也不是对象
- 方式一:import {标识符列表} from '模块'
import { name, age, message, sayHello } from './modules/foo.js';
- 方式二:导入时给标识符起别名
import { name as wName, age as wAge, message as wMessage, sayHello as wSayHello } from './modules/foo.js';
- 方式三:将模块功能放到一个模块功能对象(foo)
import * as foo from './modules/foo.js';
如果是默认导出的模块,在导入时不需要使用 {},并且可以自己来指定名字;
-
import还有动态加载的用法,即import函数
- 因为浏览器会做预解析,所以import直接写在逻辑代码里没办法运行
- 因此,如果要在逻辑代码中动态加载模块,要使用import函数
CommonJS与ES Module比较
-
CommonJS的加载过程
- CommonJS模块加载js文件的过程是运行时加载的,并且是同步的
- 运行时加载也就是说js引擎在执行js代码的过程中加载模块
- 同步的就意味着一个文件没有加载结束之前,后面的代码都不会执行
- CommonJS通过module.exports导出的是一个对象
- 导出的是一个对象意味着可以将这个对象的引用在其他模块中赋值给其他变量
- 但是最终他们指向的都是同一个对象,那么一个变量修改了对象的属性,所有的地方都会被修改
- CommonJS模块加载js文件的过程是运行时加载的,并且是同步的
-
ES Module加载过程
ES Module加载js文件的过程是编译(解析)时加载的,并且是异步的
-
ES Module通过export导出的是变量本身的引用
- export在导出一个变量时,js引擎会解析这个语法,并且创建模块环境记录(module environment record);
- 模块环境记录会和变量进行 绑定(binding),并且这个绑定是实时的;
- 而在导入的地方,我们是可以实时的获取到绑定的最新值的
注意
- 如果在导出的模块中修改了变化,那么导入的地方可以实时获取最新的变量;
- 在导入的地方不可以修改变量,因为它只是被绑定到了这个变量上(其实是一个常量)。也就是说,ES Module模式中,模块环境相当于使用一个变量const绑定了导出的引用地址,我们导入的时候不可以更改该地址。