前端模块化

1. 模块化是什么?

模块化是一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化如何影响其它部分就会变得显而易见,使得系统更容易维护。

2. 模块化的好处

模块化的优势有以下几点:
(1) 防止命名冲突
(2) 代码可复用
(3) 高可维护性

3. 模块化规范

  • 在早期,使用闭包实现模块化是常见的手段,通过函数作用域解决了命名冲突、污染全局作用域的问题

  • 现代模块化机制
    (1)CommonJS
    (2)AMD
    (3)CMD
    (4)ESM(es6模块化)

    以上四种现代模块化机制的模块加载是同步的还是异步的

    1. CommonJS是同步的
      commonjs设计出来就是为了node使用的,对node来说,模块存放在本地硬盘,同步加载,等待时间就是硬盘的读取时间,这个时间非常短;
    2. AMD、CMD是异步的
      模块存放在服务器上,需要通过网络请求,浏览器加载模块的时间取决于网络的好坏,可能要等很长时间,因此设计为异步的
    3. ESM可以看作是异步的
      设计es6模块的时候,并没有强行指定同步或异步。但是所有浏览器对<script type="module"></script>都会默认加defer,所以可以认为是异步的。

1. CommonJS【同步】

NodeJS、webpack 是以CommonJS规范的实现

  • 基础语法:

    1. 暴露模块:
      module.exports = ???
      exports.XXX = ???
      实质上暴露的是exports对象module.exports = exports = {}
    2. 引入模块:
      引入第三方模块:require(模块名)
      引入自定义模块:require(模块文件路径)
  • 实现

    1. 服务器端:node
    2. 浏览器端:Browserify(也称为js的打包工具)
1.服务器端模块化----node
  1. 下载安装node.js
  2. 创建项目结构
    |-modules
       |-module1.js
       |-module2.js
       |-module3.js
    |-app.js
    |-package.json
      {
        "name": "commonJS-node",
        "version": "1.0.0"
      }
    
  3. 下载第三方模块
  • npm install uniq --save
  1. 模块化编码
  • module1.js

    module.exports = {
      foo() {
         console.log('moudle1 foo()')
       }
     }
    
  • module2.js

    module.exports = function () {
      console.log('module2()')
    }
    
  • module3.js

     exports.foo = function () {
       console.log('module3 foo()')
     }
     
     exports.bar = function () {
       console.log('module3 bar()')
     }
    
  • app.js

     /**
      1. 定义暴露模块:
        module.exports = value;
        exports.xxx = value;
      2. 引入模块:
        var module = require(模块名或模块路径);
     */
    "use strict";
    //引用模块
    let module1 = require('./modules/module1')
    let module2 = require('./modules/module2')
    let module3 = require('./modules/module3')
    
    //使用模块
    module1.foo()
    module2()
    module3.foo()
    module3.bar()
    
  1. 通过node运行app.js
    • 命令: node app.js
    • 工具: 右键–>运行
2.浏览器端模块化----Browserify
  1. 创建项目结构

    |-js
      	|-dist //打包生成文件的目录
      	|-src //源码所在的目录
        		|-module1.js
        		|-module2.js
        		|-module3.js
        		|-app.js //应用主源文件
    |-index.html
    |-package.json
      {
        "name": "browserify-test",
        "version": "1.0.0"
      }
    
  2. 下载browserify

    • 全局: npm install browserify -g
    • 局部: npm install browserify --save
  3. 定义模块代码

  • module1.js

    module.exports = {
      foo() {
        console.log('moudle1 foo()')
      }
    }
    
  • module2.js

    module.exports = function () {
     console.log('module2()')
    }
    
  • module3.js

    exports.foo = function () {
      console.log('module3 foo()')
    }
    
    exports.bar = function () {
      console.log('module3 bar()')
    }
    
  • app.js (应用的主js)

    //引用模块
    let module1 = require('./module1')
    let module2 = require('./module2')
    let module3 = require('./module3')
    
    //使用模块
    module1.foo()
    module2()
    module3.foo()
    module3.bar()
    
  1. 打包处理js:

    browserify js/src/app.js(浏览器引入的js文件) -o js/dist/bundle.js(打包生成的js文件)

  2. 页面使用引入:
    <script type="text/javascript" src="js/dist/bundle.js"></script> 
    

2. AMD【异步】

