深入浅出JS—24谈谈前端模块化

早期JS中用不到模块化,JS多用来表单验证,没必要放到多个文件中来写;
后来JS需求变得复杂:ajax前后端分离、SPA、前端路由和Node等,使得对模块化的需求愈加强烈。
为此社区中产生了AMD、CMD、CommonJS等模块化规范,随着官方ES6中ES Module的提出,社区规范可以落幕了

什么是模块化

  • 将程序划分为一个个模块,在模块内部可以编写属于自己作用域的代码,不会影响其他模块
  • 模块可以将自己希望暴露的东西暴露给别的模块使用,也可以导入其他模块暴露的变量、函数、对象等
  • 一个独立的js文件就是一个模块

没有模块化带来的问题

在html文件中,<script>标签导入的文件没有独立作用域,会出现变量命名冲突,产生难以察觉的bug,可以采用立即执行函数来包裹每个js文件,将需要暴露的变量作为函数返回值

//a.js:暴露变量a
var moduleA = (function () {
  var a = "aaaaa";

  return {
    a: a,
  };
})();
// index.js:引用a.js中变量a
(function () {
  // 现在有全局变量moduleA
  var a = moduleA.a;
  console.log("why", a);
})();

采用立即执行函数虽然解决了作用域的问题,但是依然存在模块命名冲突的问题,同时还需要记住名字,增加了维护成本

几种模块化的方案

1 CommonJS规范

Node中对CommonJS进行了支持和实现,让我们在开发node的过程中可以方便的进行模块化开发

  • 导出
const name = "xs";
function sum(n1, n2) {
  return n1 + n2;
}

// module.exports指向要导出的对象
module.exports = {
  name,
  sum,
  age: 18,
}

module.exports和exports之间建议使用前者,那么node中要exports何用,主要是为了符合CommonJS规范

  • 导入
const {name, age, sum} = require('文件路径.js')

缺点:
导入文件是同步的,在服务端导入的是本地文件,速度较快;但是在浏览器端,去服务器上请求文件会阻塞之后JS代码的运行,所以CommonJS一般用于浏览器端

2 AMD

Asynchronous Module Definition 异步模块定义

  • 异步加载模块
  • 用于浏览器端
  • 实现的库:requirejs

3 CMD

Common Module Definition 通用模块定义

  • 异步加载模块,吸收了CommonJS关键字
  • 用于浏览器端
  • 实现的库:SeaJS

4 ES Module⭐

4.1 用法

如果js文件中使用了ES Module的模块化规范,在html导入js文件时,必须设置type属性为module

<script src="./main.js" typr="module"></script>

同时,该html文件应以本地服务器的方式从浏览器获取,否则会出现CORS跨域问题。(可以安装VScode插件Live Server)

4.2 export & import

  • 导出:export ***
// 1 分别导出
export const name = "xs";
export function sum(n1, n2) {
  return n1 + n2;
}

// 2 一起导出
const name = "xs";
function sum(n1, n2) { return n1 + n2; }

export{
  name,
  age,
 // name: 'xs 错误,{}不是对象的意思,不能写键值对
}

// 3 默认导出 不能写为export default const foo = 18;
const age = 18;
export default age;
  • 导入:import *** from ***
// 1 直接导入
import {name, sum} from "./file.js";

// 2 导入时起别名
import {name as fName, sum} from "./file.js";

// 3 全部导入
import * as foo from "./file.js"
foo.name // 见名知意:知道是foo导入的name,区分于当前文件中定义的name
foo.sum

// 4 默认导入 在导入时不需要使用 {},并且可以自己来指定名字
import age from "./file.js"
  • 导入+导入 export *** from ***

在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中;
这样方便指定统一的接口规范,也方便阅读;这个时候,我们就可以使用export和import结合使用

场景:utils文件夹下有文件:math.js、format.js等,每个文件都提供了一些通用的工具方法
通常在utils文件夹下创建一个index.js文件,导入math.js、format.js中暴露的函数,然后再依次暴露出去。

// index.js

// 导出方式一
import { add, sub } from "./math.js";
import { priceFormat, typeFormat } from "./format.js";

export { add, sub, priceFormat, typeFormat };

// 导出方式二
export { add, sub } from "./math.js";
export { priceFormat, typeFormat } from "./format.js";

// 导出方式三
export * from "./math.js";
export * from "./format.js";

4.3 静态+动态导入

静态:写在文件开头,在代码执行前JS引擎先进行解析
动态:写进逻辑里,在代码执行时动态导入,使用import函数,返回值是一个promise

let isFlag = true;
if (isFlag){
  import("./module/aaa.js").then((aaa)=>{
	aaa.aaa();
  })
}else{
  import("./module/bbb.js").then((aaa)=>{
	bbb.bbb();
  })
}

4.4 原理

识别到模块化之后,解析有三个阶段

  • 构建阶段
    fetch:获取下载文件
    parse:解析为模块记录
    其中使用module map映射表来管理各依赖模块的信息
    在这里插入图片描述

  • 实例化阶段+ 求值阶段
    实例化阶段:对模块记录进行实例化,并且分配内存空间,解析模块的导入和导出语句,把模块指向对应的内存地址
    求值阶段:运行代码,计算值,并且将值填充到内存地址中
    在这里插入图片描述
    只有导出模块可以更改变量的值,导入模块无法更改变量的值

总结

AMD和CMD现在很少用了,成为时代的眼泪。目前虽然CommonJS还在用,但是ES Module作为JS语言本身提出的规范,势必会一统天下。在当前的webpack开发中,由于webpack内部做了支持,可以让CommonJS和ES Module混用,譬如CJS语法导出,ESModule语法导入,但最好不要混用。
一句话:认准并学好ES Module!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值