zoukankan      html  css  js  c++  java
  • 前端模块化那点事-个人总结

    模块化

    其实我理解的模块化就是封装代码,将一个功能的代码封装到一个模块,然后需要使用时就加载这个模块

    模块化开发的好处:

    • 避免变量污染,命名冲突

    • 提高代码复用率

    • 提高维护性

    • 依赖关系的管理

    原生模块化

    所谓的“原生模块化”,是指模块化规范没有诞生之前的所使用的一种代码封装方式

    1. 函数封装

      函数都是挂载在window对象上的,也叫全局函数封装(全局function),污染了全局空间

      function m1(){
        //...
      }
      function m2(){
        //...
      }
      
    2. namespace模式

      解决了全局空间污染问题,但是内部模块的数据全部暴露,可以直接修改数据

      let myModule = {
        data: 'www.baidu.com',
        foo() {
          console.log(`foo() ${this.data}`)
        },
        bar() {
          console.log(`bar() ${this.data}`)
        }
      }
      myModule.data = 'other data' //能直接修改模块内部的数据
      myModule.foo() // foo() other data
      
    3. IIFE函数匿名自调用(闭包)

      作用:数据是私有的,外部只能操作暴露的方法

      将暴露的模块挂载到window对象,引用js模块文件后可直接访问模块方法

      但是,如果存在模块依赖怎么办?

      // module.js文件
      (function(window) {
        let data = 'www.baidu.com'
        //操作数据的函数
        function foo() {
          //用于暴露有函数
          console.log(`foo() ${data}`)
        }
        function bar() {
          //用于暴露有函数
          console.log(`bar() ${data}`)
          otherFun() //内部调用
        }
        function otherFun() {
          //内部私有的函数
          console.log('otherFun()')
        }
        //暴露行为
        window.myModule = { foo, bar } //ES6写法
      })(window)
      
      // index.html文件
      <script type="text/javascript" src="module.js"></script>
      <script type="text/javascript">
          myModule.foo()
          myModule.bar()
          console.log(myModule.data) //undefined 不能访问模块内部数据
          myModule.data = 'xxxx' //不是修改的模块内部的data
          myModule.foo() //没有改变
      </script>
      
    4. IIFE模块增强:引入依赖(这就是现代模块实现的基石)

      如果模块有依赖,先调用依赖的模块在调用使用依赖的模块文件,通过(function(对应依赖名的形参){})(依赖名)

      // module.js文件
      (function(window, $) {
        let data = 'www.baidu.com'
        //操作数据的函数
        function foo() {
          //用于暴露有函数
          console.log(`foo() ${data}`)
          $('body').css('background', 'red')
        }
        function bar() {
          //用于暴露有函数
          console.log(`bar() ${data}`)
          otherFun() //内部调用
        }
        function otherFun() {
          //内部私有的函数
          console.log('otherFun()')
        }
        //暴露行为
        window.myModule = { foo, bar }
      })(window, jQuery)
      
       // index.html文件
        <!-- 引入的js必须有一定顺序 -->
        <script type="text/javascript" src="jquery-1.10.1.js"></script>
        <script type="text/javascript" src="module.js"></script>
        <script type="text/javascript">
          myModule.foo()
        </script>
      

    CommonJs

    commonJs标志着“JavaScript模块化编程”诞生了。

    node.js的模块系统,就是参照CommonJS规范实现的。

    因为CommonJs是同步加载模块,对于浏览器而言是不适用的。因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

    1. 通过module.exports暴露模块

      //module1.js
      
      // module.exports = value 暴露一个对象
      module.exports = {
          msg:'module1',
          foo(){
              console.log("foo()",this.msg);
          }
      }
      
    2. 通过require()导入模块

      let uniq=require('uniq')
      
      let module1 = require('./modules/module');
      module1.foo();
      

    AMD(Asynchronous Module Definition)

    异步模块,是为了解决同步加载问题。
    AMD对应的就是很有名的RequireJS。

    • requireJS主要解决两个问题

      多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
      js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长

    1. 定义暴露模块

      requireJS定义了一个函数 define,它是全局变量,用来定义模块

      define(id?, dependencies?, factory);

      id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
      dependencies:是一个当前模块依赖的模块名称数组
      factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值

      • 定义没有依赖的模块
      // md1.js
      define(function() {
          var name = 'Ink-模块2';
          function getMinNumber(arr) {
              return Math.min.apply(null, arr)
          }
          return { 'name': name, 'getMinNumber': getMinNumber }
      });
      
      • 定义有依赖的模块
      // md2.js
      define(['md1'], function(md1) {
          function getMinNumber(arr) {
              console.log('此时调用md1的name:' + md1.name);
              return Math.min.apply(null, arr)
          }
          return { 'getMinNumber': getMinNumber }
      });
      

      2.require模块requireJs文档

      require([dependencies], function(){});

      第一个参数是一个数组,表示所依赖的模块
      第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块
      require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

      // 1.requireJs模块配置
      requirejs.config({
          // baseUrl: 'js/lib',
          paths: {
              md1: './module/md1',
              md2: './module/md2',
              // 引用第三方模块
              jquery: './libs/jquery'
          }
      });
      // 2.引入模块
      requirejs(['md1', 'md2', 'jquery'], function(ModuleA, ModuleB, $) {
          "use strict";
          console.log('初始化模块A', ModuleA);
          let arr = [1, 432, 4, 56, 99];
          console.log('数组:' + arr + '
      取最大值:	' + ModuleA.getMaxNumber(arr));
          console.log('初始化模块B', ModuleB);
          console.log('数组:' + arr + '
      取最小值:	' + ModuleB.getMinNumber(arr));
          // 3.引用第三方模块(先在config中配置)
          $('body').css({ 'background': 'darkblue' })
      });
      
      1. HTML页面
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>AMD规范</title>
      </head>
      <body>
      <!-- data-main是主js模块入口,就是说requireJs加载处理后会加载data-main中的main.js -->
          <script src="./require.js" data-main='main.js'></script>
      </body>
      </html>
      

    CMD(Common Module Definition)

    即通用模块定义,对应SeaJS,是阿里玉伯团队首先提出的概念和设计。跟requireJS解决同样问题,只是运行机制不同:是通过按需加载的方式,而不是必须在模块开始就加载所有的依赖

    1. 模块定义

      define(id?, deps?, factory)
      因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id
      CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写
      factory有三个参数

      function(require, exports, module)

      require代表依赖其他的模块
      exports代表导出给别人的模块
      module代表一个模块

      define(function(require, exports, module) {
          'use strict';
          function getMaxNumber(arr) {
              return Math.max.apply(null, arr)
          }
          let name = 'm1';
          let version = 'v1.0';
          exports.name = name;
          exports.version = version;
          exports.getMaxNumber = getMaxN
        umber;
      });
      
    2. 通过seajs函数库引用模块seajs文档

      seajs.use('./module/m1', function(m1) {
          console.log(m1);
      })
      
    3. HTML页面

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>CMD规范</title>
      </head>
      <body>
          <script src="./libs/sea.js"></script>
          <script src="./main.js"></script>
      </body>
      </html>
      

    ES6模块

    使用export指令导出了模块对外提供的接口,下面我们就可以通过 import 命令来加载对应的这个模块了

    1. export模块

      export指令用于导出变量

      // info.js
      
      let author = 'Ink-kai';
      let version = 'v1.1';
      
      // 某些情况下一个模块中包含某个功能,我们并不希望给这个功能命名,而是让导入者可以自己来命名
      // default是export对象自带的一个默认对象
      export default {
          author,
          version
      }
      // export function可以直接通过模块名.getMinNum(arr)调用
      export function getMinNum(arr) {
          return Math.min.apply(null, arr)
      }
      
    2. import模块

      用于导入模块中的内容

      // main.js 主模块
      
      // *表示导出模块所有对象(export模块的内容) as 别名
      import * as md1 from './module/info.js'
      
      let arr = [11, 323, 4, 5];
      console.log('info模块:', md1);
      console.log(arr + '
      最小值:' + md1.getMinNum(arr));
      console.log('author:' + md1.author);
      console.log('info模块版本:' + md1.version);
      
    3. HTML页面

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>ES6模块</title>
      </head>
      <body>
          <!-- 类型需要设置为module -->
          <script type="module" src="./main.js"></script>
      </body>
      </html>
      

    AMD与CMD区别

    最明显的区别就是在模块定义时对依赖的处理不同

    AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
    CMD推崇就近依赖,只有在用到某个模块的时候再去require
    看到很多网站说AMD是异步加载,CMD是同步加载,肯定是不准确的,他们的都是异步加载模块。

    对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

    CMD 推崇依赖就近,AMD 推崇依赖前置

    通俗来说:
    AMD在加载完成定义(define)好的模块就会立即执行,所有执行完成后,遇到require才会执行主逻辑。(提前加载)
    CMD在加载完成定义(define)好的模块,仅仅是下载不执行,在遇到require才会执行对应的模块。(按需加载)
    AMD用户体验好,因为没有延迟,CMD性能好,因为只有用户需要的时候才执行。

    CMD为什么会出现,因为对node.js的书写者友好,因为符合写法习惯,就像为何vue会受人欢迎的一个道理。

    官方阐述SeaJS与RequireJS异同

    两者的主要区别如下:

    1. 定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。Sea.js 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 环境中。
    2. 遵循的规范不同。RequireJS 遵循 AMD(异步模块定义)规范,Sea.js 遵循 CMD (通用模块定义)规范。规范的不同,导致了两者 API 不同。Sea.js 更贴近 CommonJS Modules/1.1 和 Node Modules 规范。
    3. 推广理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。Sea.js 不强推,采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。
    4. 对开发调试的支持有差异。Sea.js 非常关注代码的开发调试,有 nocache、debug 等用于调试的插件。RequireJS 无这方面的明显支持。
    5. 插件机制不同。RequireJS 采取的是在源码中预留接口的形式,插件类型比较单一。Sea.js 采取的是通用事件机制,插件类型更丰富。
    如果把成才比作登天,自学便是成才的天梯。
  • 相关阅读:
    python之编写购物车(第二天)
    day6作业--游戏人生
    day7 socket网络编程基础
    day7 --socket网络编程基础
    Python类和人类
    day7 socket网络编程
    day7异常处理
    day7面向对象--反射
    day6面向对象--类的特殊成员方法
    使用BigDecimal来进行精确计算
  • 原文地址:https://www.cnblogs.com/Ink-kai/p/14408068.html
Copyright © 2011-2022 走看看