个人文档
  • AI编程Cursor
  • GPT使用笔记
  • npm常用库合集
  • 同步用
  • 小Demo们
  • 工具网站教程集合
  • HTML、CSS 工具方法集合
    • HTML 全局属性
    • css常用功能
    • font-face 字体|子集相关
    • iframe父子页面传值
    • input输入优化
    • loading状态
    • nodejs使用谷歌邮箱发邮件
    • 为 Dom 自定义事件监听
    • 初始html的head标签配置
    • 拼音输入中文汉字的事件监听
    • 文字颜色效果
    • 文档片段范围 Range
    • 移动端开发-rem
    • 等宽字体推荐
    • 网站SEO优化注意点
    • 邮件html模板
  • JS 工具方法集合
    • Axios 简单使用
    • Axios 简单封装
    • Gitbook的安装和使用
    • Github 登录开发
    • HTML转为纯文本
    • JS 中强大的操作符
    • cookie 操作
    • js 动态加载js资源
    • js 常用功能语句
    • js取代trycatch的方法封装
    • js接口下载二进制
    • script 标签的异步属性
    • 判断当前是移动端还是pc端
    • 刷新token队列管理
    • 前端多线程 Web Worker
    • 加密-AES对称加密
    • 加密-node进行rsa加密解密
    • 地区省市区三级联动的地址数据 + 功能
    • 复制插件
    • 开发时环境变量
    • 得到随机图片
    • 数字格式整理集合
    • 数学计算插件
    • 时间格式整理
    • 获取ip地址
    • 获取url传参
    • 进制转换和位运算符
    • 页面隐藏|激活|关闭的监听
  • JS 知识点研究
    • Babel 历史和原理
    • Babel 配置和使用
    • Function 的 apply、call、bind
    • HTTP浏览器缓存粗解
    • Source map 文件还原为源码
    • TS常用技巧
    • js 的加载和模块化
    • js 的新数据类型 Symbol
    • js的代理对象 proxy 和 defineProperty
    • js的原型链 prototype
    • vite 打包体积优化
    • webpack 可视化打包文件大小插件
    • webpack 基础使用配置
    • webpack 版本5的报错
    • yeoman 开发脚手架的工具
    • 同步异步和微任务宏任务
    • 移动端调试---谷歌工具+eruda+vconsole
    • 转换-Blob URL
    • 转换-FileReader
    • 转换-Js文件类型和转换
    • 转换-前端开发的URL的编码和解码
    • 转换-字符串和Base 64的转换
  • Node 和 Npm 相关
    • Node 开发环境配置
    • express + jwt 校验
    • node 常用方法
    • node后台服务器-PM2
    • node基本使用
    • npm 中依赖的版本问题
    • npm 功能使用
    • npm指令说明和其他对比
    • nvm版本管理+自动切换node版本
  • React 学习
    • React Hook
    • React 项目基础开发
    • React.memo 和 React.PureComponent
    • React懒加载进阶
    • useContext Hook
    • useEffect Hook
    • useMemo 和 useCallback - Hook
    • useRef Hook
    • useState Hook
    • 同步修改变量功能封装 useVal for react
    • 轻便的传值组件
  • Rust 语言相关
    • Rust 基本
    • Rust 基础学习
    • Rust 调用 Object-C 的API
    • Tauri 基本使用
    • Tauri 是什么
  • VUE 学习
    • Vue3 使用
    • Vue3使用hook
    • Vue开发小技术点
    • vue路由切换时的动画效果
    • 花式引入组件和资源-打包时拆包减少js体积
  • Web3相关
    • Web3.0开发上-准备和概念理解
    • Web3.0开发下-功能代码示例
    • 以太坊区块链和Web3.0
    • 开发智能合约
  • python
    • pyenv版本管理工具
    • python初始化
    • python基本概念
    • venv虚拟环境
  • 个人其他
    • Steam Deck的基本设置和插件
  • 其他编程相关
    • Git教程和常用命令
    • Java开发-JDK和Maven的安装和卸载
    • Jenkins安装和基本使用
    • Linux系统指令
    • Mac 使用2K屏幕开启缩放
    • Mac 使用VS code打开项目
    • Mac 安装 Homebrew
    • Mac 的终端 shell 与 zsh
    • Mac 软件和插件
    • MacBook使用建议
    • Mac升降级到指定版本的系统
    • Mac安装Zsh
    • Mac安装软件各种提示
    • Mac系统脚本语言 AppleScript 的使用
    • Mac终端代理工具
    • Markdown(md)文档开发-Typora
    • Mysql 的安装和使用
    • Nginx 安装和基础使用
    • Nginx 稍微高深的配置
    • Slate - Api 的文档开发工具
    • Sublime配置
    • Ubuntu的 apt-get 使用
    • VScode配置
    • Windows 软件和插件
    • curl 工具使用
    • github 网站访问优化
    • host 文件
    • inquirer 终端中和用户交互
    • uTools的插件开发教程
    • vim 文本编辑功能
    • 使用 Github Pages 免费部署网站
    • 压缩指令 zip 和 unzip
    • 油猴的安装和开发(Tampermonkey)
    • 阿里云简略使用
  • 微信开发
    • 微信小程序开发
    • 微信开发必读
    • 微信开发提前购买域名
    • 微信手机打开的页面中授权登录
    • 微信扫码登录
    • 微信服务号登录+推送服务提醒
    • 自定义分享卡片-node.js实现
  • 数据结构与算法
    • KMP算法
    • Wildcard字符串分析算法
    • 二叉树
    • 字典树
    • 时间复杂度浅析
    • 算法神器——动态规划
