Web前端学习笔记——模块化开发

目录

概述

什么是模块化开发

非模块化开发的问题

为什么使用模块化开发

实现模块化的推演

step-01 全局函数

step-02 封装对象

step-03 划分私有空间

step-04 模块的扩展与维护

step-05 第三方依赖管理

实现规范

CommonJS规范

AMD规范

CMD规范

•服务器端规范

•浏览器端规范

CMD实现-SeaJS

AMD实现-RequireJS

SeaJS和RequireJS对比

实现Seajs

使用步骤

定义一个模块

使用一个模块

导出成员的方式

异步加载模块

使用第三方依赖(jQuery)

Seajs配置

使用案例

RequireJS


概述

什么是模块化开发

  • 将软件产品看作为一系列功能模块的组合
  • 通过特定的方式实现软件所需模块的划分、管理、加载

非模块化开发的问题

命名冲突

添加命名空间YUI 、EXTJS---可以从一定程度上解决命名冲突,但是增加了开发人员记忆冗长api的难度。

文件依赖

团队变大后,维护大量的文件依赖关系非常困难,公共模块的维护、升级很不方便。

为什么使用模块化开发

  • https://github.com/seajs/seajs/issues/547
  • 协同
  • 代码复用
  • 解决问题
    • 大量的文件引入
    • 命名冲突
    • 文件依赖
    • 存在
    • 顺序

实现模块化的推演

step-01 全局函数

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>全局函数的形式</title>
  <script>
    // calc.js
    // 计算模块
    // 成员一:加法运算
    function add(a, b) {
      return parseFloat(a) + parseFloat(b);
    }

    function subtract(a, b) {
      return parseFloat(a) - parseFloat(b);
    }

    function multiply(a, b) {
      return parseFloat(a) * parseFloat(b);
    }

    function divide(a, b) {
      return parseFloat(a) / parseFloat(b);
    }
    // 早期的开发过程中就是将重复使用的代码封装到函数中
    // 再将一系列的函数放到一个文件中,称之为模块
    // 约定的形式定义的模块,存在命名冲突,可维护性也不高的问题
    // 仅仅从代码角度来说:没有任何模块的概念
    //  ==========================

    window.onload = function() {
      var ta = document.getElementById('txt_a');
      var tb = document.getElementById('txt_b');
      var tres = document.getElementById('txt_res');
      var btn = document.getElementById('btn');
      var op = document.getElementById('sel_op');

      btn.onclick = function() {
        switch (op.value) {
          case '+':
            tres.value = add(ta.value, tb.value);
            break;
          case '-':
            tres.value = subtract(ta.value, tb.value);
            break;
          case 'x':
            tres.value = multiply(ta.value, tb.value);
            break;
          case '÷':
            tres.value = divide(ta.value, tb.value);
            break;
        }
      };
    };
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
  <!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>

</html>

step-02 封装对象

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>封装函数的方式</title>
  <script>
    // calc.js
    // 计算模块
    // var calculator = {
    //   // 成员一:加法运算
    //   add: function(a, b) {
    //     return parseFloat(a) + parseFloat(b);
    //   },
    //   subtract: function(a, b) {
    //     return parseFloat(a) - parseFloat(b);
    //   },
    //   multiply: function(a, b) {
    //     return parseFloat(a) * parseFloat(b);
    //   },
    //   divide: function(a, b) {
    //     return parseFloat(a) / parseFloat(b);
    //   }
    // };
    //
    var math = {};
    math.calculator = {};
    math.calculator.add = function(a, b) {
      return a + b;
    };
    math.convertor = {};
    // math.calculator.add();
    // 传统编程语言中的命名空间的概念
    // 从代码层面就已经有了模块的感觉
    // 还是污染全局(不是问题)
    // 高内聚,低耦合
    // 模块内部相关性强,模块之间没有过多相互牵连
    // 没有私有空间,
    //

    //  ==========================

    window.onload = function() {
      var ta = document.getElementById('txt_a');
      var tb = document.getElementById('txt_b');
      var tres = document.getElementById('txt_res');
      var btn = document.getElementById('btn');
      var op = document.getElementById('sel_op');

      btn.onclick = function() {
        switch (op.value) {
          case '+':
            tres.value = calculator.add(ta.value, tb.value);
            break;
          case '-':
            tres.value = calculator.subtract(ta.value, tb.value);
            break;
          case 'x':
            tres.value = calculator.multiply(ta.value, tb.value);
            break;
          case '÷':
            tres.value = calculator.divide(ta.value, tb.value);
            break;
        }
      };
    };
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
  <!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>

