js中的模块化

  前阵子一直忙着找实习,发现已经有一段时间没写博客了,面试不少时候会被问到模块化,今天就让咱们一块儿来总结下把html

 

1、什么是模块化java

  在js出现的时候,js通常只是用来实现一些简单的交互,后来js开始获得重视,用来实现愈来愈复杂的功能,而为了维护的方便,咱们也把不一样功能的js抽取出来当作一个js文件,可是当项目变的复杂的时候,一个html页面可能须要加载好多个js文件,而这个时候就会出现各类命名冲突,若是js也能够像java同样,把不一样功能的文件放在不一样的package中,须要引用某个函数或功能的时候,import下相关的包,这样能够很好的解决命名冲突等各类问题,可是js中没有模块的概念,又怎么实现模块化呢jquery

  模块化开发是一种管理方式,是一种生产方式,一种解决问题的方案,一个模块就是实现特定功能的文件,有了模块,咱们就能够更方便地使用别人的代码,想要什么功能,就加载什么模块,可是模块开发须要遵循必定的规范,不然就都乱套了,所以,才有了后来你们熟悉的AMD规范,CMD规范es6

  接下来,咱们就一块儿学习下AMD,CMD和es6中的模块化吧面试

 

2、AMD数组

  AMD 即Asynchronous Module Definition,中文名是“异步模块定义”的意思,它采用异步方式加载模块,模块的加载不影响它后面语句的运行,全部依赖这个模块的语句,都定义在一个回调函数中,等到加载完成以后,这个回调函数才会运行
浏览器

  通常来讲,AMD是 RequireJS 在推广过程当中对模块定义的规范化的产出,由于平时在开发中比较经常使用的是require.js进行模块的定义和加载,通常是使用define来定义模块,使用require来加载模块服务器

一、定义模块异步

  AMD规范只定义了一个函数define,它是全局变量,咱们能够用它来定义一个模块模块化

define(id?, dependencies?, factory);

  其中,id是定义中模块的名字,这个参数是可选的,若是没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字,若是提供了该参数,模块名必须是“顶级”的和绝对的

  dependencies是定义的模块中所依赖模块的数组,依赖模块必须根据模块的工厂方法优先级执行,而且执行的结果应该按照依赖数组中的位置顺序以参数的形式传入(定义中模块的)工厂方法中

  factory是模块初始化要执行的函数或对象,若是为函数,它应该只被执行一次,若是是对象,此对象应该为模块的输出值

  下面来看一个定义模块的例子

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

  上面的代码定义了一个alpha的模块,这个模块依赖require,exports,beta,所以须要先加载它们,再执行后面的factory

二、加载模块

  require.js中采用require()语句加载模块,在定义好了模块后,咱们可使用require进行模块的加载

require([module], callback);

  require要传入两个参数,第一个参数[module],是一个数组,里面的成员就是要加载的模块,第二个参数callback,则是加载成功以后的回调函数

  下面咱们来看一个例子

require([increment'], function (increment) {
    increment.add(1);
});

  上面的代码中,好比咱们如今已经定义了一个模块,名字为increment,里面有一个add方法,咱们如今须要用到里面的方法,只要像上面同样将模块加载进来,而后调用方法就能够了

三、requirejs使用例子

  在使用require.js时,能够经过define()定义模块,这时候里面的模块的方法和变量外部是没法访问到的,只有经过return,而后再加载这个模块,才能够进行访问

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

  上面的代码定义了一个math模块,返回了一个add方法,要使用这个模块的方法,咱们须要向下面这样进行访问

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

  经过require,咱们加载了math模块,这样就可使用math模块里面的add方法了

 

3、CMD

   CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,同时,CMD是在SeaaJS推广的过程当中造成的,CMD和AMD要解决的都是同个问题,在使用上也都很像,只不过二者在模块定义方式和模块加载时机上有所不一样
一、定义模块
  在 CMD 规范中,一个模块就是一个文件,经过define()进行定义
define(factory);

  define接受factory参数,factory能够是一个函数,也能够是一个对象或字符串

  factory为对象、字符串时,表示模块的接口就是该对象、字符串,好比能够以下定义一个 JSON 数据模块

define({ "foo": "bar" });

       也能够经过字符串定义模板模块

define('I am a template. My name is {{name}}.');

     factory为函数时,表示是模块的构造方法,执行该构造方法,能够获得模块向外提供的接口,factory方法在执行时,默认会传入三个参数:require,exports和 module

define(function(require, exports, module) {

  // 模块代码

});

  其中,require用来加载其它模块,而exports能够用来实现向外提供模块接口

define(function(require, exports) {

  // 对外提供 foo 属性
  exports.foo = 'bar';

  // 对外提供 doSomething 方法
  exports.doSomething = function() {};

});

  module是一个对象,上面存储了与当前模块相关联的一些属性和方法,传给factory构造方法的exports参数是module.exports对象的一个引用,只经过exports参数来提供接口,有时没法知足开发者的全部需求,好比当模块的接口是某个类的实例时,须要经过module.exports来实现

