前端模块化的发展史

前端模块化的发展史

1、早期

Javascript 不是一种模块化编程语言,在 ES6 以前,它是不支持类 class,所以也就没有模块 module。因此社区一直在寻找如何实现模块化效果的最佳可行性方案。

  • 原始写法:模块就是实现特定功能的一组方法。只要将多个函数放到一个文件中,引入这个文件就可以直接使用。缺点:污染全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
  • 对象写法:把模块写成一个对象,所有的模块成员都放到这个对象里面。使用的时候,就是调用这个对象的属性。缺点:这样的写法会暴露所有模块成员,内部状态可以被外部改写。
  • 自执行函数:这种方式解决了上述的所有问题,也是后续 Javascript 模块化的基本写法。
var module = (function() {
  var _count = 0
  var m1 = function() {
    alert(_count)
  }
  var m2 = function() {
    alert(_count + 1)
  }

  return {
    m1: m1,
    m2: m2
  }
})()

2、CommonJS 和 AMD 时代

2.1、CommonJS

  • 在 2009 年 8 月,CommonJS 诞生了。CommonJS 规范为 JavaScript 制定了一个美好的愿景——希望 JavaScript 能够在任何地方运行。以达到像 Python、Ruby 和 Java 具备开发大型应用的基础能力,而不是停留在小脚本程序的阶段。
  • 同年 NodeJS 被创建,javascript 语言开始用于服务器端编程。
  • CommonJS 是一套规范,NodeJS 是这种规范的实现。
    • 所有代码都运行在模块作用域,不会污染全局作用域。
    • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
    • 模块加载的顺序,按照其在代码中出现的顺序。
    • 在 CommonJS 中暴露模块使用 module.exports 和 exports。
    • 在 CommonJS 中,有一个全局性方法 require(),用于加载模块。
  • 正是由于 CommonJS 使用的 require 方式的推动,才有了后面的 AMD、CMD 也采用的 require 方式来引用模块的风格

2.2、AMD、CMD

有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。但是,由于一个重大的局限,使得 CommonJS 规范不适用于浏览器环境。

CommonJS 采用的是同步加载。在服务器端所有的模块都存放在本地硬盘,可以同步加载完成。而浏览器则取决于网速问题不可能等待文件被加载。因此只能采用异步加载。

采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。同时期出现两种规范:AMD (Asynchronous Module Definition)(代表:requirejs) 和 CMD (Common Module Definition) (代表:seajs)。

两者异同点

  • 都是用 define 定义模块使用 require 加载模块。
  • AMD 采用的是依赖前置,CMD 采用的是依赖就近。
// CMD
define(function(require, exports, module) {
  var a = require('./a') // 依赖在使用的时候引入
  a.doSomething()
  var b = require('./b')
  b.doSomething()
})

// AMD
define(['./a', './b'], function(a, b) {
  // 依赖在刚开始的时候定义
  a.doSomething()
  b.doSomething()
})
  • AMD 依赖前置,js 可以方便知道依赖模块是谁,立即加载。
  • CMD 就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病 CMD 的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略

3、ES6 时代

ES6 标准发布后,module 成为标准,标准使用是以 export 指令导出接口,以 import 引入模块。

在前端应用中我们使用新的标准,但是在开发 node 应用使用我们一贯的使用 CommonJS 规范。

module 的语法参考之前写的文章 ”ES6 Module 多种用法“

4、知识点

4.1、CommonJS 中 module.exports 与 exports 的区别

  • 1、一个模块被引用的时候最终都会被输出成 module.exports,而 exports 只是 module.exports 的一个引用。
  • 2、每个导出模块 node.js 默认会返回 module.exports。
// 根据规则 1
// exports ===  module.exports
// exports 是 module.exports 一个引用
// 因此下边两个是等价的
exports.name = ''
module.exports.name = ''

// 如果直接给 exports 赋值
// 这种写法改变了 exports 的引用给他新分配了一个引用
// 因此:
// exports === { name: '' }
// exports !== module.exports
// module.exports !== { name: '' }
// module.exports === undefined
// 因此导出的模块是空的,原因是上述规则的第二条
exports = { name: '' }

4.2、webpack 中 require.ensure() 和 import()

require.ensure()是 webpack 用来加载异步模块的方式,import()是 ES 提案。

虽然 require.ensure()任然可以使用但是建议使用 import()。

require.ensure() 接收四个参数,依赖数组、回调函数、错误回掉函数(可选)和 chunk 块名

const A = r => require.ensure([], () => r(require('@views/make/A')), 'A')
const B = () => import(/* webpackChunkName: "B" */ '@views/make/B')

4.3、webpack 中,如和使用变量引入对应文件

官网地址

在 ES 规范中提出允许 import 函数使用变量去加载文件,但是 webpack 目前还不完全支持。例如 import(foo),这样完全动态的加载方式将会失败,因为 webpack 需要一些文件位置信息。因为变量 foo 可能是系统或项目中任何文件的路径。import()必须至少包含关于模块所在位置的一些信息,因此让捆绑可以局限于特定的目录或文件夹。因此需要如下使用方式:

// 必须由三部分组成
// 1、文件的目录至少一级
// 2、文件路径 + 文件名
// 3、文件后缀
import('./' + path + '.js')
require('./' + path + '.js')

这样做 webpack 会根据 1 和 3 将所有符合条件的文件都打包到一起,然后根据结果返回想要的内容。这意味着 webpack 能够支持动态地 require,但会导致所有可能用到的模块都包含在 bundle 中。

require.context

通过 require.context() 函数来创建自己的 context。可以给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。

require.context(directory, (useSubdirectories = false), (regExp = /^\.\//))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值