</html>

step-03 划分私有空间

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>封装函数的方式</title>
  <script>
    // calc.js
    // 计算模块
    var calculator = (function() {
      // 这里形成一个单独的私有的空间
      // var name = '';

      // 私有成员的作用,
      // 将一个成员私有化,
      // 抽象公共方法(其他成员中都会用到的)

      // 私有的转换逻辑
      function convert(input){
        return parseInt(input);
      }

      function add(a, b) {
        return convert(a) + convert(b);
      }

      function subtract(a, b) {
        return convert(a) - convert(b);
      }

      function multiply(a, b) {
        return convert(a) * convert(b);
      }

      function divide(a, b) {
        return convert(a) / convert(b);
      }

      return {
        add: add,
        subtract: subtract,
        multiply: multiply,
        divide: divide
      }
    })();

    //  ==========================

    window.onload = function() {
      var ta = document.getElementById('txt_a');
      var tb = document.getElementById('txt_b');
      var tres = document.getElementById('txt_res');
      var btn = document.getElementById('btn');
      var op = document.getElementById('sel_op');

      btn.onclick = function() {
        switch (op.value) {
          case '+':
            tres.value = calculator.add(ta.value, tb.value);
            break;
          case '-':
            tres.value = calculator.subtract(ta.value, tb.value);
            break;
          case 'x':
            tres.value = calculator.multiply(ta.value, tb.value);
            break;
          case '÷':
            tres.value = calculator.divide(ta.value, tb.value);
            break;
        }
      };
    };
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
  <!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>

</html>

step-04 模块的扩展与维护

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>封装函数的方式</title>
  <script>
    // calc_v2015.js
    // 计算模块
    (function(calculator) {

      function convert(input) {
        return parseInt(input);
      }

      calculator.add = function(a, b) {
        return convert(a) + convert(b);
      }

      window.calculator = calculator;

    })(window.calculator || {});

    // 新增需求
    // calc_v2016.js
    (function(calculator) {
      function convert(input) {
        return parseInt(input);
      }
      // calculator 如果存在的话,我就是扩展,不存在我就是新加
      calculator.remain = function(a, b) {
        return convert(a) % convert(b);
      }
      window.calculator = calculator;
    })(window.calculator || {});
    // 开闭原则,对新增开放,对修改关闭;
    //
    // http://api.douban.com/v2/movie
    // http://api.douban.com/movie


    //  ==========================

    window.onload = function() {
      var ta = document.getElementById('txt_a');
      var tb = document.getElementById('txt_b');
      var tres = document.getElementById('txt_res');
      var btn = document.getElementById('btn');
      var op = document.getElementById('sel_op');

      btn.onclick = function() {
        switch (op.value) {
          case '+':
            tres.value = calculator.add(ta.value, tb.value);
            break;
          case '-':
            tres.value = calculator.subtract(ta.value, tb.value);
            break;
          case 'x':
            tres.value = calculator.multiply(ta.value, tb.value);
            break;
          case '÷':
            tres.value = calculator.divide(ta.value, tb.value);
            break;
          case '%':
            tres.value = calculator.remain(ta.value, tb.value);
            break;
        }
      };
    };
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
    <option value="%">%</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
  <!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>

</html>

