美文网首页
ES6语法—Module模块化(上)

ES6语法—Module模块化(上)

作者: silly鸿 | 来源:发表于2018-01-03 10:53 被阅读0次

在系统学习ES6语法过程中,才真正地理解到前端模块化编程的思想,所以决定写下这篇文章,让更多的人体会到前端模块化。但是这篇文章并不会直接讲述ES6中的模块语法,而是通过讲述ES6之前的模块化规范,从而引出ES6模块化,因为不了解历史就无法认识

如果你想直接了解ES6模块化可以直接跳过篇文章,请移步ES6语法—Module模块化(下)

读完这篇文章,你能收获到

前端模块化的意义
模块的写法
CommandJS规范
浏览器环境
AMD规范&&RequireJS
CMD规范&&SeaJS

ES6(ES2017)之前,前端模块规范有三种: CommandJS、AMD和CMD
CommandJS用在服务端模块化规范,AMD和CMD用在浏览器模块化规范

CommandJS: NodeJS
AMD: RequireJS
CMD: SeaJS

CommandJS:同步加载
AMD:提前执行(异步加载,回调执行)
CMD:延迟执行(运行时按需加载,顺序执行)

前端模块化的意义

  • 解决命名冲突,防止全局变量的污染
  • 管理文件依赖
  • 保护私有属性
  • 易于代码的扩展和维护

了解更多前端模块化开发的价值

模块的写法

  • 函数的写法
    只要把不同的函数简单地放在一起,就算是一个模块
 function f1(){
    //...
  }
  function f2(){
    //...
  }

这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

  • 对象写法
    为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
 var module1 = new Object({
    count : 0,
    m1 : function (){
      //...
    },
    m2 : function (){
      //...
    }
  });

    //调用这个对象的属性
    module1.m1()
 

但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。

   module1.count  = 5
  • 立即执行函数写法(IIFE)
var module1 = (function(){
    var _count = 0;
    var m1 = function(){
      //...
    };
    var m2 = function(){
      //...
    };
    return {
      m1 : m1,
      m2 : m2
    };
  })();

使用上面的写法,外部代码无法读取内部的_count变量

console.info(module1._count); //undefined

module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。

  • 放大模式
  var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);

上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。

  • 输入全局变量
    独立性是模块的重要特点,模块内部最好不要和程序的其他部门直接交互
    为了在模块内部调用全局变量,必须显式地将全局变量输入模块
var module1 = (function($){
  //...
})(jQuery)

CommandJS规范

CommonJS是服务器端模块的规范,由Node推广使用。在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务端,一定要有模块,与操作系统和其他应用程序,否则根本没法编程

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

根据CommonJS中:

  • 一个单独的文件就是一个模块。每个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性
  • 输出模块属性和方法可以用module.exports或者exports对象
  • 载入模块使用require()方法,该方法是运行时加载,生成一个对象,可以读取模块的属性和方法
  //index.js
 var math = require('math');

然后,就可以调用模块提供的方法:

 //math.js
 exports.add  = function(a, b){
    return a + b 
 }

 //index.js
 var math = require('math');
 math.add(2,3); // 5

仔细观察上面的代码, 你会注意到require()是同步的。模块系统需要同步读取模块文件内容,并编译执行以得到模块接口

浏览器环境

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

但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,会有一个很大的问题,你能看得出来吗?

//index.js
 var math = require('math');
 math.add(2,3); // 5

嘿,哥们,上面已经透露了,require()是同步执行的,因此第二行代码math.add(2,3)必须等math.js加载完成。也就是说,如果加载时间很长,浏览器整个页面就会阻塞

同步加载对服务器不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待的时间就是硬盘的读取时间,速度非常快。但是对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于“假死状态”

因此,浏览器端的模块,不能采用“同步加载(synchronous)”,只能采用“异步加载(asynchronous)”.这就是AMD规范诞生的背景

AMD规范

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

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

require([module], callback);

第一个参数[module]是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。如果将前面的代码写成AMD形式,就是下面这样:

requrie(['math], function(math){
      math.add(2, 3);
})

math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

实际上AMD是RequireJS在推广过程中对模块定义的规范户的产出

RequireJS主要解决两个问题

  • 实现js文件的异步加载,避免网页失去响应
  • 管理模块之间的依赖关系,便于代码的编写和维护

define()函数

RequireJS定义了一个函数 define,它是全局变量,用来定义模块
define(id?, dependencies?, factory);
参数说明:

  • id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
  • 依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
    依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
  • 工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

来举个🌰看看:

define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
      exports.verb = function() {
          return beta.verb();
          //Or:
          return require("beta").verb();
      }
  });

RequireJs使用例子

require.config是用来定义别名的,在paths属性下配置别名。然后通过requirejs(模块数组, callback);参数一是数组,传入我们需要引用的模块名,第二个参数是个回调函数,传入一个变量,代替刚才所引入的模块

//第一步 引入require.js
<script src="js/require.js"></script>

//main.js
//别名配置
requirejs.config({
   paths: {
        jquery: 'jquery.min' //可以省略.js
    }
})

//引入模块,用变量$表示jquery模块
requriejs(['jquery'], function($){
   $('body').css('backgroundColor','red');
})

引入模块也可以只写require()。requirejs通过define()定义模块,定义的参数上同。在模块内的方法和变量外部是无法访问的,只有通过return返回才行

//math.js
define('math',['jquery'], function ($) {//引入jQuery模块
    return {
        add: function(x,y){
            return x + y;
        }
    };
});

main.js引入模块方法

//main.js
require(['jquery','math'], function ($,math) {
    console.log(math.add(10,100));//110
});

CMD规范

CMD即Common Module Definition通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块的定义方式和模块加载(运行、解析)的时机上有所不同

在CMD规范中吗,一个模块就是一个文件。代码的书写格式如下:

define([require, exports, module], function(require, exports, module) {

  // 模块代码

});

require是可以把其他模块导入进来的一个参数;而exports是可以把模块内的一些属性和方法导出的;module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

AMD是提前加载,并且是异步加载所依赖的模块
CMD是延迟加载,运行时按需加载模块

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 提前加载
  a.doSomething()
  // 此处略去 100 行
  b.doSomething()
  ...
}) 

//CMD默认推荐的是
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此处略去 100 行
  var b = require('./b') // 延迟加载
  b.doSomething()
  // ... 
})


seajs使用例子

// 定义模块  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});

///加载模块
seajs.use(['myModule.js'], function(myModule){
  var star= myModule.data;
  console.log(star);  //1
})

ES6语法—Module模块化(上)到这里就已经结束了,下一篇文章ES6语法—Module模块化(下)将会讲述ES6Module语法完全取代CommandJS和AMD规范,成为浏览器和服务器通用的模块解决方案

参考

Javascript模块化编程(一):模块的书写
Javascript模块化编程(二):AMD规范
Javascript模块化编程(三):require.js的用法
前端模块化开发的价值
RequireJS下载地址
SeaJS官方demo下载地址
Seajs使用实例入门介绍

相关文章

网友评论

      本文标题:ES6语法—Module模块化(上)

      本文链接:https://www.haomeiwen.com/subject/rodqnxtx.html