乐趣区

关于javascript:模块化CommonJsAMDCMDES6

一、模块化了解

1. 什么是模块

  • 将简单的程序根据肯定的规定 (标准) 拆分成多个模块(文件)
  • 模块的外部数据与实现是公有的, 只是向内部裸露一些接口 (办法) 与内部其它模块通信

2. 模块化的进化过程

全局 function : 将不同的性能封装成不同的全局函数

` 毛病 `:虽说能够实现肯定的封装成果,然而大量的全局函数,净化全局命名空间,容易引起命名抵触
function module1 () {//...}
function module2 () {//...}

命名空间 : 简略对象封装

` 毛病 `:缩小了全局变量,解决命名抵触,然而内部能够间接批改模块外部的数据
let module = {
  data: 'aaa',
  func () {console.log(`${this.data}`)
  }
}
module.data = 'bbb' // 间接批改模块外部的数据
module.fn() // bbb

IIFE:(自执行函数)

` 毛病 `:实现数据公有, 内部只能通过裸露的办法操作,如果以后这个模块依赖另一个模块怎么办?
  // module.js 文件()
(function (window) {
  let data = 'aaa'
  function func () {console.log(`${this.data}`)
  }
  // 裸露接口
  window.module = {func}
})(window)
// index.html 文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
  module.func() // aaa
  console.log(module.data) // undefined 不能拜访模块外部数据
  module.data = 'bbb' // 不能批改的模块外部数据
  module.func() // aaa
</script>

IIFE 加强 : 引入依赖

  // module.js 文件
(function (window, $) {
  let data = 'aaa'
  function func () {console.log(`${this.data}`)
  }
  function func2 () {$('body').css('background', 'red')
  }
  // 裸露接口
  window.module = {func, func2}
})(window, jQuery)
 // index.html 文件
  <!-- 引入的 js 必须有肯定程序 -->
  <script type="text/javascript" src="jquery.js"></script>
  <script type="text/javascript" src="module.js"></script>
  <script type="text/javascript">
    module.func2()
  </script>

下面引入 jQuery 库,就把这个库当作参数传入,保障模块的独立性,使得模块之间的依赖关系变得显著。

3. 模块化的作用

通过下面的模块拆分,咱们发现:

  • 缩小了全局变量,无效的防止了命名净化
  • 更好的拆散,按需加载
  • 进步了复用性,维护性

然而比较复杂的利用,模块比拟多,不免须要引入多个<script>,这样又会呈现其余问题:

  • 申请过多
  • 依赖关系含糊

模块化诚然有多个益处,然而一个页面须要引入多个 js 文件,还得按肯定的程序引入,就可能呈现因为引入程序谬误而导致整个我的项目呈现重大问题。而这些问题能够通过模块化标准来解决。

模块化标准

CommonJs

CommonJS 经 node.js 应运而生,依据 CommonJS 标准,每一个模块都是一个独自的作用域。也就是说,在该模块外部定义的变量,无奈被其余模块读取。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块须要提前编译打包解决。

其核心思想就是一个独自文件就是一个模块,通过 require 办法来同步加载要依赖的模块,而后通过 extportsmodule.exports来导出须要裸露的接口。

// module1.js
var data = 5;
var doSomething = function (value) {return value + data;};
// 裸露的接口
module.exports.data = data;
module.exports.doSomething = doSomething;

下面代码通过 module.exports 输入变量 data 和函数 doSomething

var example = require('./module1.js');
console.log(example.data); // 5
console.log(example.doSomething(1)); // 6

require命令用于加载模块文件。require命令的基本功能是,读入并执行一个 JavaScript 文件,而后返回该模块的 exports 对象。

长处:服务器端模块复用性,NPM 中模块包多,有将近 20 万个。

毛病:加载模块是同步的,只有加载实现后能力执行前面的操作,也就是说现加载现用,不仅加载速度慢,而且还会导致性能、可用性、调试和跨域拜访等问题。因为 Node.js 次要用于服务器编程,加载的模块文件个别都存在本地硬盘,加载起来比拟快,不必思考异步加载的形式,因而,CommonJS 标准比拟实用。然而,这并不适宜在浏览器环境,同步意味着阻塞加载,浏览器资源是异步加载的,鉴于浏览器的状况,为了解决上述同步加载问题,实现异步加载依赖模块,因而有了 AMD、CMD 解决方案。

AMD(Asynchronous Module Definition)

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD 标准是 异步加载模块 ,容许指定回调函数。对于依赖的模块,AMD 推崇 提前执行(依赖前置),不过 RequireJS 从 2.0 开始,也改成能够提早执行(依据写法不同,解决形式不同)。

其外围接口是:define(id?, dependencies?, factory),它要在申明模块的时候指定所有的依赖 dependencies,并且还要当做形参传到factory 中,对于依赖的模块提前执行,依赖前置。

// a.js (定义没有依赖的模块)
define(function () {
    let data = 'aaa'
    function doSomething () {console.log(data)
    }
    return {doSomething} // 裸露接口
})
// b.js (定义有依赖的模块)
define(['c'], function (c) {
    let data = 'bbb'
    function doSomething () {console.log(data + c.getData())
    }
    return {doSomething} // 裸露接口
})
// c.js (此模块为 b.js 依赖)
define(function () {
    let data = 'ccc'
    function getData () {return data}
    return {getData} // 裸露接口
})
// 引入依赖的模块
require(['./a', './b'], function (a, b) { // 依赖必须一开始就写好
  a.doSomething()
  // ...
  b.doSomething()
  // ...
})
<body>
    <!-- 引入 require.js 并指定 js 主文件的入口 -->
    <script data-main="./index" src="https://cdn.staticfile.org/require.js/2.3.6/require.min.js"></script>
    <script>
        setTimeout(() => {console.log('setTimeout')
        }, 0)
    </script>
</body>

require()函数在加载依赖的函数的时候是异步加载的,这也是我在这里放了个 setTimeout 证实一下,这样浏览器不会失去响应,它指定的回调函数,只有后面的模块都加载胜利后,才会运行,解决了依赖性的问题。AMD 的异步加载解决了阻塞加载、性能问题,模块之间的依赖关系也能分明的显示进去。

CMD(Common Module Definition)

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。CMD 标准和 AMD 很类似,解决同样问题,只是运行机制不同。对于依赖的模块,CMD 推崇 提早执行(依赖就近)

// a.js(定义没有依赖的模块)
define(function (require, exports, module) {
    let data = 'aaa'
    function doSomething () {console.log(data)
    }
    exports.doSomething = doSomething // 裸露接口
})
// b.js (定义有依赖的模块)
define(function (require, exports, module) {
    let data = 'bbb'
    function doSomething () {var c = require('./c') // 依赖能够就近书写  
        console.log(data + c.data)
    }
    exports.doSomething = doSomething // 裸露接口
})
// c.js (此模块为 b.js 依赖)
define(function (require, exports, module) {
    let data = 'ccc'
    exports.data = data // 裸露模块
})
// 引入依赖的模块
define(function (require, exports, module) {// 引入依赖模块(异步)
  require.async('./a', function (a) {a.doSomething()
    console.log('a 是异步的')
  })
  // 引入依赖模块(同步)
  var b = require('./b') // 依赖能够就近书写  
  b.doSomething()
  // ... 
  var c = require('./c') // 依赖能够就近书写  
  console.log(c.data)
  // ...
})
<body>
    <script src="https://cdn.staticfile.org/seajs/3.0.3/sea.js"></script>
    <script>
        setTimeout(() => {console.log('setTimeout')
        }, 0)
        seajs.use('./index')
    </script>
</body>

ES6

ES6 模块的设计思维是尽量的动态化,使得编译时就能确定模块的依赖关系,以及输出和输入的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些货色。

export命令用于规定模块的对外接口,import命令用于输出其余模块提供的性能。为了提供方便,不必浏览文档就能加载模块,就要用到 export default 命令,为模块指定默认输入。

// a.js (定义模块)
var data = 'aaa'
var doSomething = function () {console.log('log:' + data)
};
export {data, doSomething}

// 援用模块 
import {data, doSomething} from './a'

这里在语法不做过多介绍,次要说一说 ES6 模块CommonJS 模块 的差别。

它们有两个重大差别:

  • CommonJS 模块输入的是一个值的拷贝,ES6 模块输入的是值的援用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输入接口。

第二个差别是因为 CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种动态定义,在代码动态解析阶段就会生成。

咱们来看看第一个差别,CommonJS 模块的加载机制:

// module1.js
var data = 5;
var doSomething = function () {data++;};
// 裸露的接口
module.exports.data = data;
module.exports.doSomething = doSomething;
var example = require('./module1.js');
console.log(example.data); // 5
example.doSomething(); 
console.log(example.data); // 5

ES6 模块的加载机制:

// module1.js
let data = 5;
function doSomething() {data++;}
export {data, doSomething}
import {data, doSomething} from './module1';
console.log(data); // 5
doSomething();
console.log(data); // 6

ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动静援用,并且不会缓存值,模块外面的变量绑定其所在的模块。

总结

  • CommonJS 模块输入的是一个值的拷贝,CommonJS 模块是运行时加载 ,CommonJS 标准次要用于服务端编程, 加载模块是同步的,同步意味着阻塞加载,浏览器资源是异步加载的,因而有了 AMD、CMD 解决方案。
  • AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。AMD 标准在浏览器环境中 异步加载模块 ,而且能够并行加载多个模块。AMD 的 API 默认是 一个当多个用,对于依赖的模块,AMD 推崇提前执行(依赖前置)
  • CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。CMD 的 API 严格辨别,推崇 职责繁多 加载模块是异步的,CMD 推崇提早执行(依赖就近)。
  • ES6 模块输入的是值的援用,ES6 模块是编译时输入接口,ES6 在语言规范的层面上,实现了模块性能简略,齐全能够成为浏览器和服务器通用的模块解决方案。
退出移动版