专门位于浏览器端,模块加载是异步的,使用require.js

  • 基础语法:

    • 暴露模块: define([依赖模块名], function(){return 暴露的模块对象})
    • 引入模块: require([‘模块1’, ‘模块2’, ‘模块3’], function(m1, m2){//使用模块对象})
require.js使用
  1. 下载require.js, 并引入
  • 官网: http://www.requirejs.cn/
  • 将require.js导入项目: js/libs/require.js
  1. 创建项目结构
    |-js
      	|-libs
        		|-require.js
      	|-modules
        		|-alerter.js
        		|-dataService.js
      	|-main.js
    |-index.html
    
  2. 定义require.js的模块代码
  • dataService.js
     define(function () {
       let msg = 'atguigu.com'
     
       function getMsg() {
         return msg.toUpperCase()
       }
     
     	//向外暴露
       return {getMsg}
     })
    
  • alerter.js
    //依赖dataService.js
    //'dataService'与paths里的key对映
    //第三方jQuery必须是'jquery',不需要路径映射,在jQuery源码中封装过了。
     define(['dataService', 'jquery'], function (dataService, $) {
       let name = 'Tom2'
     
       function showMsg() {
         $('body').css('background', 'gray')
         alert(dataService.getMsg() + ', ' + name)
       }
     //向外暴漏
       return {showMsg}
     })
    
  1. 应用主(入口)js: main.js

     (function () {
      //配置
       requirejs.config({
         //基本路径
         baseUrl: "js/",
         //模块标识名与模块路径映射
         paths: {
           "alerter": "modules/alerter",
           "dataService": "modules/dataService",
         }
       })
       
       //引入使用模块
       //'alerter'与paths里的key对映
       requirejs( ['alerter'], function(alerter) {
         alerter.showMsg()
       })
     })()
    
  2. 页面使用模块:

      <script data-main="js/main" src="js/libs/require.js"></script>
    

3. CMD【异步】

略。。。

AMD和CMD的区别
对依赖模块的执行时机处理不同(注意不是加载的时机)
AMD依赖前置、提前执行,在定义模块的时候就声明其依赖的模块,方便知道依赖了哪些模块,然后马上加载 , 在加载完成后, 就会执行该模块;
CMD依赖就近、延迟执行,把模块变为字符串解析一遍, 找到依赖了哪些模块, 在加载模块完成后, 不立刻执行, 而是等到require后再执行

4. es6模块化【异步】

位于浏览器端的模块化

  • 基础语法:

    export命令用于规定暴露模块
    import命令用于引入模块
    < script type = “module”>
    //所有暴漏的数据都存放于m中
    import * as m from “xxx/xxx/xxx.js”
    < /script >

  • 创建目录结构

     |-js
     		|-module1.js
     		|-module2.js
     		|-module3.js
     		|-app.js //应用主源文件
     |-index.html
    
    //module1.js
    //分别暴露
    export let name = "张颜齐"
    export function lable(){
    	console.log("远赴人间惊鸿宴,一睹世间盛世颜");
    }
    
    //module2.js
    //统一暴露
    let name = "张颜齐"
    function lable(){
    	console.log("远赴人间惊鸿宴,一睹世间盛世颜");
    }
    export {
    name,lable
    }
    
    //module3.js
    //默认暴露
    export default{
    	name: "张颜齐",
    	lable(){
    		console.log("远赴人间惊鸿宴,一睹世间盛世颜");
    	}
    }
    
    //app.js
    //入口js
    //1.通用形式导入
    import * as m1 from "./module1.js";
    console.log(m1);
    m1.lable();
    import * as m2 from "./module2.js";
    console.log(m2);
    m2.lable();
    import * as m3 from "./module3.js";
    console.log(m3);
    m3.default.lable();
    
    //2. 解构复制形式
    //2.1 分别暴露和统一暴露
    import {name,lable} from "./module1.js";
    console.log(lable);
    //2.2 默认暴露,必须使用别名,不能使用default
    import {default as m4} from "./module3.js";
    console.log(m4);
    
    //3.简便形式,只针对默认暴漏
    import m5 from "./module3.js";
    console.log(m5);
    
    //app.html
    <script src = "./js/app.js" type = "module"/></script>
    

Commonjs与ESM比较

  • CommonJs是使用module.exports或exports导出,使用require导入;ESModule则是使用export导出,使用import导入。
  • Commonjs有缓存:require值会缓存,通过require引用文件时,会将文件执行一遍后,把结果进行缓存,后续require该路径,直接从内存获取,无需重新执行文件
  • Commonjs 模块导出是值的拷贝,而 ES module 是引用拷贝。Commonjs模块输出是值的拷贝,一但输出,模块内部变化后,无法影响之前的引用,而ESModule是引用拷贝。ESM会在底层维护一个modelRecord树结构,类似于一个模块的操作记录,会根据你的import入口,将全部依赖梳理出来,modelRecord里面存的全部是引用地址
  • CommonJs是运行时加载模块,ESModule是在静态编译期间确定模块的依赖。
  • CommonJs不会提升require,ESModule在编译期间会将所有import提升到顶部

按需加载的本质就是利用了promise
面试题:

  1. 谈谈你对闭包的理解
  2. commonJS 和 ESM 分别如何解决循环依赖的
    假设A引用B,B又引用A
    • commonJS :
      引用A模块的时候,首先执行A引用B之前的代码(A1),会在缓存中先缓存一份未完成的副本A1,遇到A引用B的时候,先执行B引用A之前的代码(B1),然后在B引用A时,从缓存中拿到已缓存的副本A,不会来回循环调用,然后接着走完B引用A后面的代码(B2),当完全执行完B模块之后,再走回A模块,去执行a引用b后面的代码(A2)
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • ESM:
      import命令具有提升效果,会提升到整个模块的头部,首先执行。
      处理顺序为:预处理 B -> 预处理 A -> 执行 B -> 执行 A。
      esm会给每个模块打一个tag,标识这个模块的状态
      在这里插入图片描述
      在这里插入图片描述

参考资料:
前端模块化:CommonJS,AMD,CMD,ES6

deep into esm

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值