step-05 第三方依赖管理

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>封装函数的方式</title>
  <script src="jquery.js"></script>
  <script>
    // calc_v2015.js
    // 计算模块
    (function(calculator) {
      // 对全局产生依赖,不能这样用
      console.log($);
      function convert(input) {
        return parseInt(input);
      }
      calculator.add = function(a, b) {
        return convert(a) + convert(b);
      }

      window.calculator = calculator;

    })(window.calculator || {});

    // 新增需求
    // calc_v2016.js
    (function(calculator, $) {
      // 依赖函数的参数,是属于模块内部
      // console.log($);
      // $().
      function convert(input) {
        return parseInt(input);
      }
      // calculator 如果存在的话,我就是扩展,不存在我就是新加
      calculator.remain = function(a, b) {
        return convert(a) % convert(b);
      }
      window.calculator = calculator;
    })(window.calculator || {}, jQuery);
    // 开闭原则,对新增开放,对修改关闭;
    //
    // http://api.douban.com/v2/movie
    // http://api.douban.com/movie


    //  ==========================

    window.onload = function() {
      var ta = document.getElementById('txt_a');
      var tb = document.getElementById('txt_b');
      var tres = document.getElementById('txt_res');
      var btn = document.getElementById('btn');
      var op = document.getElementById('sel_op');

      btn.onclick = function() {
        switch (op.value) {
          case '+':
            tres.value = calculator.add(ta.value, tb.value);
            break;
          case '-':
            tres.value = calculator.subtract(ta.value, tb.value);
            break;
          case 'x':
            tres.value = calculator.multiply(ta.value, tb.value);
            break;
          case '÷':
            tres.value = calculator.divide(ta.value, tb.value);
            break;
          case '%':
            tres.value = calculator.remain(ta.value, tb.value);
            break;
        }
      };
    };
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
    <option value="%">%</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
  <!-- 需要实现计算的功能,于是乎抽象了一个计算的模块 -->
</body>

</html>

在什么场景下使用模块化开发 业务复杂 重用逻辑非常多 扩展性要求较高


实现规范

CommonJS规范

AMD规范

CMD规范

•服务器端规范

CommonJS---nodejs

•浏览器端规范

AMD---RequireJS 国外相对流行

CMD---SeaJS 国内相对流行

CMD实现-SeaJS

 SeaJS---阿里巴巴前端架构师玉伯

             js文件的依赖管理、异步加载,方便前端的模块化开发。

             官方网站:http://seajs.org/

AMD实现-RequireJS

             RequireJS-James Burke AMD规范的创始人

            与SeaJS 基本实现类似的功能

            中文官网:http://www.requirejs.cn/

SeaJS和RequireJS对比

  • 对于依赖的模块,AMD是提前执行,CMD是延后执行
  • CMD推崇依赖就近,AMD推崇依赖前置
  • AMD的API默认是一个当多个用,CMD的API严格区分,推崇职责单一

实现Seajs

使用步骤

  1. 在页面中引入sea.js文件
  2. 定义一个主模块文件,比如:main.js
  3. 在主模块文件中通过define的方式定义一个模块,并导出公共成员
  4. 在页面的行内脚本中通过seajs.use('path',fn)的方式使用模块
  5. 回调函数的参数传过来的就是模块中导出的成员对象

定义一个模块

  • define

javascript define(function(require, exports, module) { exports.add = function(a, b) { return a + b; }; });

使用一个模块

  • seajs.use
    • 一般用于入口模块
    • 一般只会使用一次
  • require
    • 模块与模块之间
<!-- 01-helloworld.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Seajs体验</title>
  <script src="node_modules/seajs/dist/sea.js"></script>
  <script>
    // 在Seajs中模块的引入需要相对路径完整写法
    seajs.use('./01-calculator.js', function(calculator) {
      var ta = document.getElementById('txt_a');
      var tb = document.getElementById('txt_b');
      var tres = document.getElementById('txt_res');
      var btn = document.getElementById('btn');
      var op = document.getElementById('sel_op');

      btn.onclick = function() {
        switch (op.value) {
          case '+':
            tres.value = calculator.add(ta.value, tb.value);
            break;
          case '-':
            tres.value = calculator.subtract(ta.value, tb.value);
            break;
          case 'x':
            tres.value = calculator.multiply(ta.value, tb.value);
            break;
          case '÷':
            tres.value = calculator.divide(ta.value, tb.value);
            break;
        }
      };
    });
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
</body>

</html>
<!-- 01-calculator.js -->

