放肆青春的博客
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)

放肆青春

一个前端菜鸟的技术成长之路
首页
前端
算法
网络
面试
技术
后端
运维
杂项
数据库
工具
网址
电脑
个人
文章
  • 分类
  • 标签
  • 归档
github (opens new window)
gitee (opens new window)
  • 前端

    • 前端 概览
    • 前端汇总

    • front 博文

    • front 项目总结

    • front 高级

      • 前端优化
      • 前端工程化
      • 前端模块化
        • 前端模块化
          • 模块化优点
        • 模块化规范
          • CommonJS
          • AMD
          • CMD
          • UMD
          • ES6 模块化
          • 原生 JS 模块化(Native JS)
        • 模块化对比
          • AMD 和 CMD 的区别
          • ES6 模块与 CommonJS 模块的差异
          • RequireJS 和 SeaJS 区别
        • 模块化导入
          • require
          • import
          • import 和 require 的区别
          • import()和 import
        • 模块化导出
          • modules.export
          • export
          • export default
          • export 和 module.export 的区别
          • export default 和 export 的区别
      • 前端监控
      • 数据可视化
      • 前端自动化测试
      • 前端安全
      • 微前端
      • 骨架屏
      • 预渲染
      • 服务器端渲染
      • 浏览器渲染
    • front tools

  • vue

    • vue 概览
    • vue 汇总

    • vue 博文

    • vue 项目总结

    • vue 高级

  • html

    • html 概览
    • html 汇总

    • html 博文

  • css

    • css 概览
    • css 汇总

    • css 博文

    • sass

    • less

  • js

    • javascript 概览
    • JS 汇总

    • ES6

    • JS 博文

    • JS 工具

  • node

    • node 概览
    • node 汇总

    • node 框架

    • node 博文

  • react

    • react 概览
    • react 汇总

    • react 博文

    • react 高级

  • 微信小程序

    • 微信小程序 概览
    • 微信小程序总结
    • 微信小程序文章
    • 微信小程序 博文

    • 微信小程序 高级

  • 微信公众号

    • 微信公众号 概览
    • 微信公众号总结
    • 微信公众号文章
  • 多端开发

    • 多端开发
    • dsbridge 概览
    • jsbridge 概览
    • webview
    • uniapp

      • uniapp 概览
    • taro

      • taro 概览
    • flutter

      • flutter 概览
      • flutter 环境搭建
    • electron

      • electron 概览
  • front
放肆青春
2021-02-26

前端模块化

# 前端模块化

image

JavaScript 在早期的设计中就没有模块、包、类的概念,开发者需要模拟出类似的功能,来隔离、组织复杂的 JavaScript 代码,我们称为模块化。

模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块。

# 模块化优点

  1. 避免命名冲突(减少命名空间污染)

  2. 更好的分离, 按需加载

  3. 提高代码复用率

  4. 提高了可维护性

# 模块化规范

常见的的 JavaScript 模块规范有:CommonJS、AMD、CMD、UMD、原生模块化,ES6 模块化

# CommonJS

CommonJs 是服务器端模块的规范,Node.js 采用了这个规范。

根据 CommonJS 规范,一个单独的文件就是一个模块。加载模块使用 require 方法,该方法读取一个文件并执行,最后返回文件内部的 exports 对象。

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像 Node.js 主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以 CommonJS 规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。

Commonjs 循环引用问题

如何解决循环加载的原理:循环加载时,属于加载时执行。即脚本代码在 require 的时候,就会全部执行,然后在内存中生成该模块的一个说明对象。当再次执行 require 命令,下次会直接读取缓存。

一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出(解决原理)

# AMD

AMD:(Asynchromous Module Definition) 异步模块定义

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出

AMD 异步加载模块。它的模块支持对象 函数 构造器 字符串 JSON 等各种类型的模块。

AMD 推崇依赖前置

对于依赖的模块,AMD 是提前执行

# CMD

CMD(Common Module Definition)公共模块定义

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出

CMD 推崇依赖就近

对于依赖的模块,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)

# UMD

UMD (Universal Module Definition) 通用模块定义

UMD 是 AMD 和 CommonJS 的综合产物。

AMD 浏览器第一的原则发展 异步加载模块。

CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。

这迫使人们又想出另一个更通用的模式 UMD (Universal Module Definition)。希望解决跨平台的解决方案。

