js中的模块化——commonjs,AMD,CMD,UMD,ES6

媒介

历史上,js没有模块化的观点,不能把一个大工程分解成许多小模块。这关于多人开辟大型,庞杂的项目形成了庞大的停滞,显著降低了开辟效力,java,Python有import,甚至连css都有@import,然则令人费解的是js竟然没有这方面的支撑。es6涌现以后才处置惩罚了这个题目,在这之前,各大社区也都涌现了许多处置惩罚要领,比较精彩的被人人广为流传的就有AMD,CMD,commonjs,UMD,本日我们就来剖析这几个模块化的处置惩罚计划。

模块加载

上面提到的几种模块化的计划的模块加载有何异同呢?
先来说下es6模块,es6模块的设想头脑是只管静态化,使得编译时就能够肯定依靠关联,被称为编译时加载。其他的都只能在运行时肯定依靠关联,这类被称为运行时加载。下面来看下例子就邃晓了,比方下面这段代码

let {a,b,c} = require("util");//会加载util里的一切要领,运用时只用到3个要领
import {a,b,c} from 'util';//从util中加载3个要领,其他不加载

模块化的几种计划

下面简朴引见一下AMD,CMD,commonjs,UMD这几种模块化计划。

commonjs

commonjs是服务端模块化采纳的范例,nodejs就采纳了这个范例。
依据commonjs的范例,一个零丁的文件就是一个模块,加载模块运用require要领,该要领读取文件并实行,返回export对象。

// foobar.js
//私有变量
var test = 123;
//公有要领
function foobar () {
 
    this.foo = function () {
        // do someing ...
    }
    this.bar = function () {
        //do someing ...
    }
}
//exports对象上的要领和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;
//读取
var test = require('./foobar').foobar;
test.bar();

CommonJS 加载模块是同步的,所以只需加载完成才实行背面的操纵。像Node.js主要用于服务器的编程,加载的模块文件平常都已存在当地硬盘,所以加载起来比较快,不必斟酌异步加载的体式格局,所以CommonJS范例比较实用。但假如是浏览器环境,要从服务器加载模块,这是就必需采纳异步形式。所以就有了 AMD CMD 处置惩罚计划。

AMD

AMD是”Asynchronous Module Definition”的缩写,意义就是”异步模块定义”
AMD设想出一个简约的写模块API:

define(id?, dependencies?, factory);

第一个参数 id 为字符串范例,示意了模块标识,为可选参数。若不存在则模块标识应当默许定义为在加载器中被要求剧本的标识。假如存在,那末模块标识必需为顶层的或许一个相对的标识。
第二个参数,dependencies ,是一个当前模块依靠的,已被模块定义的模块标识的数组字面量。
第三个参数,factory,是一个须要举行实例化的函数或许一个对象。

看下下面的例子就邃晓了

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

提到AMD就不得不提requirejs。
RequireJS 是一个前端的模块化治理的东西库,遵照AMD范例,它的作者就是AMD范例的创始人 James Burke。
AMD的基本头脑就是先加载须要的模块,然后返回一个新的函数,一切的操纵都在这个函数内部操纵,之前加载的模块在这个函数里是能够挪用的。

CMD

CMD是seajs在推行的过程当中对模块的定义的范例化产出
和AMD提早实行差别的是,CMD是耽误实行,不过requirejs从2.0最先也最先支撑耽误实行了,这取决于写法。
AMD引荐的是依靠前置,CMD引荐的是依靠就近。
看下AMD和CMD的代码

//AMD
define(['./a','./b'], function (a, b) {
    //依靠一最先就写好
    a.test();
    b.test();
});
 
//CMD
define(function (requie, exports, module) {
    //依靠能够就近誊写
    var a = require('./a');
    a.test();
    ...
    //软依靠
    if (status) {
        var b = requie('./b');
        b.test();
    }
});

UMD

UMD是AMD和commonjs的连系
AMD实用浏览器,commonjs实用服务端,假如连系了二者就达到了跨平台的处置惩罚计划。
UMD先推断是不是支撑AMD(define是不是存在),存在用AMD模块的体式格局加载模块,再推断是不是支撑nodejs的模块(exports是不是存在),存在用nodejs模块的体式格局,不然挂在window上,当全局变量运用。
这也是现在许多插件头部的写法,就是用来兼容种种差别模块化的写法。

(function(window, factory) {
    //amd
    if (typeof define === 'function' && define.amd) {
        define(factory);
    } else if (typeof exports === 'object') { //umd
        module.exports = factory();
    } else {
        window.jeDate = factory();
    }
})(this, function() {  
...module..code...
})

ES6

