前端模块化

388 阅读5分钟

六月学习记录

前言

在六月份,由于在一次需要项目打包的时候我完全没有头绪,因为之前没有特意去了解过,只是知道webpack等一些工具是用来打包项目的,至于打包的格式、原理都一概不知(可能有看过,只是都忘了)。于是我对自己做了反思,拓展知识的时候一定要去了解它的历史和原理,不要觉得记住几个api就会了,只要知道了原理,基于该知识的任何框架和工具都是手到擒来!
一开始打包的需求只是把我的脚本ES6转成ES5,后面顺藤摸瓜,查找了好多跟打包有关的知识,同时我又在看红宝书,让我的脑子一下子有点吸收不来,所以写下这篇文章记录一下自己脑中尚存的知识碎片。写文章的重点是,让自己散乱的知识点有意识去寻找关系,从而能够给自己梳理,查漏补缺o(╥﹏╥)o。

  • 模块化 (CommonJSAMD, CMD, UMD, ES6, IIFE
  • JavaScript 编译器(bable
  • 打包工具(rollup, webpack, father
  • 环境(node环境, 浏览器环境 编译时, 运行时
    这篇文章先对模块化进行梳理。

一. 模块化的理解

1. 什么是模块化?

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起。
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信。

2. 模块化的发展历程

  • js一开始并没有模块化的概念,直到AJAX被提出,前端能够像后端请求数据,前端逻辑越来越复杂,就出现了许多问题:全局变量,函数名冲突,依赖关系不好处理
  • 当时使用自执行函数(IIFE)来解决这些问题,比如经典的jquery就使用了匿名自执行函数封装代码,将他们全都挂载到全局变量jquery下边,$(document).ready(function(){}
  • 后来随着js的出场机会变多,逻辑交互变得更加复杂,功能越来越多,就需要拆分成多个文件,这就导致一个HTML页面需要加载多个script,而且还可能出现命名冲突,同步加载导致页面卡顿,还要确保加载js依赖正确。So。
  • 2009 年 1 月,Node.js横空出世,它采用 CommonJS 模块化规范,同时还带来了 npm (全球最大的模块仓库) ,随后出现了AMDCMDUMD、再到现在最流行的ESM,都是根据JS推出的模块化规范。

AJAX不是JavaScript的规范,它只是一个哥们“发明”的缩写:Asynchronous JavaScript and XML,意思就是用JavaScript执行异步网络请求。其实就是XMLHttpRequest,通过onreadystatechange异步回调获取数据达到更新效果。主要问题:不同浏览器需要写不同代码,并且状态和错误处理写起来很麻烦。但是jqueryajax封装了一遍,不但不需要考虑浏览器问题,代码也能大大简化,所以我一度以为ajax就是jquery框架伟大的api。o(╥﹏╥)o

二. 模块化规范

1. CommonJS

1)描述

  • 它是Node环境中的JS模块化规范,在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
  • 在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理

2)特点

  • 所有代码都运行在模块作用域,不会污染全局作用域。
  • 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。
  • 模块加载的顺序,按照其在代码中出现的顺序。

3)基本语法

  • module.exports = {}exports.XX = XX导入,实际上每个文件都被视为独立的模块,每个模块的全局作用域就是module(module 实际上不是全局的,而是每个模块的局部。),exports就是一个导出的对象,require("文件路径")导入模块。
// a.js
let value = "a";
let add = () => {
    return value + 1
}
module.exports = {value, add}
// or
// exports.value = value
// exports.add = add
// main.js
const a = require(./a.js) // “./”是相对路径
const test = a.add();
console.log(test) // "a" + 1 = "a1"
console.log(a.value) // "a"

4) 加载机制

  • CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值(正在研究原理)。require是按顺序同步加载的,而且是运行时加载。

5) 缓存机制

  • 模块在第一次加载后被缓存。 这意味着(类似其他缓存)每次调用 require('foo') 都会返回完全相同的对象(如果解析为相同的文件)。
  • 如果 require.cache 没有被修改,则多次调用 require('foo') 不会导致模块代码被多次执行。

6)运行

  • node环境可以同步加载运行
  • 浏览器环境要借助打包工具编译后运行

2. AMD