UMD 先判断是否支持 Node.js 的模块(exports)是否存在,存在则使用 Node.js 模块模式。

在判断是否支持 AMD(define 是否存在),存在则使用 AMD 方式加载模块。

# ES6 模块化

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。而 CommonJS 和 CMD,都只能在运行时确定依赖。

可以进行可靠的静态分析,进而进行 tree-shaking

ES6 模块化特点:

  1. 只能作为模块顶层的语句出现

  2. import 的模块名只能是字符串常量

  3. import binding 是 immutable 的

ES6 模块化 循环引用问题

不论是基本数据类型还是复杂数据类型。当模块遇到 import 命令时,就会生成一个只读引用,脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

循环加载时,ES6 模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行(解决原理)

# 原生 JS 模块化(Native JS)

上述的模块都不是原生 JavaScript 模块。它们只不过是我们用模块模式(module pattern)、CommonJS 或 AMD 模仿的模块系统。

JavaScript 标准制定者在 TC39(该标准定义了 ECMAScript 的语法与语义)已经为 ECMAScript 6(ES6)引入内置的模块系统了。

相对于 CommonJS 或 AMD,ES6 模块如何设法提供两全其美的实现方案:简洁紧凑的声明式语法和异步加载,另外能更好地支持循环依赖。

# 模块化对比

# AMD 和 CMD 的区别

  1. AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。

  2. AMD 推崇依赖前置。 CMD 推崇依赖就近,

  3. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)

  4. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹

# ES6 模块与 CommonJS 模块的差异

  1. 导入导出不同

① CommonJS 使用 require ,exports 或者 module.exports

② ES6 使用 import, export

  1. 输出不同

① CommonJS 模块输出的是一个值的拷贝

对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。

对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。

② ES6 模块输出的是值的引用,ES6 模块中的值属于【动态只读引用】

对于只读来说,即不允许修改引入变量的值,import 的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到 import 命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

对于动态来说,原始值发生变化,import 加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。

  1. 加载时机不同

① CommonJS 模块是运行时加载,CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。

② ES6 模块是编译时加载(静态加载)。 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

参考:https://www.cnblogs.com/unclekeith/archive/2017/10/17/7679503.html (opens new window)

# RequireJS 和 SeaJS 区别

  1. 两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 服务器端

  2. 两者遵循的标准有差异。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不同,导致了两者 API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。

  3. 两者社区理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。

  4. 两者代码质量有差异。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。

  5. 两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能,非常方便便捷。RequireJS 无这方面的支持。

  6. 两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致:开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。

# 模块化导入

# require

require 前端和后端 node 都可使用,用于引入模块、 JSON、或本地文件。

require 是 CommonJs,AMD,CMD 规范中定义的模块请求方式

require 是同步请求,require 支持条件导入、动态导入等

require 模块查找机制

  1. require 判断加载标识,优先加载缓存的模块 (加载过的文件会缓存起来,不会重复加载)

  2. 如果是加载核心模块,直接从内存中加载,并缓存

  3. 如果是相对路径,则根据路径加载自定义模块(不存在对应的文件,就将这个路径作为文件夹加载),并缓存

  4. 如果不是自定义模块,也不是核心模块,则加载第三方模块

(1)node 会去本级 node_modules 目录中找 xxx.js---->xxx.json ----> xxx.node(找到一个即返回),找到并缓存。

(2)如果找不到,node 则取上一级目录下找 node_modules/xxx 目录,规则同上

(3)如果一直找到代码文件的文件系统的根目录还找不到,则报错:模块没有找到。

第三方模块不会和核心模块加载冲突?不可能有第三方模块名会和核心模块名一样(规定)

参考:深入 Node.js 的模块加载机制 (opens new window)

require 避免重复加载

require 会接收一个参数——文件标识符,然后分析定位文件,接下来会从 Module 上查找有没有缓存,如果有缓存,那么直接返回缓存的内容。如果没有缓存,会创建一个 module 对象,缓存到 Module 上,然后执行文件,加载完文件,将 loaded 属性设置为 true ,然后返回 module.exports 对象。借此完成模块加载流程。

require 避免循环引用

