前端模块化 commonjs AMD CMD ES6模块化

模块化

1.什么是模块化

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

2.模块化的理解

模块化是一种处理复杂系统分解为更好的可管理模块的方式。简单来说就是解耦,简化开发,一个模块就是实现特定功能的文件,可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范

3.模块化的好处

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

4.模块化的作用

  • 减少JS文件的请求次数
    通过模块化将JS文件整合为一个入口,然后引入页面可以有效的减少对JS文件的请求

  • 使各JS文件的依赖关系清晰
    在模块化中可以清晰的分析各模块的引用关系,明确JS代码的结构

  • 降低项目的维护成本
    当有某个模块需要添加或减少某个功能使,不需要将整个代码重构,只需要在相应的模块进行修改就可以

CommonJS规范

Node 应用由模块组成,采用 CommonJS 模块规范,前端的webpack也是对CommonJS原生支持的。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。

基本语法

定义模块

es1.js
var name = "Tom";
var age = 16;
function say(name,age){
   console.log(name+"is"+age+"years old")
}
//语法1
module.exports.name = name;
module.exports.age = age;
module.exports.say = say;
//语法2
exports.name = name;
exports.age = age;
exports.say = say;
//语法3
module.exports = {
  name,
  age,
  say
}
//语法4
module.exports = {
  name : "Tom",
  age : 16,
  say(name,age){
   console.log(name+"is"+age+"years old")
  }
}

注:
1.exports 与module.exports 的区别:exports 是对 module.exports 的引用,不能直接给exports 赋值,直接赋值无效,结果是一个空对象, module.exports 可以直接赋值
2.一个文件不能写多个module.exports ,如果写多个,对外暴露的接口是最后一个module.exports
3.模块如果没有指定使用module.exports 或者exports 对外暴露接口时,在其他文件就引用该模块,得到的是一个空对象{}
4.浏览器不兼容CommonJS,在于缺少四个Node.js环境的变量。(module,exports,require,global)

引用模块

var module = require('./es1.js')
consolo.log(module.name)          //Tom
consolo.log(module.age)           //16
module.say(name,age)              //Tom is 16 years old
//直接引用模块的方法
var name = require('./es1.js').name
var age = require('./es1.js').age
var say= require('./es1.js').say
console.log(name)          //Tom
console.log(age)           //16
say(name,age)              //Tom is 16 years old

CMD规范

CMD规范专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。CMD规范整合了CommonJS和AMD规范的特点。在 Sea.js 中,所有 JavaScript 模块都遵循 CMD模块定义规范。

  • 优点 可以按需加载,依赖就近。
  • 缺点 依赖SPM打包,模块的加载逻辑偏重
基本语法

定义模块

// 使用 exports 直接向外提供接口。
define(function(require, exports) { 
 // 对外提供 name属性
  exports.name = 'Tom'; 
 // 对外提供 say 方法
  exports.say= function(name) {
    console.log("hello"+name)
  };
});
 
// 使用 return 直接向外提供接口。
define(function(require) {  
  return {
    name : 'Tom',    
    say: function(name) {
     console.log("hello"+name)
    }
  };
});
 
// 使用 module.exports 直接向外提供接口。
define(function(require, exports, module) { 
  module.exports = {
    name: 'Tom', 
    say: function(name) {
      console.log("hello"+name)
    }
  };
});

引用模块

define(function (require) {
  var m1 = require('./module1')
  console.log(m1.name)      // Tom
  m1.say(m1.name)           // Hello Tom
})