// 定义一个模块,遵循Seajs的写法
define(function(require, exports, module) {
  // 此处是模块的私有空间
  // 定义模块的私有成员
  // 载入01-convertor模块
  var convertor = require('./01-convertor.js');

  function add(a, b) {
    return convertor.convertToNumber(a) + convertor.convertToNumber(b);
  }

  function subtract(a, b) {
    return convertor.convertToNumber(a) - convertor.convertToNumber(b);
  }

  function multiply(a, b) {
    return convertor.convertToNumber(a) * convertor.convertToNumber(b);
  }

  function divide(a, b) {
    return convertor.convertToNumber(a) / convertor.convertToNumber(b);
  }
  // 暴露模块的公共成员
  exports.add = add;
  exports.subtract = subtract;
  exports.multiply = multiply;
  exports.divide = divide;
});
<!-- 01-convertor.js -->
/**
 * 转换模块,导出成员:convertToNumber
 */
define(function(require, exports, module) {
  // 公开一些转换逻辑
  exports.convertToNumber = function(input) {
    return parseFloat(input);
  }
});

导出成员的方式

  • module.exports
  • exports.xxx
  • return
  • 三种方式的优先级
<!-- 02-export.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Seajs体验</title>
  <script src="node_modules/seajs/dist/sea.js"></script>
  <script>
    // 在Seajs中模块的引入需要相对路径完整写法
    seajs.use('./02-calculator.js', function(e) {
      // var p = new Person();
      console.log(e);
    });
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
</body>

</html>
<!-- 02-calculator.js -->

// 定义一个模块,遵循Seajs的写法
define(function(require, exports, module) {

  // function add(a, b) {
  //   return parseFloat(a) + parseFloat(b);
  // }

  // function subtract(a, b) {
  //   return parseFloat(a) - parseFloat(b);
  // }

  // function multiply(a, b) {
  //   return parseFloat(a) * parseFloat(b);
  // }

  // function divide(a, b) {
  //   return parseFloat(a) / parseFloat(b);
  // }
  // // 暴露模块的公共成员
  // exports.add = add;
  // exports.subtract = subtract;
  // exports.multiply = multiply;
  // exports.divide = divide;

  // console.log(module.exports === exports);
  //
  // function Person(name, age, gender) {
  //   this.name = name;
  //   this.age = age;
  //   this.gender = gender;
  // }

  // Person.prototype.sayHi = function() {
  //   console.log('hi! I\'m a Coder, my name is ' + this.name);
  // };

  // // exports.Person = Person;
  // module.exports = Person;
  // 最终导出的以 module.exports
  // module.exports = { name: 'world' };
  // // 此时module.exports 指向的是一个新的地址
  // exports.name = 'hello';
  // // exports是module.exports的快捷方式,指向的任然是原本的地址

  // module.exports优先级第二
  module.exports = { name: 'hello' };
  console.log(module);
  // return 的优先级最高
  return { name: 'world' };
});

异步加载模块

  • 默认require的效果是同步的,会阻塞代码的执行,造成界面卡顿
  • require.async();
require.async('path',function(module) {

});
<!-- 03-async-require.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Seajs体验</title>
  <script src="node_modules/seajs/dist/sea.js"></script>
  <script>
    // 在Seajs中模块的引入需要相对路径完整写法
    seajs.use('./03-module1.js', function(e) {
      // var p = new Person();
      console.log(e);
    });
  </script>
</head>

<body>
  <input type="text" id="txt_a">
  <select id="sel_op">
    <option value="+">+</option>
    <option value="-">-</option>
    <option value="x">x</option>
    <option value="÷">÷</option>
  </select>
  <input type="text" id="txt_b">
  <input type="button" id="btn" value=" = ">
  <input type="text" id="txt_res">
</body>

</html>
<!-- 03-module1.js -->

define(function(require, exports, module) {
  // console.log('module1 ---- start');
  // // require 必须执行完成过后(./module2.js加载完成)才可以拿到返回值
  // var module2 = require('./03-module2.js'); // 阻塞代码执行
  // // JS中的阻塞会有卡顿的情况出现
  // console.log('module1 ---- end');
  //
  console.log('module1 ---- start');
  require.async('./03-module2.js', function(module2) {

  }); // 此处不会阻塞代码执行

  console.log('module1 ---- end');
});
<!-- 03-module2.js -->

