• 浏览器模块化详解


    浏览器模块化详解

    ES6 ModuleES6中规定的模块体系。传统script标签的代码加载容易导致全局作用域污染。

    <script>
      const a = 1;
    script>
    <script>
      console.log(a)       // 1
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    像上面的demo,即使是不同的script,但是能互相访问。项目一大,维护起来越来越困难。

    Javascript社区做了很多努力,在现有的运行环境中,实现”模块”的效果。

    最原始的写法应该就是把不同的函数简单放在一起,当作一个模块。

    function module1() {
      // ...
    }
    
    function module2() {
      // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这种写法其实缺点很明显,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

    为了解决函数的缺点,把模块写成一个对象,所有模块成员都放入对象中。

    const module = {
      counter: 0,
      module1: () => {
        // ...
      },
      module2: () => {
        // ...
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这样在调用模块的时候直接调用对象的属性就可以了,但是这样又把所有的模块成员暴露了,使得内部的状态可以被外部改写。

    module.counter++;  // 外部修改couter值
    
    • 1

    使用立即执行函数就可以达到不暴露私有成员的目的了。

    const module = (function() {
      let counter = 0;
      const module1 = () => {
        // ...
      }
      const module2 = () => {
        // ...
      }
      return {
        module1,
        module2
      }
    })()
    
    module.counter // undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    主流模块

    es6以前,还没有提出一套官方的规范,从社区和框架推广程度而言,目前通行的javascript模块规范有两种:CommonJSAMD

    CommonJS

    主要用于node服务端。

    CommonJS中,暴露模块使用module.exportsexports

    CommonJS中,有一个全局性方法require(),用于加载模块。

    // 引入http模块
    const http = require('http');
    
    // 使用http模块中的方法
    http.createServer(/*...*/);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    AMD

    有了服务器端模块以后,很自然地,大家就想要客户端模块。而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行。

    但是CommonJS不适用于浏览器环境,如果要在浏览器中使用,如果想要使用对应的模块,需要提前加载。像上方的http模块,如果像使用对应的方法,就需要等到http加载完成后才能使用。如果加载时间很长,整个应用就会停在那里等。

    但是由于服务端所有的模块都存放在本地硬盘上,可以同步加载完成(硬盘读取时间很快),但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于”假死”状态。
    因此,浏览器端的模块,不能采用”同步加载”(synchronous),只能采用”异步加载”(asynchronous)。这就是AMD规范诞生的背景。

    AMDAsynchronous Module Definition的缩写,采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。

    模块必须采用特定的define()函数来定义。

    语法

    define(id?, dependencies?, factory)
    
    • 1
    • id:字符串,模块名称(可选)
    • dependencies: 是我们要载入的依赖模块(可选),使用相对路径。注意是数组格式
    • factory: 工厂方法,返回一个模块函数

    如果一个模块不依赖其他模块,那么可以直接定义在define()函数之中。

    define(function() {
      let counter = 0;
      const add = () => {
        console.log('add')
        return counter++;
      }
      return {
        add
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果这个模块还依赖其他模块,那么define()函数的第一个参数,必须是一个数组,指明该模块的依赖性。

    define(['Lib'], function(Lib){
     function foo(){
      Lib.doSomething();
     }
     return {
      foo : foo
     };
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用require()函数来加载上面定义的模块。

    AMD也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数。

    语法:

    require([module], callback);
    
    • 1
    • module: 数组,里面的成员就是要加载的模块。
    • callback: 加载成功之后的回调函数,这个函数接收一个参数,就是definefactory的返回值。
    require(['./test.js'], function(test) {
      test.add();
    })
    
    • 1
    • 2
    • 3

    test这个文件中定义的模块的加载跟require中的回调函数不是同步执行的,并不会出现假死的状态。

    所以很显然,AMD比较适合浏览器环境。

    目前,主要有两个Javascript库实现了AMD规范:require.jscurl.js

    如果想要使用requiredefine,需要提前引入对应的模块。
    像使用require.js,需要在页面中引入。
    去官网下载最新版本,直接放到页面进行加载

      <script src="js/require.js">script>
    
    • 1

    CMD

    CMD (Common Module Definition), 是seajs推崇的规范。与AMD不同的是,CMD是在需要模块的时候才使用require引入。

    define的使用跟AMD一样。

    require的调用时机不同。

    比如我们想在某个模块中导入其他模块

    define(function() {
      const xxx = require('xxx');
    })
    
    • 1
    • 2
    • 3

    AMD就需要提前在define中说明。当然在CMD也可以这样使用。

    CMDAMD区别

    AMDCMD最大的区别是对依赖模块的执行时机处理不同,而不是加载的时机或者方式不同,二者皆为异步加载模块。
    AMD依赖前置,js可以方便知道依赖模块是谁,立即加载;
    CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略。

    现阶段标准

    ES6标准发布后,module成为标准。就是我们熟悉的importexport语法。

    标准使用是以export指令导出接口,以import引入模块,但是在node服务端中,依然采用的是CommonJS规范,使用require引入模块,使用module.exports导出接口。

    export

    export语法声明用于导出函数、对象、指定文件(或模块)的原始值。

    注意:在node中使用的是exports,不要混淆了

    export有两种模块导出方式:命名式导出(名称导出)和默认导出(定义式导出),命名式导出每个模块可以多个,而默认导出每个模块仅一个。

    命名式导出

    通过export前缀关键词声明导出对象,导出对象可以是多个。这些导出对象用名称进行区分,称之为命名式导出。

    function module1() {
      // ...
    }
    
    // 导出一个定义的函数
    export {
      module1
    }
    
    // 导出一个遍历
    export const a = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    使用*from关键字来实现的模块的继承

    export * from 'module1';
    
    • 1

    使用as关键字对导出成员进行重命名

    export {a as b};
    
    • 1

    默认导出

    默认导出也被称做定义式导出。命名式导出可以导出多个值,但在在import引用时,也要使用相同的名称来引用相应的值。而默认导出每个导出只有一个单一值,这个输出可以是一个函数、类或其它类型的值,这样在模块import导入时也会很容易引用。

    // 可以导出一个函数
    export default function() {}; 
    
    // 也可以出一个类
    export default class(){}; 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    默认导出可以理解为另一种形式的命名导出,默认导出可以认为是使用了default名称的命名导出。

    const a = 1;
     
    export default a;
    export { a as default };
    
    • 1
    • 2
    • 3
    • 4

    import

    import语法声明用于从已导出的模块、脚本中导入函数、对象、指定文件(或模块)的原始值。

    import模块导入与export模块导出功能相对应,也存在两种模块导入方式:命名式导入和默认导入。

    命名式导入

    通过指定名称,就是将这些成员插入到当作用域中。导出时,可以导入单个成员或多个成员。

    function module1() {
      // ...
    }
    
    // 导出一个定义的函数
    export {
      module1
    }
    
    // 引入的函数名需要跟导出的一致
    import { module1 } from 'xxx';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过*符号,我们可以导入模块中的全部属性和方法。当导入模块全部导出内容时,就是将导出模块所有的导出绑定内容,插入到当前模块的作用域中。

    function module1() {
      // ...
    }
    const a = 1;
    
    export {
      module1,
      a
    }
    
    import * as module1 from 'xxx';
    // 使用
    module1.a;
    module1.module1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    导入模块对象时,也可以使用as对导入成员重命名,以方便在当前模块内使用:

    function module1() {
      // ...
    }
    const a = 1;
    
    export {
      module1,
      a
    }
    
    import { a as b } from 'xxx';
    console.log(b)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    默认导出

    const a = 1;
     
    export default a;
    
    import a from 'xxx';
    console.log(a)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在script标签中使用模块

    如果文件中使用importexport这些标准语法,需要在script标签中携带type="module"属性,不然会报错。

    <script src="xxx.js" type="module">script>
    
    • 1

    这样,不同的script之前就不能互相访问属性了。

    像使用vue2.x时候我们发现,index.htmlscript标签为什么没有type="module"属性呢?是因为底层使用webpack,对import语法进行了处理,之后并不存在import语法了。

  • 相关阅读:
    坚叔:让科幻片的概念变成产品丨编程挑战赛 x 嘉宾分享
    vue 树状结构数据渲染 (java 处理 list ->树状)
    什么是智能视频美颜SDK?
    I/O设备的概念和分类,I/O控制器
    Python3----------抽象(多态、封装、继承等)
    如何通过链路追踪进行定时任务诊断
    FPGA领域顶级学术会议
    异地监控如何实现远程访问?贝锐蒲公英无需公网IP即可实现
    基于C51的中断系统控制
    Oracle查询语句中做日期加减运算
  • 原文地址:https://blog.csdn.net/qq_42880714/article/details/126357573