1)描述

  • AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

2)特点

  • 异步加载依赖,预先加载所有依赖。
  • 使用require.js库,去官网下载导入项目。

3)基本语法

  • require.config()指定引用路径等,用define()定义模块,用require()加载模块。
/** 网页中引入require.js及main.js **/
<script src="js/require.js" data-main="js/main"></script>

// main.js 入口文件/主模块 
// 首先用config()指定各模块路径和引用名
require.config({
  baseUrl: "js/lib",
  paths: {
    "jquery": "jquery.min",  //实际路径为js/lib/jquery.min.js
    "underscore": "underscore.min",
  }
});
// 执行基本操作
require(["jquery","underscore"],function($,_){
  // some code here
});
  • 自己定义的模块
// math.js
// 如果需要引入其他依赖,第一个参数为["依赖名"]
define(function () {
    var basicNum = 0;
    var add = function (x, y) {
        return x + y;
    };
    return {
        add: add,
        basicNum :basicNum
    };
});

// main.js
require(['jquery', 'math'],function($, math){
  var sum = math.add(10,20);
  $("#sum").html(sum);
});

3. CMD

1)描述

  • CMD是另一种模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。
  • CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行CMD规范整合了CommonJSAMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

2)特点

  • CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。
  • 整合了CommonJSAMD规范的特点。
  • 使用Sea.js库,官网下载查看

3)基本语法

// module1.js
define(function(require, exports, module) {
    module.exports = {
      msg: 'I Will Back'
    }
})
// main.js文件
define(function (require) {
  var m1 = require('./module1')
})
// html中引入sea.js和入口文件
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
  seajs.use('./js/modules/main')
</script>

4. UMD

1)描述

  • UMD (Universal Module Definition), 希望提供一个前后端跨平台的解决方案(支持AMDCommonJS模块方式),。

2)实现原理

  • 先判断是否支持Node.js模块格式(exports是否存在),存在则使用Node.js模块格式。
  • 再判断是否支持AMDdefine是否存在),存在则使用AMD方式加载模块。
  • 前两个都不存在,则将模块公开到全局(windowglobal),也就是IIFE(立即执行函数)。
  • UMDgithub源码(jQuery使用)
// 这里是没有模块依赖的简化代码
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // 当前环境有define方法,使用AMD规范. 
        define([], factory);
    } else if (typeof exports === 'object') {
        // Node环境.
        module.exports = factory();
    } else {
        // Browser globals (root is window)
        root.returnExports = factory();
  }
}(this, function () {

    // Just return a value to define the module export.
    // This example returns an object, but the module
    // can return a function as the exported value.
    return {};
}));

5. ES6 Module

1)描述

  • ES6 前,实现模块化使用的是 RequireJS 或者 seaJS(分别是基于 AMD 规范的模块化库和基于 CMD 规范的模块化库)。
  • ES6 引入了模块化,其设计思想是在编译时就能确定模块的依赖关系,以及输入和输出的变量。

2)特点

  • 编译时加载,在代码静态解析阶段就会生成。
  • ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict;
  • 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
  • 每一个模块只加载一次(是单例的), 若再去加载同目录下同文件,直接从内存中读取。

3)基本语法

  • export 命令可以出现在模块的任何位置,但必需处于模块顶层。
  • import 命令会提升到整个模块的头部,首先执行。
// export1.js
const addV = (v) => {
    return v + value
}
export const value = 123
export default addV
// as 重新命名
import addV as anything, {value} from "./export1"
console.log(value) // 123;
console.log(anything(1)) // 124;

三. 总结

  • CommonJS服务器端浏览器端都可以用,服务器端动态同步加载模块的,浏览器端需要先编译打包再引入。
  • AMDCMD都专供浏览器端,动态异步加载模块,分别需要引入Require.jsSea.js
  • UMD是兼容CommonJSAMD,以及IIFE的打包格式。
  • ES6服务器端和浏览器端都可以用
    • node环境使用ES6模块化规范有两种方式,后缀改为.mjs或者在package.json里面加type: module
    • 在浏览器使用需要用打包工具编译打包引入。 end:)有新发现再补充,下一篇文章主要总结一下自己使用过的打包工具,查漏补缺be better