如何解决循环加载的原理:循环加载时,属于加载时执行。即脚本代码在 require 的时候,就会全部执行,然后在内存中生成该模块的一个说明对象。当再次执行 require 命令,下次会直接读取缓存。

一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出(解决原理)

require 主要支持三种文件类型

  1. .js:.js 文件是我们最常用的文件类型,加载的时候会先运行整个 JS 文件,然后将前面说的 module.exports 作为 require 的返回值。
  2. .json:.json 文件是一个普通的文本文件,直接用 JSON.parse 将其转化为对象返回就行。
  3. .node:.node 文件是 C++编译后的二进制文件,纯前端一般很少接触这个类型。

require 加载文件夹

加载文件夹的顺序:

  1. 先看看这个文件夹下面有没有 package.json,如果有就找里面的 main 字段,main 字段有值就加载对应的文件。所以如果大家在看一些第三方库源码时找不到入口就看看他 package.json 里面的 main 字段吧,比如 jquery 的 main 字段就是这样:"main": "dist/jquery.js"。

  2. 如果没有 package.json 或者 package.json 里面没有 main 就找 index 文件。index.js / index.json / index.node

# import

import 是 es6 的一个语法标准,如果要兼容浏览器的话必须转化成 es5 的语法

注意,import 命令具有提升效果,会提升到整个模块的头部,首先执行。

由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

# import 和 require 的区别

  1. 遵循规范

require 是 AMD 规范引入方式

import 是 es6 的一个语法标准,如果要兼容浏览器的话必须转化成 es5 的语法

  1. 调用时间

require 是运行时调用,所以 require 理论上可以运用在代码的任何地方

import 是编译时调用,所以必须放在文件开头

  1. 本质

require 是赋值过程,其实 require 的结果就是对象、数字、字符串、函数等,再把 require 的结果赋值给某个变量

import 是解构过程,但是目前所有的引擎都还没有实现 import,我们在 node 中使用 babel 支持 ES6,也仅仅是将 ES6 转码为 ES5 再执行,import 语法会被转码为 require

# import()和 import

  1. import()是动态加载。

  2. import()返回一个 Promise 对象,import()加载模块成功以后,这个模块会作为一个对象,当作 then 方法的参数。

  3. import()类似于 Node 的 require 方法,区别主要是前者是异步加载,后者是同步加载

  4. import()通常用于按需加载、条件加载、动态的模块路径

# 模块化导出

# modules.export

nodeJS 采用 commonJs 规范,当前文件是一个模块(module)私有域,通过 exports 属性导出,通过 require()引入模块,通过.xx 去获取值,从而了解到加载某个模块,其实是加载该模块的 exports 属性

Node 内部提供一个 Module 构建函数。所有模块都是 Module 的实例

modules.export 给 module 实例中的 exports 对象添加的属性或者方法;

module.exports 属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取 module.exports 变量。

// 导出:example.js
module.exports.a = "111";
module.exports.fun = function() {};

// 导出:或者写成对象形式
module.exports = {
  a: "111",
  fun: function() {},
};

// 导入
let test = require("./example.js");

console.log(test.a);
console.log(test.fun);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# export

export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能。

// 引入模块的某些变量(方法、类),配合4、5使用
import { xxx, xxxx } from "xxx";
// 输出模块的变量(方法、类),对应引入方法为1
let xxx = "x";
export { xxx };
1
2
3
4
5

# export default

// 默认输出一个函数
export default function() {
  console.log("foo");
}
// 引用并指定名字
import customName from "./export-default";
1
2
3
4
5
6

# export 和 module.export 的区别

exports 是 module.exports 的引用

exports:

直接打印 exports 是个空对象,且不会报错

因为内部执行了 var module = new Module(); var exports = module.exports

# export default 和 export 的区别

  1. 在一个文件或模块中,export、import 可以有多个,export default 仅有一个

  2. 通过 export 方式导出,在导入时要加{ },export default 则不需要

共同点:export 与 export default 均可用于导出常量、函数、文件、模块


参考:require() 源码解读-阮一峰 (opens new window)

更新时间: 1/20/2022, 9:20:54 AM
前端工程化
前端监控

← 前端工程化 前端监控→

最近更新
01
前端权限管理
02-24
02
vue2指令
02-24
03
vue2 hook
02-24
更多文章>
Theme by Vdoing | Copyright © 2019-2022 放肆青春
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式