Powered by GitBook
On this page
  • js 的加载和模块化
  • 前言
  • node 的 CommonJS
  • 前端原生引入 js 方法
  • AMD 规范的 Requirejs
  • ES6 的模块化

Was this helpful?

  1. JS 知识点研究

js 的加载和模块化

title: js 的加载和模块化 id: 90f53a359207b9f02ff5b8edf7d9c047 tags: [] date: 2000/01/01 00:00:00 updated: 2023/03/04 19:29:12 isPublic: true --#|[分隔]|#--

js 的加载和模块化

本文主要内容为 js 的模块化功能,包含如下:

  • node 和 CommonJS 规范

    • CommonJS 官网

    • CommonJS wiki

  • RequireJS 和 AMD 规范

    • RequireJS 官方文档

    • RequireJS 中文版说明

    • AMD 中文版说明

    • AMD wiki

  • SeaJs 和 CMD 规范

  • ES6(ECMAScript 6) 的模块化


前言

最初的 js 定位于前端简单的用户交互,没有考虑过它成规模后的模块化。

直到 2009 年出现了 node.js 这种用于后台的 js,作为后台语言,逻辑和功能的复杂度飙升,就必须考虑模块化了。

node.js 开发之初就遵守了 CommonJS 规范。

CommonJS 是一个规范,实现它的语言有很多,每个语言也并不是实现它的全部功能。

再往后,客户端设备性能飙升,功能愈加复杂、用户交互要求愈来愈高,前端慢慢也足够复杂,也到了需要考虑模块化引入功能模块的程度。

虽然后台的 node.js 和前端的 JavaScript 是同一门语言,但毕竟运行环境不同,后台服务器下载同一个版本的 node.js 就能统一,前端却有五花八门的浏览器、不同的版本等等,还有不同用户的网速不同、不同的设备、用的是 wifi 还是流量等等,太多条件的考虑和限制。

关键是前端不支持 CommonJS,但是前端也想拥有模块化加载的能力。。。

于是 2010 年左右,出现了 RequireJS 这个 js 工具库,而这个工具库,就是基于 AMD(Asynchronous Module Definition) 这个模块化开发规范,如果 node 是 CommonJS 的一种实现,那 RequireJS 就是 AMD 的实现。

RequireJS 出现后,2011 年左右,国内也出现了一个 SeaJS 的工具库,它基于的是 CMD(Common Moudle Definition) 规范。

最后,随着 js 语言本身的发展,当 js 发展到 ES6 时,js 的本身规范,也出现了一种模块化开发规范,但浏览器支持性很差,如果想要使用,最好还是使用工具,比如 babel 转成 ES5 的语法。

