JS模块化
2022/1/25 23:34:23
本文主要是介绍JS模块化,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
一、什么是模块化
在js出现的时候,js一般只是用来实现一些简单的交互,后来js开始得到重视,用来实现越来越复杂的功能,而为了维护的方便,我们也把不同功能的js抽取出来当做一个js文件,但是当项目变的复杂的时候,一个html页面可能需要加载好多个js文件,而这个时候就会出现各种命名冲突,如果js也可以像java一样,把不同功能的文件放在不同的package中,需要引用某个函数或功能的时候,import下相关的包,这样可以很好的解决命名冲突等各种问题,但是js中没有模块的概念,又怎么实现模块化呢
模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块,但是模块开发需要遵循一定的规范,否则就都乱套了,因此,才有了后来大家熟悉的AMD规范,CMD规范
二、JS模块发展背景
1)幼年期(也就是无模块化)
1.开始需要在页面中增加一些不同的js:动画、表单、格式化
2.多种js文件被分在不同的文件中
3.不同的文件又被同一个模板引用(文件分离是最基础的模块化第一步)
1 2 3 4 | < script src = "jquery.js" ></ script > < script src = "main.js" ></ script > < script src = "dep1.js" ></ script > //…… |
4.出现的问题:污染全局作用域 => 不利于大型项目的开发以及多人团队的共建
2)成长期(模块化的雏形 - IIFE(语法侧的优化) )
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数
1.作用域的把控
1 2 3 4 5 6 7 8 9 10 11 | // 定义一个全局变量 let count = 0; // 代码块1 const increase = () => ++count; // 代码块2 const reset = () => { count = 0; } increase(); reset(); |
2.利用函数块级作用域
1 2 3 4 | (()=>{ let count = 0; //... }) |
3.仅定义了一个函数 如果立即执行,就在函数后加(),表示立即执行
1 2 3 4 | (() => { let count = 0; // …… }(); |
以上步骤,仅是初步实现了一个最最简单的模块.
下面,尝试去定义一个最简单的模块
1 2 3 4 5 6 7 8 9 10 11 12 | const iifeModule = (() =>{ let count = 0; return { increase: () => ++count; reset: () => { count = 0; } } })(); iifeModule.increase(); iifeModule.reset(); |
4.相关问题
1)有额外依赖时,如何优化IIFE相关代码?
答:依赖其他模块的IIFE
1 2 3 4 5 6 7 8 9 10 11 | const iifeModule = ((dependencyModule1, dependencyModule2) => { //dependencyModule即为其他模块 let count = 0; return { increase: () => ++count; reset: () => { count = 0; } } })(dependencyModule1, dependencyModule2); iifeModule.increase(); iifeModule.reset(); |
2)了解早期jquery的依赖处理以及模块加载方案吗?/ 了解传统IIFE是如何解决多方依赖的问题
答:IIFE加传参调配
实际上 JQuery框架其实应用了revealing(揭露)的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 | const iifeModule = ((dependencyModule1, dependencyModule2) => { let count = 0; const increase = () => ++count; const reset = () => { count = 0; } return { increase, reset } })(dependencyModule1, dependencyModule2); iifeModule.increase(); iifeModule.reset(); |
3)成熟期( Commonjs(CJS) )
由node.js制定
特征:1.通过 module + exports 去对外暴露接口
2.通过 require 来调用其他模块
1.模块组织方式:
1).main.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 引入部分 const dependencyModule1 = require(./dependencyModule1); const dependencyModule2 = require(./dependencyModule2); // 处理部分 let count = 0; const increase = () => ++count; const reset = () => { count = 0; } // 做一些跟引入依赖相关事宜…… // 暴露接口部分 exports.increase = increase; exports.reset = reset; module.exports = { increase, reset } |
2).模块使用方式( require 调用即可 )
1 2 3 4 | const { increase, reset } = require('./main.js'); increase(); reset(); |
2.CJS优缺点
- 优点:CommonJS率先在服务端实现了,从框架层面解决依赖、全局变量污染的问题
- 缺点:主要针对了服务端的解决方案。对于异步拉取依赖的处理整合不是那么的友好
那么在此产生了新的问题 ----- 异步依赖 ,就需要AMD来解决
三、AMD规范
方法:通过异步加载 和 允许制定回调函数
经典实现框架: require.js
1)新增定义方式
通过 define 来定义一个模块,然后 require 进行加载
1 2 3 | //params: 模块名,依赖模块,工厂方法 define(id, [depends], callback); require([module], callback); |
2)模块定义方式
1 2 3 4 5 6 7 8 9 10 11 12 13 | define('amdModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1, dependencyModule2) => { // 业务逻辑 // 处理部分 let count = 0; const increase = () => ++count; const reset = () => { count = 0; } return { increase, reset } }) |
3)引入模块
1 2 3 | require(['amdModule'], amdModule => { amdModule.increase(); }) |
4)相关问题
1.如果在AMDmodule中想兼容已有代码,如何做?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | define('amdModule', [], require => { // 引入部分 const dependencyModule1 = require(./dependencyModule1); const dependencyModule2 = require(./dependencyModule2); // 处理部分 let count = 0; const increase = () => ++count; const reset = () => { count = 0; } // 做一些跟引入依赖相关事宜…… return { increase, reset } }) |
2.AMD中使用revealing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | define('amdModule', [], (require, export, module) => { // 引入部分 const dependencyModule1 = require(./dependencyModule1); const dependencyModule2 = require(./dependencyModule2); // 处理部分 let count = 0; const increase = () => ++count; const reset = () => { count = 0; } // 做一些跟引入依赖相关事宜…… export.increase = increase(); export.reset = reset(); }) define('amdModule', [], require => { const otherModule = require('amdModule'); otherModule.increase(); otherModule.reset(); }) |
3.兼容AMD&CJS / 如何判断CJS和AMD UMD的出现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | (define('amdModule', [], (require, export, module) => { // 引入部分 const dependencyModule1 = require(./dependencyModule1); const dependencyModule2 = require(./dependencyModule2); // 处理部分 let count = 0; const increase = () => ++count; const reset = () => { count = 0; } // 做一些跟引入依赖相关事宜…… export.increase = increase(); export.reset = reset(); }))( // 目标是一次性区分CommonJSorAMD typeof module === "object" && module.exports && typeof define !== "function" ? // 是 CJS factory => module.exports = factory(require, exports, module) : // 是AMD define ) |
5)AMD优缺点
- 优点:适合在浏览器中加载异步模块,可以并行加载多个模块
- 缺点:会有引入成本,不能按需加载
四、CMD规范
按需加载
主要应用框架:sea.js
1 2 3 4 5 6 7 | define('module', (require, exports, module) => { let $ = require('jquery'); // jquery相关逻辑 let dependencyModule1 = require('./dependecyModule1'); // dependencyModule1相关逻辑 }) |
CMD优缺点:
- 优点:按需加载,依赖就近
- 缺点:依赖于打包,加载逻辑存在于每个模块中,扩大模块体积
相关问题: AMD & CMD 的区别
答:依赖就近,按需加载
五、ES6模块化
1)新增定义
引入关键字 ---- import
导出关键字 ---- export
2)模块引入、导出和定义的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 引入区域 import dependencyModule1 from './dependencyModule1.js'; import dependencyModule2 from './dependencyModule2.js'; // 实现代码逻辑 let count = 0; export const increase = () => ++count; export const reset = () => { count = 0; } // 导出区域 export default { increase, reset } |
3)模板引入的地方
1 | < script type = "module" src = "esModule.js" ></ script > |
4)在node中
1 2 3 4 5 6 7 | import { increase, reset } from './esModule.mjs'; increase(); reset(); import esModule from './esModule.mjs'; esModule.increase(); esModule.reset(); |
5)相关问题
ES6中的动态模块是哪些?
答: export 和 promise
6)ES11原生解决方案(包含了 promise)
1 2 3 | import('./esModule.js').then(dynamicEsModule => { dynamicEsModule.increase(); }) |
7)ES6模块化优缺点
优点(重要性):通过一种最统一的形态整合了js的模块化
缺点(局限性):本质上还是运行时的依赖分析
六、解决模块化的新思路 - 前端工程化
1)背景
根本问题 - 前端的模块化处理方案依赖于运行时分析
2)问题解决方案
线下执行 grunt gulp webpack......
1 2 3 4 5 6 7 8 9 10 11 12 | <!doctype html> < script src = "main.js" ></ script > < script > // 给构建工具一个标识位 require.config(__FRAME_CONFIG__); </ script > < script > require(['a', 'e'], () => { // 业务处理 }) </ script > </ html > |
1 2 3 4 5 6 7 8 | define('a', () => { let b = require('b'); let c = require('c'); export.run = () { // run } }) |
3)工程化实现(思路)
1.扫描依赖关系表
1 2 3 4 5 | {//a b e的全局依赖 a: ['b', 'c'], b: ['d'], e: [] } |
2.重新生成依赖数据模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!doctype html> < script src = "main.js" ></ script > < script > // 构建工具生成数据 require.config({ "deps": { a: ['b', 'c'], b: ['d'], e: [] } }) </ script > < script > require(['a', 'e'], () => { // 业务处理 }) </ script > </ html > |
3.执行工具,采用模块化方案解决模块化处理依赖
1 2 3 4 | define('a', ['b', 'c'], () => { // 执行代码 export.run = () => {} }) |
4)工程化实现优点
- 构建时生成配置,运行时执行
- 最终转化成执行处理依赖
- 可以拓展
七、最终的完全体
webpack(核心的工程化)+ mvvm框架组件化 + 设计模式
这篇关于JS模块化的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2025-01-04React 19 来了!新的编译器简直太棒了!
- 2025-01-032025年Node.js与PHP大比拼:挑选最适合的后端技术进行现代web开发
- 2025-01-03?? 用 Gemini API、Next.js 和 TailwindCSS 快速搭建 AI 推文生成项目 ??
- 2024-12-31Vue CLI多环境配置学习入门
- 2024-12-31Vue CLI学习入门:一步一步搭建你的第一个Vue项目
- 2024-12-31Vue3公共组件学习入门:从零开始搭建实用组件库
- 2024-12-31Vue3公共组件学习入门教程
- 2024-12-31Vue3学习入门:新手必读教程
- 2024-12-31Vue3学习入门:初学者必备指南
- 2024-12-30Vue CLI多环境配置教程:轻松入门指南