define(function() {
  console.log('module2 exec');
});

使用第三方依赖(jQuery)

  • 由于CMD是国产货,jquery默认不支持
  • 改造

javascript // 适配CMD if (typeof define === "function" && !define.amd) { // 当前有define函数,并且不是AMD的情况 // jquery在新版本中如果使用AMD或CMD方式,不会去往全局挂载jquery对象 define(function() { return jQuery.noConflict(true); }); }

<!-- 04-dependences.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Seajs使用第三方依赖</title>
  <script src="node_modules/seajs/dist/sea.js"></script>
  <script>
    seajs.use('./04-main.js');
  </script>
</head>

<body>
</body>

</html>
<!-- 04-main.js -->

'use strict';

define(function(require, exports, module) {
  // 想用jquery怎么办
  var $ = require('./jquery.js');
  console.log($);
  $(document.body).css('backgroundColor', 'red');
});

Seajs配置

  • 配置
  • seajs.config
    • base
    • alias
<!-- 05-config.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <script src="node_modules/seajs/dist/sea.js"></script>
  <script>
    seajs.config({
      alias: {
        // 变化点封装
        calc: './modules/calc.js',
      }
    });
    seajs.use('calc');
  </script>
</head>

<body>
</body>

</html>
<!-- calc.js -->

'use strict';
define(function(require, exports, module) {
  console.log(11111);
});

使用案例

  • Tab标签页
<!-- 06-pagination.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>使用模块化的方式定义一个分页组件</title>
  <link rel="stylesheet" href="../bootstrap.min.css">
  <script src="node_modules/seajs/dist/sea.js"></script>
  <script>
    window.onload = function() {
      seajs.use('./06-main.js');
    };
  </script>
</head>

<body>
  <ul class="pagination"></ul>
  <ul class="pagination1"></ul>
</body>

</html>
<!-- 06-main.js -->

define(function(require, exports, module) {
  var Pagination = require('./modules/pagination.js');
  var pager = new Pagination(1, 20, 7);
  pager.render('.pagination');
  pager.render('.pagination1');
});
<!-- pagination.js -->

define(function(require, exports, module) {
// format = 'http://www.baidu.com/page/@/'
  function Pagination(current, total, show, format) {
    this.current = current;
    this.total = total;
    this.show = show;
    // 1. 根据显示数量算出正常情况当前页的左右各有几个
    var region = Math.floor(show / 2);
    // 2. 计算出当前界面上的起始值
    var begin = current - region; // 可能小于 1
    begin = begin < 1 ? 1 : begin;
    var end = begin + show; // end必须小于total
    if (end > total) {
      end = total + 1;
      begin = end - show;
      begin = begin < 1 ? 1 : begin;
    }
    this.begin = begin;
    this.end = end;
  };

  /**
   * 渲染当前这个组件到界面上
   */
  Pagination.prototype.render = function(containers) {
    // 获取分页展示容器
    // p.render('.pgfds');
    if (typeof containers === 'string') {
      containers = document.querySelectorAll(containers);
    }
    if (containers.length === undefined) {
      // dom对象
      containers = [containers];
    }
    for (var c = 0; c < containers.length; c++) {
      var container = containers[c];
      // 先append上一页
      var prevElement = document.createElement('li');
      prevElement.innerHTML = '<a href="?page=' + (this.current - 1) + '" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>';
      if (this.current == 1) {
        prevElement.classList.add('disabled');
      }
      container.appendChild(prevElement);
      for (var i = this.begin; i < this.end; i++) {
        var liElement = document.createElement('li');
        liElement.innerHTML = '<a href="?page=' + i + '">' + i + '</a>';
        if (i == this.current) {
          // 此时是当前页
          liElement.classList.add('active');
        }
        container.appendChild(liElement);
      }
      var nextElement = document.createElement('li');
      nextElement.innerHTML = '<a href="#" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>';
      if (this.current == this.total) {
        nextElement.classList.add('disabled');
      }
      container.appendChild(nextElement);
    }
  };

  module.exports = Pagination;
});

RequireJS

猜你喜欢

转载自blog.csdn.net/tichimi3375/article/details/82709249