以上就是 js 的模块化发展,除了 RequireJS 和 SeaJS,还出现过其他类似功能的模块化工具,但都没有这两个流行。

导入模块的时候,会有一个依赖链逻辑,详情请移步:npm 功能使用

node 的 CommonJS

以下说的 CommonJS,都只是 nodejs 中的 CommonJS。

定义和介绍

nodejs 中的 CommonJS 同步加载的,引入的模块是「单例模式」。

使用 module.exports 导出,使用 require 引入。

node 中每个 js 文件都是一个模块,会为每个 js 模块提供以下四个变量:

1. module:当前 js 文件这个模块的对象本身

打印 module 有以下主要字段

  • id 模块的识别符,通常是带有绝对路径的模块文件名。

  • filename 模块的文件名,带有绝对路径。

  • loaded 布尔值,表示模块是否已经完成加载。

  • parent 对象,表示调用该模块的模块。

  • children 数组,表示该模块要用到的其他模块。

  • exports 关键,表示模块对外输出的值,默认值为一个空对象,其他模块引入该模块,得到的就是这个字段的值。

  • path 字符串,一个路径

  • paths 数组

2. exports:

其实就是 module.exports,可以理解为,这个文件最上面有一行 let exports = module.exports;

3. require:

用于引入其他模块,用法都知道。

4. global:

node 的全局对象,因为每个文件都是一个闭包的模块,global 是全局唯一且任意文件均可修改访问的对象,类似于前端页面中的 window

一个字段一个字段的输出

// util_1.js
module.exports.a = 10
exports.b = 20

// index.js
let util_1 = require('./util_1')
console.log(util_1) // { a: 10, b: 20 }

直接输出一个对象

// util_1.js
// 注意下面不能写 exports = ...
// 因为 exports === module.exports
// 如果再给 exports 赋值,那 exports 就只相当于一个普通变量,和 module 无关了
module.exports = {
  a: 10,
  b: 20,
}

// index.js
let util_1 = require('./util_1')
console.log(util_1) // { a: 10, b: 20 }

前端原生引入 js 方法

HTML 原生支持的异步加载 js 文件,只能控制 HTML 中 script 标签引入 js 时的加载和执行,详情请移步:script 标签的异步属性。

使用标签加载全部

<script src="./js/util_1.js"></script>
<script src="./js/util_2.js"></script>
<script src="./js/util_3.js"></script>
<script src="./js/util_4.js"></script>
<script src="./js/index.js"></script>

使用 js 控制加载其他 js

var scriptDom = document.createElement('script')
scriptDom.src = './js/util.js'
scriptDom.onload = scriptDom.onerror = function() {
  // 加载成功或失败
  document.querySelector('body').removeChild(scriptDom) // 删除 dom
  // code
}
document.querySelector('body').appendChild(scriptDom)

AMD 规范的 Requirejs

官方文档:https://requirejs.org/

前端需要模块化引入,一个原因是上面所述原因,前端的功能的复杂度提升,需要组件化、模块化,还有一个原因,就是前端进行开发时,需要的一些插件,大部分体积小的工具库,可以一开始就引入,但有一些体积大的库,如果也应开始就引入,会导致首屏或某页的文件体积巨大,延长页面加载时间,影响用户体检。

一些并不需要一开始就引入的场景:

  • 不同环境判断后引入:使用不同浏览器时,根据浏览器兼容情况,引入不同的兼容性 polyfill,或者不同网络环境,需要功能降级。

  • 当使用某个功能时才引入:比如点击编辑按钮时,引入编辑器插件;点击切换图表时,引入需要的图表库。

  • 控制不重要的大文件延后引入:首页默认就需要展示一个体积巨大的图表,但可以先加载处页面整体框架,再添加局部 loading,开始引入图表库再展示。

同样,在开发 vue、react 等单页面应用时,虽然项目又很多页面,但使用率高的只有几个,使用率特别低的也有几个,用户在打开某页时,没有必要把所有页面资源都下载下来,这些框架有各自的方法进行资源的异步加载。

Requirejs 可以人为可控的引入 js 文件和使用 Requirejs 定义的模块。

AMD 规范

基础用法一:引入 + 使用