es6的模块自动采纳严厉形式,不论有无在头部加上’use strict’
模块是由export和import两个敕令组成。

export敕令

  1. export敕令能够涌现在模块的任何位置,只需处于模块的顶层(不在块级作用域内)即可。假如处于块级作用域内,会报错。
  2. export语句输出的值是动态绑定的,绑定其地点的模块。

export default敕令

//a.js
export default function(){
  console.log('aaa');
}
//b.js
import aaa from 'a.js';

1.运用export default的时刻,对应的import不须要运用大括号,import敕令能够为default指定恣意的名字。
2.不实用export default的时刻,对应的import是须要运用大括号的
3.一个export default只能运用一次

import敕令

  1. import敕令具有提拔效果,会提拔到全部模块的头部起首实行,所以发起直接写在头部,如许也轻易检察和治理。
  2. import语句会实行所加载的模块,因为有以下的写法
  import 'lodash;

上面的代码仅仅实行了lodash模块,没有输入任何值

团体加载

团体加载有两种体式格局

//import
import * as circle from './circle'
//module
//module背面跟一个变量,示意输入的模块定义在该变量上
module circle from './circle'

轮回加载

在讲轮回加载前,先了解下commonjs和es6模块加载的道理

commonjs模块加载的道理

commonjs的一个模块就是一个剧本文件,require敕令第一次加载剧本的时刻就会实行全部剧本,然后在内存中天生一个对象

{
  id:"...",
  exports: {...},
  loaded: true,
  ...
}

上面的对象中,id是模块名,exports是模块输出的各个接口,loaded是一个布尔值,示意该模块的剧本是不是实行终了.
以后要用到这个模块时,就会到exports上取值,纵然再次实行require敕令,也不会实行该模块,而是到缓存中取值

es6模块加载的

commonjs模块输入的是被输出值的拷贝,也就是说一旦输出一个值,模块内部的变化就影响不到这个值
es6的运行机制和commonjs不一样,它碰到模块加载敕令import不会去实行模块,只会天生一个动态的只读援用,比及真正要用的时刻,再到模块中去取值,因为es6输入的模块变量只是一个‘标记链接’,所以这个变量是只读的,对他举行从新赋值会报错。

import {obj} from 'a.js';
obj.a = 'qqq';//ok
obj = {}//typeError

剖析完二者的加载道理,来看下二者的轮回加载

commonjs的轮回加载

commonjs模块的主要特征是加载时实行,即代码在require的时刻就会实行,commonjs的做法是一旦涌现轮回加载,就只输出已实行的部份,还未实行的部份不会输出.
下面来看下commonjs中的轮回加载的代码

//a.js
exports.done = false;
var b = require('./b.js');
console.log('在a.js中,b.done=',b.done);
exports.done = true;
console.log('a.js实行终了')
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在b.js中,a.done=',a.done);
exports.done = true;
console.log('b.js实行终了')
//main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在main.js中,a.done=',a.done,',b.done=',b.done);

上面的代码中,实行a.js的时刻,a.js先输出done变量,然后加载另一个剧本b.js,此时a的代码就停在这里,守候b.js实行终了,再往下实行。然后看下b.js的代码,b.js也是先输出done变量,然后加载a.js,这时候发生了轮回加载,根据commonjs的机制,体系会去a.js中的exports上取值,但是实在a.js是没有实行完的,只能输出已实行的部份done=false,然后b.js继承实行,实行终了后将实行权返回给a.js,因而a.js继承实行,直到实行终了。
所以实行main.js,效果为
在b.js中,a.done=false
b.js实行终了
在a.js中,b=done=true
a.js实行终了
在main.js中,a.done=true,b.done=true
上面这个例子说了两点

  1. 在b.js中a.js没有实行完,只实行了第一行

2.main.js中实行到第二行不会再次实行b.js,而是输出缓存的b.js的实行效果,即第4行

es6的轮回加载

es6处置惩罚轮回加载和commonjs差别,es6是动态援用,碰到模块加载敕令import时不会去实行模块,只会天生一个指向模块的援用,须要开辟者本身保证能取到输出的值
看下面的例子

//a.js
import {odd} from 'b.js';
export counter = 0;
export function even(n){
  counter++;
  return n==0 || odd(n-1);
}
//b.js
import {even} from 'a.js';
export function odd(n){
  return n!=0 && even(n-1);
}
//main.js
import {event,counter } from './a.js';
event(10)
counter //6

实行main.js,根据commonjs的范例,上面的代码是没法实行的,因为a先加载b,b又加载a,然则a又没有输出值,b的even(n-1)会报错
然则es6能够实行,效果是6

    原文作者:juan26
    原文地址: https://segmentfault.com/a/1190000017209262
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