模块化
模块化
模块化就是把系统分离成独立功能的方法,这样我们需要什么功能,就加载什么功能
- 每个模块都是独立的
- 良好设计的模块会尽量与外部的代码撇清关系,以便于独立对其进行改进和维护
- 可以重复利用,而不用经常复制自己之前写过的代码
区别综述:
https://www.jianshu.com/p/c7af891121e3
https://blog.csdn.net/crystal6918/article/details/74906757
CommonJS与AMD,CMD
-
CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。
-
CommonJS 是服务器端模块的规范,Node.js 采用了这个规范。CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD 规范则是非同步加载模块,允许指定回调函数。
-
AMD 推荐的风格通过返回一个对象做为模块对象,CommonJS 的风格通过对 module.exports 或 exports 的属性赋值来达到暴露模块对象的目的
AMD与CMD
-
AMD 是 RequireJS 在推广过程中对模块定义的规范化产出
-
CMD是SeaJS 在推广过程中对模块定义的规范化产出
-
对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。
-
AMD推崇依赖前置,CMD推崇依赖就近。
CommonJS 和 ES6 中的模块化的两者区别是:
-
前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持,但是已有提案
-
前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响
-
前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。
-
但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化
-
后者会编译成 require/exports 来执行的
-
ES6中的模块有以下特点:
- 模块自动运行在严格模式下
- 在模块的顶级作用域创建的变量,不会被自动添加到共享的全局作用域,它们只会在模块顶级作用域的内部存在;
- 模块顶级作用域的 this 值为 undefined
- 对于需要让模块外部代码访问的内容,模块必须导出它们
模块化主要解决两个问题,“命名冲突”、“文件依赖”
命名冲突
// a.js
var a = 1;
// b.js
var a = 2;
文件依赖
// b.js依赖a.js,标签的书写顺序必须是:
<script src='a.js' type='text/javascript'></script>
<script src='b.js' type='text/javascript'></script>
这样在多人开发的时候很难协调啊,令人头疼的问题
解决冲突、依赖(按需加载)
使用命名空间
这样的写法会暴露所有模块内的成员,内部状态可以被外部改写
let module = {
name: 'likang xie',
sayName() {
console.log(this.name);
}
}
立即执行函数+闭包
函数内部有自己独立的作用域,外部只能访问自己暴露的成员而不能读取内部的私有成员
let module = (function () {
let privateName = 'private'; // 私有变量
let privateFn = function () {}; // 私有函数
// 对外暴露的成员
return {
name: 'likang xie', // 公有属性
sayName() { // 公有方法
console.log(this.name);
}
}
})();
// 外部调用
module.sayName(); // likang xie
使用立即执行函数+类
同上
const People = (function () {
let privateName = 'private'; // 私有变量
let fn = function () {}; // 私有方法
return class People {
constructor () {
this.name = 'likang xie'; // 公有变量
}
// 公有方法
sayName() {
console.log(this.name);
}
}
})()
CommonJs(用于Node环境)
根据CommonJs规范,每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见,CommonJS规范加载模块是同步的,也就是说,加载完成才可以执行后面的操作,Node.js主要用于服务器编程,模块一般都是存在本地硬盘中,加载比较快,所以Node.js采用CommonJS规范。
定义模块
// module.js
let name = 'liakng xie';
let sayName = function () {
console.log(name);
};
module.exports = { name, sayName }
//或者
exports.sayName = sayName;
加载模块
// 通过 require 引入依赖
let module = require('./module.js');
module.sayName(); // likang xie
module.export跟exports的区别
- module.exports 方法还可以单独返回一个数据类型(String、Number、Object…),而 exports 只能返回一个 Object 对象
- 所有的 exports 对象最终都是通过 module.exports 传递执行,因此可以更确切地说,exports 是给 module.exports 添加属性和方法
exports.name = 'likang xie';
exports.age = 21;
console.log(module.exports); // 运行结果:{ name: 'likang xie', age: 21 }
同时用到module.export跟exports的时候
// 情况1
module.exports = { a: 1 }
exports.b = 2;
// { a:1 }
console.log(module.exports);
// 情况2
module.exports.a =1;
exports.b = 2;
// { a:1, b:2 }
console.log(module.exports);
AMD(用于浏览器环境)
AMD是RequireJS在推广过程中对模块定义的规范化产出,它是一个概念,RequireJS是对这个概念的实现,就好比JavaScript语言是对ECMAScript规范的实现。AMD是一个组织,RequireJS是在这个组织下自定义的一套脚本语言
RequireJS:是一个AMD框架,可以异步加载JS文件,按照模块加载方法,通过define()函数定义,第一个参数是一个数组,里面定义一些需要依赖的包,第二个参数是一个回调函数,通过变量来引用模块里面的方法,最后通过return来输出。
是一个依赖前置、异步定义的AMD框架(在参数里面引入js文件),在定义的同时如果需要用到别的模块,在最前面定义好即在参数数组里面进行引入,在回调里面加载
定义模块
define(['module'], function() {
let name = 'likang xie';
function sayName() {
console.log(name);
}
return { sayName }
})
使用模块
// 通过 require 引入依赖
require(['module'], function(mod) {
mod.sayName(); // likang xie
})
CMD(用于浏览器环境)
CMD与AMD用法很相似
-
是SeaJS在推广过程中对模块定义的规范化产出,是一个异步模块定义,是SeaJS的一个标准,SeaJS是CMD概念的一个实现,SeaJS是淘宝团队提供的一个模块开发的js框架.
-
通过define()定义,没有依赖前置,通过require加载jQuery插件,CMD是依赖就近,在什么地方使用到插件就在什么地方require该插件,即用即返,这是一个同步的概念
定义模块、使用模块
define(function(require, exports, module) {
// 通过 require 引入依赖
var otherModule = require('./otherModule');
// 通过 exports 对外提供接口
exports.myModule = function () {};
// 或者通过 module.exports 提供整个接口
module.exports = function () {};
})
ES6模块(用于浏览器环境)
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
ES6中的模块有以下特点:
- 模块自动运行在严格模式下
- 在模块的顶级作用域创建的变量,不会被自动添加到共享的全局作用域,它们只会在模块顶级作用域的内部存在;
- 模块顶级作用域的 this 值为 undefined
- 对于需要让模块外部代码访问的内容,模块必须导出它们
定义模块、输出变量
a.js
export default { name: 'likang xie' }
b.js
// 输出多个变量
export let name = 'likang xie';
export let sayName = () => console.log(name);
使用模块
import people from 'a.js';
console.log(people); // { name: 'likang xie' }
// 将所有获取到的变量存到people里
import * as people from 'b.js';
console.log(people); // 一个module对象 { name: 'likang xie', sayName: .... }
// 或者
import { name, sayName } from 'b.js';
浏览器原生支持ES6的模块?
具体请参考 在浏览器中使用javascript module(译)https://www.jianshu.com/p/f7db50cf956f
<script type="module">
import * as fn from './b.js';
</script>
Webpack
Webpack其实就是一个打包工具,他的思想就是一切皆模块,css是模块,js是模块,图片是模块。并且提供了一些列模块加载(各种-loader)来编译模块。官方推荐使用CommonJs规范,但是也支持CMD、AMD、ES6模块,所以基本通杀,准备写一篇入门Webpack的文章,所以这里就不过多说啦