js模块化:详解与面试
JS模块化
1. 不得不说的历史
背景
JS本身简单的页面设计:页面动画 + 表单提交
并无模块化 or 命名空间的概念
但是因为JS的模块化需求日益增长
幼年期: 无模块化
- 开始需要在页面中增加一些不同的js:动画、表单、格式化
- 多种js文件被分在不同的文件中
- 不同的文件又被同一个模板引用
<script src="jquery.js"></script><script src="main.js"></script><script src="dep1.js"></script>//……
认可:
文件分离是最基础的模块化第一步
问题出现:
- 污染全局作用域 => 不利于大型项目的开发以及多人团队的共建
成长期: 模块化的雏形 - IIFE(语法侧的优化)
作用域的把控
栗子:
// 定义一个全局变量let count = 0;// 代码块1const increase = () => ++count;// 代码块2const reset = () => {count = 0;}increase();reset();
利用函数块级作用域
(() => {let count = 0;// ……}
仅定义了一个函数,如果立即执行
(() => {let count = 0;// ……}();
初步实现了一个最最最最简单的模块
尝试去定义一个最简单的模块
const iifeModule = (() => {let count = 0;return {increase: () => ++count;reset: () => {count = 0;}}
})();iifeModule.increase();
iifeModule.reset();
面试官可能会追问:有额外依赖时,如何优化IIFE相关代码
优化1: 依赖其他模块的IIFE
const iifeModule = ((dependencyModule1, dependencyModule2) => {let count = 0;return {increase: () => ++count;reset: () => {count = 0;}}
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();
面试1:了解早期jquery的依赖处理以及模块加载方案吗?/ 了解传统IIFE是如何解决多方依赖的问题
答:IIFE加传参调配
实际上,jquery等框架其实应用了revealing的写法:
揭示模式
const iifeModule = ((dependencyModule1, dependencyModule2) => {let count = 0;const increase = () => ++count;const reset = () => {count = 0;}return {increase, reset}
})(dependencyModule1, dependencyModule2);
iifeModule.increase();
iifeModule.reset();
成熟期:
CJS - Commonjs
node.js制定
特征:
- 通过module + exports 去对外暴露接口
- 通过require来调用其他模块
模块组织方式
main.js 文件
// 引入部分
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
}
模块使用方式
const { increase, reset } = require('./main.js');increase();reset();
可能被问到的问题
实际执行处理
(function (thisValue, exports, require, module) {const dependencyModule1 = require(./dependencyModule1);const dependencyModule2 = require(./dependencyModule2);// 业务逻辑……}).call(thisValue, exports, require, module);
- 优点:
CommonJS率先在服务端实现了,从框架层面解决依赖、全局变量污染的问题
- 缺点:
主要针对了服务端的解决方案。对于异步拉取依赖的处理整合不是那么的友好。
新的问题 —— 异步依赖
AMD规范
通过异步加载 + 允许制定回调函数
经典实现框架是:require.js
新增定义方式:
// 通过define来定义一个模块,然后require进行加载/*defineparams: 模块名,依赖模块,工厂方法*/define(id, [depends], callback);require([module], callback);
模块定义方式
define('amdModule', ['dependencyModule1', 'dependencyModule2'], (dependencyModule1, dependencyModule2) => {// 业务逻辑// 处理部分let count = 0;const increase = () => ++count;const reset = () => {count = 0;}return {increase, reset}})
引入模块:
require(['amdModule'], amdModule => {amdModule.increase();})
面试题2: 如果在AMDmodule中想兼容已有代码,怎么办
define('amdModule', [], require => {// 引入部分const dependencyModule1 = require(./dependencyModule1);const dependencyModule2 = require(./dependencyModule2);// 处理部分let count = 0;const increase = () => ++count;const reset = () => {count = 0;}// 做一些跟引入依赖相关事宜……return {increase, reset}})
面试题3: AMD中使用revealing
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();})
面试题4:兼容AMD&CJS/如何判断CJS和AMD
UMD的出现
(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();}))(// 目标是一次性区分CommonJSorAMDtypeof module === "object"&& module.exports&& typeof define !== "function"? // 是 CJSfactory => module.exports = factory(require, exports, module): // 是AMDdefine)
- 优点: 适合在浏览器中加载异步模块,可以并行加载多个模块
- 缺点:会有引入成本,不能按需加载
CMD规范
按需加载
主要应用的框架 sea.js
define('module', (require, exports, module) => {let $ = require('jquery');// jquery相关逻辑let dependencyModule1 = require('./dependecyModule1');// dependencyModule1相关逻辑})
- 优点:按需加载,依赖就近
- 依赖于打包,加载逻辑存在于每个模块中,扩大模块体积
面试题5:AMD&CMD区别
答:依赖就近,按需加载
ES6模块化
走向新时代
新增定义:
引入关键字 —— import
导出关键字 —— export
模块引入、导出和定义的地方:
// 引入区域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}
模板引入的地方
<script type="module" src="esModule.js"></script>
node中:
import { increase, reset } from './esModule.mjs';increase();reset();import esModule from './esModule.mjs';esModule.increase();esModule.reset();
面试题6:动态模块
考察:export promise
ES11原生解决方案:
import('./esModule.js').then(dynamicEsModule => {dynamicEsModule.increase();})
- 优点(重要性):通过一种最统一的形态整合了js的模块化
- 缺点(局限性):本质上还是运行时的依赖分析
解决模块化的新思路 - 前端工程化
背景
根本问题 - 运行时进行依赖分析
前端的模块化处理方案依赖于运行时分析
解决方案:线下执行
grunt gulp webpack
<!doctype html><script src="main.js"></script><script>// 给构建工具一个标识位require.config(__FRAME_CONFIG__);</script><script>require(['a', 'e'], () => {// 业务处理})</script></html>
define('a', () => {let b = require('b');let c = require('c');export.run = () {// run}})
工程化实现
step1: 扫描依赖关系表:
{a: ['b', 'c'],b: ['d'],e: []}
step2: 重新生成依赖数据模板
<!doctype html><script src="main.js"></script><script>// 构建工具生成数据require.config({"deps": {a: ['b', 'c'],b: ['d'],e: []}})</script><script>require(['a', 'e'], () => {// 业务处理})</script></html>
step3: 执行工具,采用模块化方案解决模块化处理依赖
define('a', ['b', 'c'], () => {// 执行代码export.run = () => {}})
优点:
- 构建时生成配置,运行时执行
- 最终转化成执行处理依赖
- 可以拓展
最终: 完全体 webpack为核心的工程化 + mvvm框架组件化 + 设计模式
js模块化:详解与面试相关推荐
- 波比-JS模块化详解
1.什么是模块化 模块化是一种代码管理.组织和通信的模式 2.为什么要用模块化? 随着项目复杂度的提高,项目变得越来越难以维护,js模块化也油然而生,所有的模块都处于全局域下,容易造成命名冲突的问题, ...
- JAVA9模块化详解(一)——模块化的定义
JAVA9模块化详解(一)--模块化的定义 前言 java9已经出来有一段时间了,今天向大家介绍一下java9的一个重要特性--模块化.模块化系统的主要目的如下: 更可靠的配置,通过制定明确的类的依赖 ...
- 8 年经验面试官详解 Java 面试秘诀!
日前,全球知名 TIOBE 编程语言社区最新发布 11 月的编程语言排行榜,根据最新的榜单显示,相比上个月编程语言 Top 5 并没有太大的变化,其中 Java 依旧稳坐榜首,随后分别是 C.Pyth ...
- 李洪强iOS经典面试题156 - Runtime详解(面试必备)
李洪强iOS经典面试题156 - Runtime详解(面试必备) 一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C ...
- Three.js实例详解___旋转的精灵女孩(附完整代码和资源)(一)
Three.js实例详解___旋转的精灵女孩(附完整代码和资源)(一) 本文目录: 一.[旋转的精灵女孩]案例运行效果 二.Three.js简介 三.Three.js代码正常运行显示条件 (1)不载入 ...
- Python爬虫JS解密详解,学会直接破解80%的网站(一)!!!
文章目录 1.网页查看 2.有道翻译简单实现源码 3.JS解密(详解) 4.python实现JS解密后的完整代码 4.1.实现效果 5.JS解密后完整代码升级版 5.1.实现效果 CSDN独家福利降临 ...
- 使用Gin框架集成JWT,源码、详解、面试问题
使用Gin框架集成JWT,源码.详解.面试问题 一.什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519). ...
- 【Java网络编程与IO流】Http协议详解以及面试有关问题
HTTP协议详解以及面试有关题目 1 HTTP请求 一个HTTP请求报文由请求行.请求头部.空行和请求数据四个部分组成. 1.1 请求行 请求行中有请求方法字段.URL字段和HTTP协议版本3个字段组 ...
- Three.js实例详解___旋转的精灵女孩(附完整代码和资源)(三)
Three.js实例详解___旋转的精灵女孩(附完整代码和资源)(三) 本篇目录: 六.完整构建整个[旋转的精灵女孩]实例 (1).新建.启动webGL工程空间 (2).构建项目的目录层次结构 (2. ...
- Three.js实例详解___旋转的精灵女孩(附完整代码和资源)(二)
Three.js实例详解___旋转的精灵女孩(附完整代码和资源)(二) 本篇目录: 五.实例中所使用的代码语法详细解释 (1).构建一个三维空间场景 (2).选择一个透视投影相机作为观察点 (a).创 ...
最新文章
- OpenCV 错误:无法打开摄像头(打开摄像头卡机)
- MIT警示“深度学习过度依赖算力”,研究三年算法不如用10倍GPU
- ipad连接电脑_Ipad已经停用需要连接iTunes的一种解决方案,
- 从零入门 Serverless | 一文搞懂函数计算及其工作原理
- 语音识别中强制对齐_语音识别中的标注问题和嵌入式训练
- socket只能连接本地mysql_MySQL本地用IP登陆而非socket
- 如何在WinForm中发送HTTP请求
- 60-140-044-使用-DataSink-使用OutputTag进行Side Output(侧输出)
- JSON.Stringify
- 安全测试——SQL注入
- 内网穿透NPS使用教程
- R、冗余分析(RDA)、ggplot2、置信椭圆
- 华为牛人在华为工作十年的感悟!
- 大学生创业知识(转)
- 报错:Unfortunately you can‘t have non-Gradle Java modules and Android-Gradle modules in one project
- Cassandra之jdbc-cassandra使用笔记
- SQLServer 2000 服务不能启动的解决办法
- 三星宣布量产64层V-NAND闪存芯片: 传输速度达1Gbps
- 分享几个谷歌(Chrome 内核浏览器)浏览器扩展 crx 下载站
- Java中向上转型与向下转型
热门文章
- vue 自适应屏幕的宽高度
- linux docker 常用命令
- Codeforces Round #826 (Div. 3)(D. Masha and a Beautiful Tree)线段树解法
- 记录一次nodejs 从高得地图根据地名获取经纬度
- TEQC数据处理与质量分析
- Python金融量化
- 关于浮子流量计的小知识!
- 2、maven案例idexXML调模板maven运Tomcat报错org.apache.jasper.JasperException: Unable to compile class forJSP
- Office Word 护眼模式设置
- bing 高级搜索_如何使用Bing的高级搜索运算符:更好搜索的8条提示