当前位置: 首页 / 技术分享 / 正文
JavaScript 模块化

2022-12-02

模块 js require module

  模块化简介

  JavaScript 最初的目的是为了解决用户交互页面的问题;但是随着互联网技术的发展,浏览器性能大大提升,很多用户交互页面也随之复杂起来,更是随着 Web2.0 的发展,页面异步交互,前端代码库层出不穷,前端代码日益膨胀;此时 Js 方便就会去考虑使用代码模块规范去管理。

  而在项目开发中我们常把一个功能归类为一个模块,多个程序员去负责不同的模块。 js也不例外,然而 js 早期通过闭包的方式去实现模块化的方式存在很多的问题,随着 js 的发展中, 很多人为 js 提供了各种出色的模块化方案。 其中代表有requirejs ,seajs,commonjs,es6 的模块化方案。(其中 es6 的模块化方案必定是将来的主流,这也是至今官方提出唯一的规范)。

  模块化初级

  什么是模块

  将一些复杂的程序代码根据制定的规则封装成几个块(文件), 并进行组合在一起

  块(文件)的内部数据是私有的, 只向外部暴露部分接口(方法)与其它模块进行通信

  模块化的进化

  原始写法

  模块就是实现特定功能的一组方法。只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。

  function m1(){

  //...

  }

  function m2(){

  //...

  }

  上面的函数 m1()和 m2(),组成一个模块。使用的时候,直接调用就行了。这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

  对象写法

  为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。

  var module1 = {

  count : 0.

  m1: function (){

  //...

  },

  m2: function (){

  //...

  }

  };

  上面的函数 m1()和 m2(),都封装在 module1 对象里。使用的时候,就是调用这个对象的属性。module1.m1();但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。module1.count = 5;

  匿名函数自调用(闭包)

  将数据和行为封装到一个函数内部, 通过给 window 添加属性来向外暴露接口

  // module.js 文件

  (function(window,$) {

  var data = '1000phone.com'

  //操作数据的函数

  function foo() {//用于暴露有函数

  console.log("foo()"+data);

  }

  function bar() {//用于暴露有函数

  console.log("bar()"+data);

  $('body').css('background', 'red');

  otherFun(); //内部调用

  }

  function otherFun() {

  //内部私有的函数

  console.log('otherFun()');

  }

  //暴露行为

  window.myModule = {

  foo: foo,

  bar: bar

  }

  })(window, $)

  使用闭包的方式, 可以达到不暴露私有成员的目的, 外部通过暴露的方法操作私有成员

  案例中通过 jquery 方法将页面的背景颜色改成红色, 所以必须先引入 jQuery 库,就把这个库当作参数传入。 这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。

  模块化的好处

  减少命名空间污染

  更好的分离, 按需加载

  更高复用性

  高可维护性

  引入多个模块后出现的问题

  请求过多

  首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多

  依赖模糊

  我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。

  难以维护

  前两个原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。

  模块化固然有多个好处,然而一个页面需要引入多个 js 文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决

  CommonJS

  09 年, Ryan Dahl 创造了 node.js 项目,将 JavaScript 语言用于服务器端编程。这也标志着"JavaScript 模块化编程"正式诞生。因为老实说,在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块, 与操作系统和其他应用程序互动,否则根本没法编程。

  node.js 的模块系统,就是参照 CommonJS 规范实现的。在 CommonJS 中,有一个全局性方法 require(),用于加载模块。假定有一个文件模块 fs.js,就可以像下面这样加载,并调用模块提供的方法

  const fs = require('fs');

  fs.writeFileSync('./test.js'.'hello node');

  //require()用于加载模块

  上面的代码中有一个问题,fs.writeFileSync('./test.js'.'hello node')代码的运行是在载入 fs.js 之后; 即文件的同步加载还是异步加载, 这对于服务端不是问题,但是对于浏览器端就是大问题;浏览器端如果是同步加载模块, 则载入的模块的等待时间取决于网速的快慢,可能需要等待很长时间, 这对用户体验是一个很大的考验。 由此而来 AMD 规范诞生的背景。

  AMD 异步模块定义

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

  我们主要介绍require.js

  require.js 的诞生,就是为了解决这两个问题:

  实现 js 文件的异步加载,避免网页失去响应;

  管理模块之间的依赖性,便于代码的编写和维护。

  require.js 的加载

  使用 require.js 的第一步,是先去官方网站下载最新版本。https://requirejs.org/docs/download.html#latest,下载后,假定把它放在 js/libs 子目录下面,就可以加载了。

  上面的 require.js 加载完毕后,会首先检查script标签中的 data-main 属性, 加载 data-main 属性值中所定义的 js 文件;如:data-main='js/libs/main',将在 require.js 加载完毕后第一个加载 js/libs 路径下的main.js注意: require.js 后缀名是 js,所以 main 的后缀名省略)

  使用案例(引入第三方库)

  创建 js/libs 目录存放 require.js 及第三方库文件

  将下载的 require.js 文件及需要用到的第三方库文件放入 js/libs 目录下

  编写自定义模块

  编写自定义功能模块(网址大写转换)

  // js/modules/dataService.js 文件

  // 定义没有依赖的模块

  define(function() {

  var msg = 'www.1000phone.com';

  function getMsg() {

  return msg.toUpperCase();

  }

  return { getMsg } // 暴露模块

  })

  编写自定义功能模块(控制台输出,改变网页背景颜色)

  // js/modules/console.js 文件

  // 定义有依赖的模块

  define(['dataService','jquery'], function(dataService,$) {

  var name = 'Leon';

  function showMsg() {

  console.log(dataService.getMsg() + ',' + name);

  $('body').css('background','skyblue');

  }

  // 暴露模块

  return { showMsg }

  })

  语法解析: 定义暴露模块:

  //定义没有依赖的模块

  define(function(){

  return 模块

  })

  //定义有依赖的模块

  define(['module1', 'module2'], function(m1. m2){

  return 模块

  })

  // 其中 module1. module2 为依赖模块

  定义入口模块

  // js/main.js 文件

  (function() {

  require.config({

  baseUrl: 'js/', //基本路径 出发点在根目录下

  paths: {

  //映射: 模块标识名: 路径

  // 自定义模块

  console: './modules/console', //此处不能写成 console.js,会报错

  dataService: './modules/dataService',

  // 第三方模块

  jquery: './libs/jquery-1.11.3.min'

  }

  });

  require(['console'], function(console) {

  console.showMsg();

  });

  })()

  语法解析:

  引入使用模块:

  require(['module1', 'module2'], function(m1. m2){

  // 使用 m1/m2

  })

  require——该函数用于读取依赖。同样它是一个全局函数,不需要使用 requirejs 命名空间.

  config——该函数用于配置 RequireJS.

  require.config 配置参数选项

  baseUrl——用于加载模块的根路径。

  paths——用于映射存在根路径下面的模块路径

  定义主页面

  AMD module

  小结:AMD 模块定义的方法非常清晰,不会污染全局环境,能够清楚地显示依赖关系。 AMD 模式可以用于浏览器环境,并且允许非同步加载模块,也可以根据需要动态加载模块。

  CMD

  CMD 规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。 CMD 规范整合了 CommonJS 和 AMD 规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。 Sea.js 可以实现 JavaScript 的模块化开发及加载机制。它应用于早期的一些 js 项目中,是淘宝 js 工程师玉伯提出的一个方案。文档

  CMD 规范基本语法

  define(function(require, exports, module){...});

  用来定义模块。 Sea.js 推崇一个模块一个文件,遵循统一的写法

  define(function(require){var a = require("xModule"); ... });

  require 用来获取指定模块的接口,引入的是模块, js 文件的后缀.js 可以不写

  require.async

  用来在模块内部异步加载一个或多个模块。 例如:

  define(function(require){

  require.async(['aModule','bModule'],function(a,b){

  // 异步加载多个模块,在加载完成时,执行回调

  a.func();

  b.func();

  });

  });

  exports

  用来在模块内部对外提供接口。 例如:

  define(function(require, exports){

  exports.varName01 = 'varValue'; // 对外提供 varName01 属性

  exports.funName01 = function(p1.p2){ // 对外提供 funName01 方法

  ....

  }

  });

  module.exports

  用来在模块内部对外提供接口。例如:

  define(function(require, exports, module) {

  module.exports = { // 对外提供接口

  name: 'a',

  doSomething: function() {...};

  };

  });

  seajs.config({...});

  用来对 Sea.js 进行配置。

  seajs.use(['a','b'],function(a,b){...});

  用来在页面中加载一个或多个模块。

  定义暴露模块:

  //定义没有依赖的模块

  define(function(require, exports, module){

  exports.xxx = value

  module.exports = value

  })

  //定义有依赖的模块

  define(function(require, exports, module){

  //引入依赖模块(同步)

  var module2 = require('./module2')

  //引入依赖模块(异步)

  require.async('./module3', function (m3) {})

  //暴露模块

  exports.xxx = value

  })

  引入使用模块:

  define(function (require) {

  var m1 = require('./module1')

  var m4 = require('./module4')

  m1.show()

  m4.show()

  })

  创建 js/libs 目录,

  将下载的 seajs 放在js/libs目录下

  自定义模块代码并使用

  定义 module1 模块,并暴露 show 方法

  // js/modules/module1.js 文件

  define(function (require, exports, module) {

  //内部变量数据

  var data = '1000phone.com';

  //内部函数

  function show() {

  console.log('module1 show() ' + data);

  }

  //向外暴露

  exports.show = show;

  })

  定义 module2 模块,并暴露接口信息 msg

  // js/modules/module2.js 文件

  define(function (require, exports, module) {

  module.exports = {

  msg: '1000phone study'

  }

  })

  定义 module3 模块,并暴露一个常量信息

  // js/modules/module3.js 文件

  define(function(require, exports, module) {

  const API_KEY = 'leon';

  exports.API_KEY = API_KEY

  })

  定义 module4 模块, 同步引入 module2. 异步引入 module3 模块

  // js/modules/module4.js 文件

  define(function (require, exports, module) {

  // 引入依赖模块(异步)

  require.async('./module3', function (m3) {

  console.log('异步引入依赖模块 3 ' + m3.API_KEY)

  });

  // 引入依赖模块(同步)

  var module2 = require('./module2');

  function show() {

  console.log('module4 show() ' + module2.msg)

  }

  exports.show = show;

  });

  定义主文件 main 模块,引入模块 module1 和模块 module4 并分别调用 show 方法

  // js/modules/main.js 文件

  define(function (require) {

  var m1 = require('./module1')

  var m4 = require('./module4')

  m1.show()

  m4.show()

  })

  创建主页面,并在页面中加载 main 模块

  小结:通过 Sea.js 可以将大量 javascript 代码封装成一个个小模块,然后轻松实现模块的加载和依赖管理

  ES6 的 Module

  在 ES6 之前,模块加载方案主要还是使用 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。 ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。 CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如:CommonJS 模块就是对象,输入时必须查找对象属性。ES6 的模块自动采用严格模式,不管你有没有在模块头部加上 "use strict";。

  模块功能主要命令

  模块功能主要由两个命令构成: export 和 import。

  export 命令用于规定模块的对外接口,

  import 命令用于输入其他模块提供的功能。

  一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。使用 export 命令定义了模块的对外接口以后,其他 JS 文件就可以通过 import 命令加载这个模块。

  /** 定义模块 math.js **/

  var basicNum = 0;

  var add = function (a, b) {

  return a + b;

  };

  export { basicNum, add };

  /** 引用模块 **/

  import { basicNum, add } from './math';

  function test(ele) {

  ele.textContent = add(99 + basicNum);

  }

  浏览器加载 ES6 模块

  浏览器加载 ES6 模块,也使用上面代码在网页中插入一个模块 foo.js,由于 type 属性设为 module,所以浏览器知道这是一个 ES6 模块。

  浏览器对于带有 type="module"的

  使用 Web 服务器打开首页 index.html 文件

图片10

分享: 更多

上一篇:JavaScript 线程与进程初探

下一篇:手封MyPromise

好程序员公众号

  • · 剖析行业发展趋势
  • · 汇聚企业项目源码

好程序员开班动态

More+
  • HTML5大前端 <高端班>

    开班时间:2021-04-12(深圳)

    开班盛况

    开班时间:2021-05-17(北京)

    开班盛况
  • 大数据+人工智能 <高端班>

    开班时间:2021-03-22(杭州)

    开班盛况

    开班时间:2021-04-26(北京)

    开班盛况
  • JavaEE分布式开发 <高端班>

    开班时间:2021-05-10(北京)

    开班盛况

    开班时间:2021-02-22(北京)

    开班盛况
  • Python人工智能+数据分析 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2020-09-21(上海)

    开班盛况
  • 云计算开发 <高端班>

    开班时间:2021-07-12(北京)

    预约报名

    开班时间:2019-07-22(北京)

    开班盛况
在线咨询
试听
入学教程
立即报名

Copyright 2011-2023 北京千锋互联科技有限公司 .All Right 京ICP备12003911号-5 京公网安备 11010802035720号