使用npm

npm install requirejs

CND地址

Requirejs 的 CDN 链接复制地址:点击打开进行复制

简单使用,直接引入 require.js,然后使用 require() 引入需要的其他文件。

语法:require(assetsArr, callback)

  • assetsArr:资源地址的字符串的数组,比如,require(['./index.js', '/js/min.js']) 引入需要的其他 js,数组中的几个 js 文件,哪个先下载完就先执行,无先后顺序。

  • callback:可选,assetsArr中的资源全部引入并执行后的回调函数,会把引入的资源,按顺序传入(如果资源没有暴露出 Requirejs 支持的模块,那就没有了)。

<script src="./require.js"></script>
<script>
  // 可以发现给 window 添加了至少 require 和 requirejs 两个字段
  console.log(
    require === window.require &&
    require === requirejs &&
    require === window.requirejs
  ) // true

  // util_1.js 和 util_2.js 并行下载,下载完后立即执行,都执行完成后,执行回荡函数
  require(['./util_1.js', '/js/util_2.js'], function(util_1, util_2) {
    console.log('require 加载文件完成')
    console.log(util_1, util_2) // 引入的两个资源对象
  })
</script>

如果没有依赖项,则回调方法的默认参数为(require, exports, module)

使用 data-main 属性

可以给 引入 require.js 的 script 标签,添加一个 data-main 属性,data-main 本身是一个 HTML 全局属性。

data-main 值需要是一个 js 文件的地址,他在这里的作用,是指定网页程序的主模块,也就是 require.js 文件加载完成后,再去下载并执行的 js 文件。

可以把 require 的使用,移到 dataMain.js 文件中了。


<!-- html 文件中 -->
<script data-main="./dataMain.js" src="./require.js"></script>
// 文件 dataMain.js 中
require(['./util_1.js', '/js/util_2.js'], function(util_1, util_2) {
  console.log('require 加载文件完成')
  console.log(util_1, util_2) // 引入的两个资源对象
})

使用 define 定义模块

使用 require.js 提供的全局 define 方法,可以定义自己的模块了,像上面的 callback 回调函数中的参数,终于可以派上用上了。

语法:define(id?, dependencies?, factory)

  • id:可选,字符串,声明的这个模块的模块名

  • dependencies:可选,此模块的依赖项,字符串数组,默认为["require", "exports", "module"]

  • factory:模块初始化要执行的函数或对象,如果为函数,它应该只被执行一次,返回值为模块输出的值;如果是对象,则此对象就是该模块的输出值

如果同一文件多次调用 define 方法暴露模块,则以第一次执行的结果为准

以下展示常用用法:

// util_1.js 中
define({
  a: 100,
  b: 200
})

// util_2.js 中
define(['util_min'], function(util_min) {
  return util_min
})

// util_min.js 中
define(function() {
  return 'Name is util_min'
})

// 其他文件夹中使用 require 引入该模块
require(['./util_1.js', './util_2.js'], function(util_1, util_2) {
  console.log(util_1) // {a: 100, b: 200}
  console.log(util_2) // 'Name is util_min'
})

使用 require.config 配置

require.config(options) 顾名思义,就是用来配置 require,入参为 options 为配置对象,常用的有以下几个配置:

  • baseUrl:路径,在 require.config 中,引入模块时的 base 路径。

  • waitSeconds:引入文件时的超时时间,单位为秒,默认 7 秒。

  • paths:对象,可以预先声明一些模块名和模块路径,之后引入需要的模块时,依赖数组中直接写模块名即可引入。

  • shim:对于一些符不符合 AMD 模块规范的 js,可以使用此字段进行处理,详情可至官网或网上学习了解。

注意:

这其实是本来就存在的情况,这里只是重申一下:原生 js 开发时,因为所有的 js 最终都会引入到 html 文件中执行,所以 js 中所写的相对位置,都是基于这个 html 相对的。

所以,如果 require.config 写在单独的 js 文件中,它里面需要写路径的比如 baseUrl、paths 等,如果使用相对位置,就需要注意一下了。

使用的代码示例:

require.config({
  baseUrl: "/js/",
  paths: { // 预先写好几个模块的路径,使用时才引入
    "module_1": "module_1", // 最终指向该文件 /js/module_1.js
    "module_2": "module_2", // 最终指向该文件 /js/module_2.js
  },
  waitSeconds: 10, // 超时时间设置为 10 秒
})

// 因为 /js/module_1.js 已经在 require.config 中定义,所以这里可以直接写 module_1,即可引入
require(['module_1'], function(module_1) {
  // code
})

// module_2 只定义了路径,使用最终没有使用,所以不会引入这个 js

ES6 的模块化

ES6 的模块功能主要由两个命令构成:

export:模块输出的对外接口,一个模块就是一个独立的文件,文件内部的所有变量外部都无法获取,如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。

import:用于引入其他模块提供的功能

导出模式 export

导出模块默认值

模块默认值只能有一个,所以这个导出写法一个文件只能写一次,也可以不写,那就没有模块默认值而已。

需要使用 export default 命令:

// 导出直接量或匿名方法
export default 100
export default function() {}

// 导出具名方法,此方法在此文件的其他位置可以调用
export default function a() {}

// 先声明,再导出
const a = 123
// or
const a = function() {}
// or
function a() {}
export default a

散装的变量导出

模块默认值导出,只能有一个,不够用,所以有了这个散装的导出写法。

两者的概念上的区别是:

  • 导出的模块默认值,一个文件只能有一个,因为只有一个,所以可以是匿名的、可以是直接量。

  • 散装导出,可以导出无限多个值,所以这些值每一个都需要有变量名,导入时需要指定导入。

写法上的区别是:

  • 导出模块默认值的写法是export default。

  • 而散装变量的导出是 export。

下面是散装变量的导出写法

// 声明的同时导出
export const hello = 'hello'
export function b() {}

// 先声明再导出
const a = 100
const b = function() {}
// 先声明再导出可以单个单个的导出
export { a }
export { b }
// or 一起导出
export { a, b }
// or 导出时给变量重命名
export { a, b as c }

导入模式 import

只执行,不引入值

// 只执行 js 文件或 css
import './util.js'
import './index.css'

引入模块的默认值

该写法只能引入使用 export default 导出的默认值,不能引入其他,如果未导出默认值,则为 undefined

// util.js 文件中
const a = 300
export default a

// index.js
import util from './util.js'
console.log(util) // 打印 300
// 因为 util.js 中的模块默认值只可能有一个,所以 index.js 中只需要一个形参来接受
// 变量名随便写,不用和 util.js 导出的变量名一致

引入模块散装输出的值

// 引入散装输入的值时,每一个值的变量名都是确定的,不能乱写
// 但仍然可以使用 as 给变量重命名
import { a, b, c as myC } from './util.js'

同时引入默认值和散装值

必须默认值在前,散装的解构取值在后

import util, { a, b, c as myC } from './util.js'

导入模块的全部内容

使用 * as 可以把模块默认值和散装值,全部放在后面的形参中,其中 util.default 放置的是默认输出值。

如果输出的散装值中有 default 这个字段,编译会报错

// util.js 文件中
export default 300
const a = 100
const b = 200
export { a, b }

// index.js
import * as utils from './util.js'
console.log(JSON.stringify(utils))
// 打印内容见下,可见除了模块默认值被放在了 default 字段中,其他散装值都直接放在了对象中:
{"a":100,"b":200,"default":300}

整合多个文件统一导出

有时,大量工具js放在一个文件夹中,但其他很多文件想使用这些工具时,想只引入一个文件,则可以使用一个文件把工具都引入,然后直接导出,充当一个中介:

// ./utils/util.js 文件中
export default 300
const a = 100
const b = 200
export { a, b }

// ./utils/index.js 中介文件中
export { default, a, b } from './util.js'
// 可以引入再导出其他文件的值

// ./index.js 可以只引入一个文件,下面是导入了模块的全部内容,也可以使用其他引入方式
import * as utils from './utils/index.js'
console.log(JSON.stringify(utils))
{"a":100,"b":200,"default":300}
PreviousTS常用技巧Nextjs 的新数据类型 Symbol

Last updated 3 months ago

Was this helpful?