// require.async 方法用来在模块内部异步加载模块,并在加载完成后执行指定回调。callback 参数可选。
define(function(require, exports, module) {  
// 异步加载一个模块
  require.async('./module1', function(a) {
    a.doSomething();
  }); 

// 异步加载多个模块,在加载完成时,执行回调
  require.async(['./module2', './module3'], function(b, c) {
    b.doSomething();
    c.doSomething();
  });

AMD规范

AMD 即 Asynchronous Module Definition,中文名是“异步模块定义”的意思。在浏览器环境,要从服务器端加载模块,就必须采用非同步模式,因此浏览器端一般采用AMD规范。AMD是一个在浏览器端模块化开发的规范,而AMD规范的实现,就是require.js。

  • 优点 异步加载,不阻塞页面的加载,能并行加载多个模块
  • 缺点 不能按需加载,必须提前加载所需依赖

定义模块

// 独立模块 es1.js
define({
    module1: function() {},
    module2: function() {},
});

// 等价写法
define(function () {
    return {
        module1: function() {},
        module2: function() {},
    };
});
// 非独立模块 只有先加载这两个模块,新模块才能正常运行  es2.js
define(['es1', 'es2'], function(m1, m2) {
       return {
        method: function() {
            m1.methodA();
            m2.methodB();
        }
    };
});

注:上面代码表示新模块返回一个对象,该对象的method方法就是外部调用的接口,menthod方法内部调用了m1模块的methodA方法和m2模块的methodB方法。
需要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。

// 当依赖过多的时,引用变得十分繁琐
define(
    [ 'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7', 'dep8'],
    function(dep1,   dep2,   dep3,   dep4,   dep5,   dep6,   dep7,   dep8){
        ...
    }
);

// RequireJS提供一种更简单的写法
define(
    function (require) {
        var dep1 = require('dep1'),
            dep2 = require('dep2'),
            dep3 = require('dep3'),
            dep4 = require('dep4'),
            dep5 = require('dep5'),
            dep6 = require('dep6'),
            dep7 = require('dep7'),
            dep8 = require('dep8');
            ...
    }
});

引用模块

require(['es1', 'es2'], function ( es1, es2) {
        es1.module1()
        es1.module2()
        es2.mothod()
});

ES6 模块化

ES6 在语言标准的层面上,实现了模块功能,而且非常简单,ES6到来,完全可以取代 CommonJS 和 AMD规范,成为浏览器和服务器通用的模块解决方案。ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。
注:由于ES6目前在部分版本的浏览器中无法执行,所以,我们需要通过babel将不被支持的import编译为当前受到广泛支持的 require。

特点
  1. ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
  2. 自动采用严格模式"use strict"。须遵循严格模式的要求
  3. ES6 模块的设计思想是尽量的静态化,编译时加载”或者静态加载,编译时输出接口
  4. ES6 模块export、import命令可以出现在模块的任何位置,但是必须处于模块顶层。如果处于块级作用域内,就会报错
  5. ES6 模块输出的是值的引用

定义模块 export

//变量    es3.js
export var m = 1;

//函数
export function fn(x, y) {
  return x * y;
};

//类class
class Hello{
  test(){
    console.log("hello")
  }
}
// 也可以合并为一个出口暴露
var m = 1;
function fn(x, y) {
  return x * y;
};
class Hello{
  test(){
    console.log("hello")
  }
}
export {
  m,
  fn,
  Hello
}

// 在暴露模块时,可以通过 as 来进行重命名
export{
  num as m,
  foo as fn,
  Test as Hello
}

引用模块

//静态加载,只加载es3.js 文件中三个变量,其他不加载
import {m, fn, Hello} from './es3.js';

//import命令要使用as关键字,将输入的变量重命名。
import {fn as fn1} from './es3.js';

//整体加载模块
improt * as all from './es3.js'
console.log(all.m)              // 1
console.log(all.fn(3,4))        // 12
all.Hello.test()                // hello

定义模块 export default

// es4.js
export default function foo() {
  console.log('foo');
}

// 或者写成

function foo() {
  console.log('foo');
}
export default foo;

// 当有多个变量或函数需要暴露时,也可以合并为一个出口同时暴露
//  export default  {
//    m,
//    fn,
//    Hello
//  }
//此时的引用方法也发生一点变化
import  myfoo from './es4.js';
myfoo()        // foo   =>(string)

仔细对比可以发现,在引用模块的时候第二种方法接收的变量不再需要包裹在{}中。
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。这样在引用模块的时候,我们就可以自己定义任意的变量来接收传入的模块,而不需要关注原模块输出的函数名了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容