一、模块化了解
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
办法来同步加载要依赖的模块,而后通过extports
或module.exports
来导出须要裸露的接口。
// module1.jsvar 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); // 5console.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.jsvar data = 5;var doSomething = function () { data++;};// 裸露的接口module.exports.data = data;module.exports.doSomething = doSomething;
var example = require('./module1.js');console.log(example.data); // 5example.doSomething(); console.log(example.data); // 5
ES6 模块的加载机制:
// module1.jslet data = 5;function doSomething() { data++;}export { data, doSomething }
import { data, doSomething } from './module1';console.log(data); // 5doSomething();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 在语言规范的层面上,实现了模块性能简略,齐全能够成为浏览器和服务器通用的模块解决方案。