JS模块化发展历程(初稿)

本文讲述了JavaScript模块化的发展历程,包括幼年期的基于文件的模块化,成长期的IIFE优化,成熟期的CommonJS和AMD,以及完全体的ESM标准,强调了模块化如何推动编程范式变化,如函数式编程和组件化。
摘要由CSDN通过智能技术生成

背景

网景公司的那个胖子,用了七天的时间开发出了 JavaScript,JS最初的目标是实现简单的页面交互:页面动画 + 提交表单。此时并无模块化或命名空间的基础。

但计算机硬件的发展,用户的电脑越来越好,业务人员希望网页能够实现越来越复杂的功能,开发人员也愿意为此付出劳动,随着业务逻辑日趋复杂,简单的JS无法再满足人们的需求,于是模块化需求日益增长。

我们可以把模块化的发展氛围四个时期:幼年期、成长期、成熟期和完全体。那么废话不多说,开始吧!

幼年期:以js文件为维度的委婉的模块化

开始出现各种js库:动画、表单、格式化等。以js文件的形式被加载。你最初学习 html 时,应该写过如下代码,这是文件分离最基础的模块化方式。

<!-- 库文件 -->
<script src="tools.js"></script>
<script src="map.js"></script>

<!-- 主文件 -->
<script src="main.js"></script>

这里会涉及到 script 两个参数 :async 和 defer

<script src="tools.js" async></script>
<script src="map.js" defer></script>

浏览器加载一个js文件时,先下载,再解析,那上面两个参数区别是?

普通(不加参数):同步下载,下载文件,解析文件,执行后续代码。

defer:异步下载,下载文件,执行后续代码,待DOM解析完成,再解析文件。

async: 异步下载,下载文件,执行后续代码,下载完成后解析文件,阻塞线程。

这个问题常常伴随着话题导向:浏览器渲染原理、同步异步原理、模块化加载原理。本文介绍的是第三个。

这种模块化的缺点

污染全局作用域,不利于大型项目或涉及多人团队共建。

成长期:模块化初现 - IIFE

IIFE:立即执行函数

这个时期并没有出现新的技术,只是利用了语法的优势:函数的独立作用域进行的一种写法优化。

举个例子,我写一段幼年期的模块文件

// count.js
let count = 0;

const increase = () => ++count;

const reset = () => {
  count = 0;
}

// 调用方式,全局调用
increase()
reset()

按照幼年期的引入方式,这个 count  就成了全局变量,会污染全局。

而我使用 IIFE,一个立即执行的函数,其写法如下

const module = (() => {
  let count = 0;
  return {
    increase: () => ++count,
    reset: () => {
      count = 0;
    }
  }
})();

// 使用方式,挂在在 module 下调用
module.increase();
module.reset();

这样 count 就成了一个局部变量,要使用它时,就需要通过 module 模块,这就是 IIFE 的魅力 ,它可以抽象成以下方式,即定义了一个匿名函数并直接执行。

(() => {
  let count = 0;
  // ...
})()

 这是最最最简单的模块化,也是最最最基础的模块化,你可以在 jquery 中看到它的身影,你甚至在 webpack 中配置打包方式设置为 "IIFE" 时,你打包出来的代码也是这样的格式,它简单但也很流行。

问题:如果我的库依赖外部库,怎么办呢?答案是在 IIFE 里传参即可,放码过来

const module = ((dependencyModule1, dependencyModule2) => {
  //
})(dependencyModule1, dependencyModule2);

成熟期:百花齐放

CommonJS

CommonJS极大地推动了 JavaScript 的发展,它直接改变了生态(破圈了)!因为它不仅仅实现了前端模块化,它还直接可以让 JS 向服务端发展,最典型的就是 NodeJs,NodeJs的制定就是以它为标准的。它的主要特征是

  1. 通过 module + export 向外暴露接口
  2. 通过 require 调用其他接口

show一段代码看看 CJS 是如何使用

// 在 count.js 中

// 引入部分
const dependencyModule1 = require(../dependencyModule1)
const dependencyModule2 = require(../dependencyModule2)

let count = 0
const increase = () => ++count;

const reset = () => {
  count = 0;
}

// 暴露的接口
module.exports = {
  increase,
  reset
}

优点

  • 解决了全局污染的问题。
  • 完完全全从主观感受上实现了模块化,虽然 IIFE 也是模块化,但写法挺没有”模块感“。

缺点:require 是一个同步加载的过程,不能解决异步问题。

AMD:异步加载 + 回调函数

最经典的框架是 require.js,其大致代码可表示为如下

define(
  "amdModule", 
  ["dependencyModule1", "dependencyModule2"], 
  (dependencyModule1, dependencyModule2) => {

})

require(["amdModule"], amdModule => {
  amdModule.increse()
})

如果一个模块,既使用了 CJS ,又使用了 AMD,那么应该如何区分呢?UMD。

AMD的优点解决了服务端,客户端的动态以来问题,也实现了异步加载。

缺点是,没有按需加载。需要先加载所有的依赖,才能触发回调,但在回调里的代码可能并不会全部执行,可能在某一个分支就 return 了,这样的话后续代码就用不到某些依赖。

CMD:按需加载,依赖就近

主要框架:sea.js

define('module', (require, exports, module) => {
  let $ = require('map');

  if (xxx) {
    return;
  }

  let depends2 = require('./dependencyModule2');
})

缺点是

  1. 依赖打包,需要编译(虽然现在工程化都需要打包,但其实还有更好的模块化方法,正因为有更好的方法,所以以来打包就变成了不足之处,继续读下文)
  2. 扩大了模块化内的体积

完全体:ESM,走进新时代

ES6新增标准:引入 import,导出 export。我们不再需要第三方库,就能实现模块化,ES6已经把模块化这个思想写进 JavaScript 规则里,只要调用 import 和 export 就能实现模块化,不管是异步加载,还是按需加载。

// 引入区域
import dependencyModule1 from './dependencyModule1';

// 逻辑处理
// ……

export default {
  increase, reset
}


// 动态加载
import("./dependencyModule1").then(dependencyModule1 => {})

模块化给编程带来的改变

  • 模块化:解耦、组件化。
  • 面向过程转变成面向对象。
  • 模块化的基础造就了函数式编程。

函数式编程

每个函数都是一个模块,将大问题转化成小问题,将复杂问题转化成简单问题,最典型的就是 React,React的每个组件都是一个 function 函数。

function Component() {
  const [count, increaseCount] = useState(0)

  const onClick = () => {
    increaseCount(++count)
  }

  return (<>
    <button onClick={onClick}>{count}</button>
  </>)
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值