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


    本文从以时间为轴从以下几个方面进行总结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>
    
    • 1
    • 2
    • 3

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

    * 面试中的追问

    script标签的参数:async & defer

    <script src="jquery.js" async>script>
    
    • 1
    总结
    • 三种加载
    1. 普通加载:解析到立即阻塞,立刻下载执行当前script
    2. defer加载:解析到标签开始异步加载,在后台下载加载js,解析完成之后才会去加载执行js中的内容,不阻塞渲染
    3. async加载:(立即执行)解析到标签开始异步加载,下载完成后开始执行并阻塞渲染,执行完成后继续渲染

    image.png

    • 兼容性:> IE9

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

    • 出现的问题

    1. 污染全局作用域

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

    image.png

    作用域的把控
    let count = 0;
    const increase = () => ++count;
    const reset = () => {
        count = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    利用函数的块级作用域

    (() => {
        let count = 0;
        ...
    })
    //最基础的部分
    
    • 1
    • 2
    • 3
    • 4
    • 5
    实现一个最简单的模块
    const iifeModule = (() => {
        let count = 0;
        const increase = () => ++count;
        const reset = () => {
            count = 0;
        }
        console.log(count);
        increase();
    })();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 追问:独立模块本身的额外依赖如何优化

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

    const iifeModule = ((dependencyModule1,dependencyModule2) => {
        let count = 0;
        const increase = () => ++count;
        const reset = () => {
            count = 0;
        }
        console.log(count);
        increase();
        ...//可以处理依赖中的方法
    })(dependencyModule1,dependencyModule2)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    参考 前端进阶面试题详细解答

    面试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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

    • 追问:
    1. 继续模块化横向展开
    2. 转向框架:jquery|vue|react模块细节
    3. 转向设计模式

    成熟期

    image.png

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    exe

    const {increase, reset} = require(./main.js)
    
    • 1

    复合使用

    (function(this.value,exports,require,module){
        const dependencyModule1 = require('./dependencyModule1')
        const dependencyModule2 = require('./dependencyModule2')
    }).call(this.value,exports,require,module)
    
    • 1
    • 2
    • 3
    • 4
    追问:一些开源项目为何要把全局、指针以及框架本身作为参数
    (function(window,$,undefined){
        const _show = function(){
            $("#app").val("hi zhuawa")
        }
        window.webShow = _show;
    })(window,jQuery)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    阻断思路

    • 一. window

      1. 全局作用域转换为局部作用域,window是全局作用域,如果不转成局部作用域会有一个向上查找知道全局的过程,提升执行效率
      2. 编译时优化:编译后会变成(优化压缩成本,销毁)
      (function(c){})(window) // window会被优化成c
      //window在里面所有别的执行所有的变化都会随着执行完毕都会跟着c一起被销毁
      
      • 1
      • 2
    • 二. jquery

      1. 独立定制复写和挂载
      2. 防止全局串扰
    • 三. undefined
      防止改写:在执行内部这段代码的时候保证undefined是正确的,不会被改写,如在外部定义一个undefined =1
      undefined对jquery本身是一个很重要的一个存在

    优缺点
    • 优点:CommonJS率先在服务端实现了,从框架层面解决了依赖,全局变量未然的问题
    • 缺点: 针对服务端的解决方案,异步拉取,依赖处理不是很友好

    => 异步依赖的处理

    AMD

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

    新增定义方式:

    //define来定义模块
    define(id, [depends], callback)
    //require来进行加载
    reuqire([module],callback)
    
    • 1
    • 2
    • 3
    • 4

    模块定义的地方

    define('amdModule',[dependencyModule1,dependencyModule2],(dependencyModule1,dependencyModule2) => {
        //业务逻辑
        let count = 0;
        const increase = () => ++count;
        module.exports = {
            increase
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    引入的地方

    require(['amdModule'],amdModule => {
        amdModule.increase()
    })
    
    • 1
    • 2
    • 3

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

    define('amdModule',[],require => {
        const dependencyModule1 = require('./dependencyModule1')
        const dependencyModule2 = require('./dependencyModule2')
        //业务逻辑
        let count = 0;
        const increase = () => ++count;
        module.exports = {
            increase
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    面试题:手写兼容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
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    优缺点
    • 优点:适合在浏览器中加载异步模块的方案
    • 缺点:引入成本

    CMD

    按需加载
    主要应用框架:sea.js

    define('module',(require,exports,module) => {
        let $ = require('jquery')
        let dependencyModule1 = require('./dependencyModule1')
    })
    
    • 1
    • 2
    • 3
    • 4
    优缺点
    • 优点:按需加载,依赖就近
    • 缺点:依赖打包,加载逻辑存在于每个模块中,扩大了模块体积,同时功能上依赖编译

    ES6模块化

    新增定义:

    • 引入:import
    • 引出:export

    面试:

    1. 性能 - 按需加载
    // ES11原生解决方案
    import('./esMModule.js').then(dynamicModule => {
        dynamicModule.increase();
    })
    
    • 1
    • 2
    • 3
    • 4

    优点:
    通过一种统一各端的形态,整合了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')
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

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

  • 相关阅读:
    在 Docker 容器内集成 Crontab 定时任务
    信息系统项目管理师考试重点汇总,看完这篇再拿十分!
    微信小程序使用npm教程
    C语言面试必看-指针笔试题详解
    Eclipse2022创建SSM项目及问题解决
    2024年FPGA可以进吗
    Selenium—入门+案例
    Apipost forEach控制器怎么用
    2023年高教杯数学建模2023B题解析(仅从代码角度出发)
    java高级:动态代理
  • 原文地址:https://blog.csdn.net/loveX001/article/details/127896694