一、不得不说的历史
JS本身是为了满足简单的页面设计 ==> 页面动画 + 表单提交
无模块化 or 命名空间的概念
JS的模块化需求日益增长
幼年期: 无模块化
- 1.开始需要在页面中加载不同的JS: 动画、组件、格式化
- 2.多钟js 文件会被分在不同的文件中
- 3.不同文件又被同一个模板所引用
<script src="jquery.js">
<script src="main.js">
<script src="lodash.js">
1. 知识点 - async & defer 的区别
普通js 是按照书写顺序从上往下执行,defer和async 标签可以使异步加载js,defer是“DOM渲染完再执行”,async是“下载完就执行”。
总结: 一般不依赖dom的js 可以用async,而有依赖或被依赖的,用defer
具体执行如下图

问题可以被引导到
- 1.浏览器渲染原理
- 2.同步异步原理
- 3.模块化加载原理
- 4.污染全局作用域 => 不利于大型项目开发以及多人团队的共建
二、成长期: 模块化前夜 - ITFE(语法侧优化)
作用域的把控
为什么会污染
// 同一文件别的js中也有定义a 此时就会相互影响,看谁先执行了
<script> const a = 0; </script>
<script> let a = 1; </script >
利用函数的块级作用域 - 隔离区
例子
(() => {
let a = 0;
// ...
})();
尝试定义一个最简单的模块
const lifeModule = (()=>{
let a = 0;
// ...
})()
- 面试追问: 独立模块本身的额外依赖,如何优化 ===> 作为参数传入
优化1: 依赖其他模块的传参型
const lifeModule = ((module1, module2)=>{
let a = 0;
// ...
})(module1, module2)
- 面试追问: jquery 或者其它很多开源的模块加载方案 ==> 传参形式拿依赖,把自身透传出去一个$ 让外面的去调用
揭示模式 revealing => 上层无需了解底层实现,仅关注抽象 => 框架
- 面试追问:
- 1.继续模块化横向展开
- 2.转向框架 jquery | vue | react 模块化细节
比如vue 问题: computed,methods 等都是对象,而data是一个函数呢??
答: 每个模块每个组件都是vue的实例,相互独立,而 data 是要做合并的,合并过程中要防止相互引用,变量污染,串扰~
三、成熟期:
CJS - Commonjs
node.js
特征:
- 通过module + exports 去对外暴露接口
- 通过require去引入外部模块
const module1 = require('./module1.js')
const module2 = require('./module2.js')
let a = 0;
const add=()=>{a++};
// 一种暴露出去
exports. add = add
// 第二种暴露出去
module.exports ={
add
}
// exe
const { add } = require('./main.js')
-
追问 require 和 import 的区别?
答: 简单讲,require 是运行时动态加载,而import 是静态编译,require 得到的是一个值的拷贝,而import 得到的是一个值的只读引用,所以引用模块值改变,require 方式不会改变,而 import 方式值会改变
具体参考 require 和 import 的区别 -
为什么一些开源项目为何要把全局,指针以及框架本身作为参数
举例:
(funtion(window,$, undefined ){
const _show = funtion(){
$('app').val('你好')
}
window.webShow = _show
})(window, jQuery)
答: 阻断思路
- 比如 window 全局作用域转化为局部作用域
- 编译时优化 (funtion(c){})(window) // window 被优化成c,便于压缩,及时处理释放
- 对于jquery 独立定制复写和挂载 防止全局串扰
- 上述的undefined - 防止重写 比如jquery 为了确保undefined是真的undefined,比如在外头 undefined = 1 会影响
commonjs 优点
- 率先在服务端实现了,框架层面解决了依赖、全局变量污染的问题
- 缺点
针对了服务端的解决方案,异步依赖处理不是很完美
新的问题 ---- 异步依赖
AMD 规范
通过异步加载 + 允许定制回调函数 ===> 在回调函数里去处理逻辑,可以确保加载完模块后进行,让异步模块也能顺利进行
经典实现框架: require.js
新增定义方式:
// define 来定义模块
define(id, [depends], callback)
// require 来加载
require([module], callback)
模块定义地方:
define('mdaModule', [module1, module2], (module1, module2)
=>{
// 业务逻辑
})
引入的地方
require(['amdModule', amdModule=>{
amdModule.increase();
}])
- 追问: 如果想在amdModule中兼容之前的CJS已有代码
define('mdaModule', [], require
=>{
const module1 = require('./module1')
const module2 = require('./module1')
// 业务逻辑
})
- 面试题 请手写兼容CJS 和 AMD ??
// 判断关键object 还是function 2. exports? 还是 3. define
(define('amdModule'), [], require(require, export, module) =>{
const module1 = require('./module1')
const module2 = require('./module1')
// 业务逻辑
let count = 0
const add = ()=>{count++}
export.add = add
})(
// 目标: 一次性区分CJS 还是AMD
typeof module === 'object' && module.exports && typeof define !== 'function' ?
// 是CJS
factory => module.exports = factory(require, exports, module)
:
// AMD
define)
优点: 适合浏览器中异步加载模块的方案
缺点: 引入成本
CMD
按需加载: sea.js
define('module', (require, exports, module) =>{
let $ = require('jquery');
// jquery
let module1 = require('./module1')
// module1相关逻辑
})
优点 按需加载,依赖就近
缺点是依赖打包,加载逻辑在于每个模块中,扩大了模块体积,同时功能上依赖编译
四、ES6模块化
走向新时代
新增定义:
引入: import
暴露: export
模块在引入和导出,定义的地方:
import module1 from './module1'
import module2 from './module2'
const aa = () => {
// 业务逻辑
}
// 导出
export const a = () => {}
export default {
aa
}
- 追问
- 性能 - 按需加载 import 分chunk 按需加载打包 require.esure 区别?
答
- 性能 - 按需加载 import 分chunk 按需加载打包 require.esure 区别?
- 动态模块
ES11 原生解决方案
- 动态模块
import('./esModule.js').then(module1=>{
module1.add()
})
优点: 通过一种最终统一各端的形态,整合了js模块化的通用方案
局限性: 本质上还是一种运行时的依赖分析
五、解决模块化新思路 - 前端工程化
遗留
根本问题: 运行时进行依赖分析
解决方案: 线下执行
- 面试题: 可否简述,实现一个编译时依赖处理的思路??
<! doctype html>
<script src="main.js"> </script>
<script>
// 给构建工具一个标识位
require.config(_FRAME_CONFIG_)
</script>
<script>
require(['a', 'b'], ()=>{
})
</script>
<html>
define('a', ()=>{
let b = require('b')
let c = require('c')
})
工程化实现
step1: 扫描依赖关系
{
a: ['b', 'c']
}
step2: 根据依赖关系重置模板
<! doctype html>
<script src="main.js"> </script>
<script>
// 给构建工具一个标识位
require.config({
'deps':{
'a': ['b','c'],
'b': ['d'],
}
})
</script>
<script>
require(['a', 'b'], ()=>{
})
</script>
<html>
step3: 执行工具,采用模块化解决方案处理
define('a', ['b', 'c'], ()=>{
export.run=()=>{}
})
优点
- 构建时生成配置,运行时去运行
- 最终转化成为可执行的依赖处理
- 可以拓展
简述: 首先是埋点标识位,然后根据我们需要做文件扫描,得到依赖关系,生成一个配置图,去替换埋点,因为在srcipt 中会被执行,执行时用我们预设的工具去执行即可
六、完全体 webpack 为核心的前端工程化 + mvvm框架的组件化 + 设计模式
这个我们都熟,就不再赘述~
网友评论