写点什么

JS 模块化—CJS&AMD&CMD&ES6- 前端面试知识点查漏补缺

作者:loveX001
  • 2022 年 10 月 10 日
    浙江
  • 本文字数:3084 字

    阅读完需:约 10 分钟

本文从以时间为轴从以下几个方面进行总结 JS 模块化。从无模块化 => IIFE => CJS => AMD => CMD => ES6 => webpack 这几个阶段进行分析。

历史

幼年期:无模块化

方式

  1. 需要在页面中加载不同的 js,用于动画,组件,格式化

  2. 多种 js 文件被分在了不同的文件中

  3. 不同的文件被同一个模板所引用


<script src="jquery.js"></script><script src="main.js"></script><script src="dep1.js"></script>
复制代码


此处写法文件拆分是最基础的模块化(第一步)

* 面试中的追问

script 标签的参数:async & defer


<script src="jquery.js" async></script>
复制代码

总结

  • 三种加载


  1. 普通加载:解析到立即阻塞,立刻下载执行当前 script

  2. defer 加载:解析到标签开始异步加载,在后台下载加载 js,解析完成之后才会去加载执行 js 中的内容,不阻塞渲染

  3. async 加载:(立即执行)解析到标签开始异步加载,下载完成后开始执行并阻塞渲染,执行完成后继续渲染



  • 兼容性:> IE9

  • 问题可能被引导到 => 1. 浏览器的渲染原理 2.同步异步原理 3.模块化加载原理

  • 出现的问题


  1. 污染全局作用域

成长期(模块化前夜) - IIFE(语法测的优化)

作用域的把控

let count = 0;const increase = () => ++count;const reset = () => {    count = 0;}
复制代码


利用函数的块级作用域


(() => {    let count = 0;    ...})//最基础的部分
复制代码

实现一个最简单的模块

const iifeModule = (() => {    let count = 0;    const increase = () => ++count;    const reset = () => {        count = 0;    }    console.log(count);    increase();})();
复制代码


  • 追问:独立模块本身的额外依赖如何优化


优化 1:依赖其他模块的传参型


const iifeModule = ((dependencyModule1,dependencyModule2) => {    let count = 0;    const increase = () => ++count;    const reset = () => {        count = 0;    }    console.log(count);    increase();    ...//可以处理依赖中的方法})(dependencyModule1,dependencyModule2)
复制代码

面试 1:了解 jquery 或者其他很多开源框架的模块加载方案

将本身的方法暴露出去


const iifeModule = ((dependencyModule1,dependencyModule2) => {    let count = 0;    const increase = () => ++count;    const reset = () => {        count = 0;    }    console.log(count);    increase();    ...//可以处理依赖中的方法    return         increase,reset    }})(dependencyModule1,dependencyModule2)iifeModule.increase()
复制代码


=> 揭示模式 revealing => 上层无需了解底层实现,仅关注抽象 => 框架


  • 追问:


  1. 继续模块化横向展开

  2. 转向框架:jquery|vue|react 模块细节

  3. 转向设计模式

成熟期

CJS (Commonjs)

node.js 指定


特征:


  1. 通过 module + exports 对外暴露接口

  2. 通过 require 去引入外部模块,参考 前端进阶面试题详细解答


main.js


const dependencyModule1 = require('./dependencyModule1')const dependencyModule2 = require('./dependencyModule2')
let count = 0;const increase = () => ++count;const reset = () => { count = 0;}console.log(count);increase();
exports.increase = increase;exports.reset = reset;
module.exports = { increase, reset}
复制代码


exe


const {increase, reset} = require(./main.js)
复制代码


复合使用


(function(this.value,exports,require,module){    const dependencyModule1 = require('./dependencyModule1')    const dependencyModule2 = require('./dependencyModule2')}).call(this.value,exports,require,module)
复制代码

追问:一些开源项目为何要把全局、指针以及框架本身作为参数

(function(window,$,undefined){    const _show = function(){        $("#app").val("hi zhuawa")    }    window.webShow = _show;})(window,jQuery)
复制代码


阻断思路


  • 一. window

  • 全局作用域转换为局部作用域,window 是全局作用域,如果不转成局部作用域会有一个向上查找知道全局的过程,提升执行效率

  • 编译时优化:编译后会变成(优化压缩成本,销毁)


  (function(c){})(window) // window会被优化成c  //window在里面所有别的执行所有的变化都会随着执行完毕都会跟着c一起被销毁
复制代码


  • 二. jquery

  • 独立定制复写和挂载

  • 防止全局串扰

  • 三. undefined 防止改写:在执行内部这段代码的时候保证 undefined 是正确的,不会被改写,如在外部定义一个 undefined =1undefined 对 jquery 本身是一个很重要的一个存在

优缺点

  • 优点:CommonJS 率先在服务端实现了,从框架层面解决了依赖,全局变量未然的问题

  • 缺点: 针对服务端的解决方案,异步拉取,依赖处理不是很友好


=> 异步依赖的处理

AMD

通过异步执行 + 允许指定回调函数经典实现框架:require.js


新增定义方式:


//define来定义模块define(id, [depends], callback)//require来进行加载reuqire([module],callback)
复制代码


模块定义的地方


define('amdModule',[dependencyModule1,dependencyModule2],(dependencyModule1,dependencyModule2) => {    //业务逻辑    let count = 0;    const increase = () => ++count;    module.exports = {        increase    }})
复制代码


引入的地方


require(['amdModule'],amdModule => {    amdModule.increase()})
复制代码


面试题:如果在 AMDModule 中想兼容已有代码,怎么办?


define('amdModule',[],require => {    const dependencyModule1 = require('./dependencyModule1')    const dependencyModule2 = require('./dependencyModule2')    //业务逻辑    let count = 0;    const increase = () => ++count;    module.exports = {        increase    }})
复制代码


面试题:手写兼容 CJS&AMD


//判断的关键:    1. object还是function    2. exports ?    3. define
(define('AMDModule'),[],(require,export,module) => { const dependencyModule1 = require('./dependencyModule1') const dependencyModule2 = require('./dependencyModule2')
let count = 0; const increase = () => ++count; const reset = () => { count = 0; } console.log(count); export.increase = increase();})( //目标:一次性区分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')    let dependencyModule1 = require('./dependencyModule1')})
复制代码

优缺点

  • 优点:按需加载,依赖就近

  • 缺点:依赖打包,加载逻辑存在于每个模块中,扩大了模块体积,同时功能上依赖编译

ES6 模块化

新增定义:


  • 引入:import

  • 引出:export


面试:


  1. 性能 - 按需加载


// ES11原生解决方案import('./esMModule.js').then(dynamicModule => {    dynamicModule.increase();})
复制代码


优点:通过一种统一各端的形态,整合了 js 模块化的方案缺点:本质上还是运行时分析

解决模块化新思路 - 前端工程化

遗留

根本问题:运行时进行依赖分析解决方案:线下执行

编译时依赖处理思路

<script src="main.js"></script><script>  // 给构建工具一个标识位  require.config(__FRAME_CONFIG__);</script><script>  require(['a', 'e'], () => {    // 业务逻辑  })</script>
define('a', () => { let b = require('b') let c = require('c')})
复制代码


完全体:webpack 为核心的前端工程化 + mvvm 框架的组件化 + 设计模式

用户头像

loveX001

关注

还未添加个人签名 2022.09.01 加入

还未添加个人简介

评论

发布
暂无评论
JS模块化—CJS&AMD&CMD&ES6-前端面试知识点查漏补缺_JavaScript_loveX001_InfoQ写作社区