define(function(require, exports, module) {

  // exports 是 module.exports 的一个引用
  console.log(module.exports === exports); // true

  // 从新给 module.exports 赋值
  module.exports = new SomeClass();

  // exports 再也不等于 module.exports
  console.log(module.exports === exports); // false

});

  说了这么多,相信你们可能有点乱,来个简单的例子,咱们看看使用AMD和CMD定义的模块的写法

// CMD
define(function(require, exports, module) {
  var a = require('./a')
  a.doSomething()
  // 此处略去 100 行
  var b = require('./b') // 依赖能够就近书写
  b.doSomething()
  // ... 
})

// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
  a.doSomething()
  // 此处略去 100 行
  b.doSomething()
  ...
}) 

  在上面的代码中,相信你们很容易能够看出区别吧,AMD和CMD都是经过define()定义模块,AMD须要把依赖的模块先写出来,能够经过return暴露接口,CMD在定义模块须要传入require,exports和module这几个参数,要加载某个模块时,使用require进行加载,要暴露接口时,能够经过exports,module.exports和return

二、加载模块

  在前面定义模块时,咱们说过,当factory为函数时,require会做为默认参数传递进去,而require能够实现模块的加载

  require是一个方法,接受模块标识做为惟一参数,用来获取其余模块提供的接口

define(function(require, exports) {

  // 获取模块 a 的接口
  var a = require('./a');

  // 调用模块 a 的方法
  a.doSomething();

});
   从上面定义模块和加载模块的方式上,咱们也能够看出AMD和CMD主要有下面几个不一样:
      (1)AMD是RequireJS在推广过程当中对模块定义的规范化产出,CMD是SeaJS在推广过程当中对模块定义的规范化产出
      (2)对于依赖的模块,AMD是提早执行,CMD是延迟执行
      (3)对于依赖的模块,AMD推崇依赖前置,CMD推崇依赖就近
三、seajs使用例子
  由于CMD是SeaJS在推广过程当中对模块定义的规范化产出,所以通常在实际开发中,咱们都是经过SeaJS进行模块的定义和加载
  下面是一个简单的例子
// 定义模块  myModule.js
define(function(require, exports, module) {
  var $ = require('jquery.js')
  $('div').addClass('active');
  exports.data = 1;
});

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

  上面的代码中定义了myModule.js模块,由于该模块依赖于jquery.js,所以在须要使用该模块时可使用require进行模块的加载,而后经过exports暴露出接口,经过SeaJS的use方法咱们能够加载该模块,而且使用该模块暴露出的接口

 

4、es6中的模块化

  在es6没有出来以前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种,前者用于服务器,后者用于浏览器,ES6 在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案

  es6中的模块化有一个比较大的特色,就是实现尽可能的静态化,好比说在CommonJS中咱们要加载fs中的几个方法,须要这样写

// CommonJS模块
let { stat, exists, readFile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

  上面的代码实际上是加载了fs中的全部方法,生成一个对象,再从这个对象上读取方法,这种加载其实叫作运行时加载,也就是只有运行时才能获得这个对象,不能实如今编译时实现静态优化

  ES6 模块不是对象,而是经过export命令显式指定输出的代码,再经过import命令输入

// ES6模块
import { stat, exists, readFile } from 'fs';

  上面代码的实质是从fs模块加载 3 个方法,其余方法不加载,这种加载称为“编译时加载”或者静态加载,即 ES6 能够在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高,固然,这也致使了无法引用 ES6 模块自己,由于它不是对象

一、export

  模块功能主要由两个命令构成:export和import,export命令用于规定模块的对外接口,import命令用于输入其余模块提供的功能

  通常来讲,一个模块就是一个独立的文件,该文件内部的全部变量,外部没法获取,若是你但愿外部可以读取模块内部的某个变量,就必须使用export关键字输出该变量

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

  若是要输出函数,能够像下面这样定义

function v1() { ... }
function v2() { ... }

export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

  上面的代码中,咱们使用了as对函数的对外接口进行了重命名

二、import

  使用export命令定义了模块的对外接口之后,其余 JS 文件就能够经过import命令加载这个模块

// main.js
import {firstName, lastName, year} from './profile.js';

function setName(element) {
  element.textContent = firstName + ' ' + lastName;
}

  import命令接受一对大括号,里面指定要从其余模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同

  咱们也能够对加载的模块进行重命名

import { lastName as surname } from './profile.js';

  除了指定加载某个输出值,还可使用总体加载,即用星号(*)指定一个对象,全部输出值都加载在这个对象上面

  下面是一个circle.js文件,它输出两个方法area和circumference

// circle.js

export function area(radius) {
  return Math.PI * radius * radius;
}

export function circumference(radius) {
  return 2 * Math.PI * radius;
}

  总体加载的写法以下

import * as circle from './circle';

console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));

  这里有一个地方须要注意,模块总体加载所在的那个对象(上例是circle),应该是能够静态分析的,因此不容许运行时改变,下面的写法都是不容许的

import * as circle from './circle';

// 下面两行都是不容许的
circle.foo = 'hello';
circle.area = function () {};

  关于import其实还有不少用法,具体的你们能够查看相关的文档

 

  今天就先介绍到这里,其实还有commonjs,尚未进行介绍,若是你们感兴趣,能够查看相关的用法呢

相关文章
相关标签/搜索