1、模块化的由来:
1、IE6之前是没有js引擎的,js是由渲染引擎执行。页面中所有的js代码全部写在一个js文件中。
2、由于浏览器的交互越来越多,js逻辑也越来越复杂,导致js代码维护成本加高。
3、这时每一个页面对于一个js文件,这个js文件就处理这个页面的逻辑。结构与行为相分离。
4、如果页面中出现同样的逻辑代码,导致的问题js代码的重复度高。
5、把重复的代码抽离出来一个common.js。让每一个页面按需引入,这时某个一个页面只需要common.js的某一些方法,导致的问题是,代码冗余。
6、这时需要把common.js继续抽离分成多js文件按需引入,这样导致的问题
1、如果a.js中用到b.js中的变量C,这时标签的顺序恰恰是:
<script src="./a.js"></script>
<script src="./b.js"></script>
由于<script>标签有阻塞,b.js无法加载,导致a.js找不到变量C报ReferenceError
2、同一个页面所有的<script>共享一个全局作用域,导致的问题:
变量数据类型 全局污染 ——> 变量重名导致 ——> 变量覆盖
这个时模块化就开始闪亮登场了,模块化解决的问题:
1、污染全局
2、js的加载顺序;
1、解决污染全局,并且可以相互依赖
通过立即执行函数创建模块独立的作用域,通过对象的形式把变量包装好通过闭包return抛出去,或者把这个包装好的对象,挂载到window下:
var ModuleA=(function(){
var a=18
var b=20
return{
a:a,
b:b
}
}());
;(function(){
var a=18
var b=20
var moduleB={
a:a,
b:b
}
window.moduleB=moduleB
}());
如果moduleB需要用到moduleA中的变量,通过模块的注入的方式把ModuleA传给moduleB:
var ModuleA=(function(){
var a=18
var b=20
return{
a:a,
b:b
}
}());
;(function(ModuleA){
var a=18
var b=20
var module={
a:a,
b:b
}
window.module=module
}(ModuleA));
模块的名称是在全局的,按模块命名所以而且模块比变量少得太多了,如果出现被覆盖,查找模块一定比查找变量容易。模块注入的好处就是,让模块引入到局部作用域下,就不需要每一次都要到全局里面去找,提高效率。
插件化封装提供模块的复用性:通过配置项,让用户决定插件的行为,是模块化的一部分,。
以上解决模块化,都是基于js语言本身的特点。还不足以解决变量污染,加载顺序问题也依然。
二、模块化解决方案
1、AMD RequireJs Asynchronous Module Definition异步模块定义
1、define(moduleName,[module],factory)定义模块
2、require([module],callback)引入模块
math.js
define('moduleName',function() {
var add = function(x, y) {
return x + y;
};
var opt={
name:'qige',
age:18
}
return {
opt,
add
};
});
index.js
require.config({
paths: {
moduleName: 'js/math',
}
})
require(['moduleName'], function(moduleName) {
console.log(123);
console.log(moduleName);
});
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script src="./js/require.js" type="text/javascript" charset="utf-8"></script>
<script src="./js/index.js" type="text/javascript" charset="utf-8"></script>
</body>
</html>
2、CMD seajs common Module Definition 通用模块定义(阿里出品)。
1、define(function(require,exports,module){})定义模块;
2、require加载 exports导出/return module操作模块;
seajs.use([module路径],function(moduleA,moduleA,moduleC){})使用模块。
AMD和CMD的异同
默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<script>标签就会停下来,等到执行完脚本,再继续向下渲染。如果js体积过大,就会导致浏览器“堵塞”。为了解决这个问题,浏览器允许js脚本异步加载,给<script>标签添加async或者defer属性。
1、async 表示js脚本加载完成之后,渲染引擎停止渲染,立即执行当前脚本。
2、defer 只是开启异步脚本加载,直到页面全部渲染完成之后,再按照标签的顺序执行脚本。
相同之处:
1、所有的文件都加载完成之后,回调函数才执行。
2、加载的本质是通过<script src="./modulePath.js" async></script>
异同之处
CMD:是依赖后置 就近原则,用到的时候才加载,按需加载。
AMD:是依赖前置,不管用不用到,只要出现就全部下载回来才执行。
3、Common.js
Node.js的诞生带来了真正的模块化开发体验:不依赖html页面实现js之间模块依赖。
1、require('.....) 引入模块。
2、module.exports 导出模块exports。
3、commonJs是同步加载文件,主要是运行在Node环境。
//commonJs.js
var obj = { count: 1 };
var qige = 123;
function print() {
qige=456;
obj.count++;
console.log('commonJs::',qige);//456
}
module.exports = {
print,
obj,
qige:qige
}
//index.js
const common = require('./commonJs.js')
console.log('index::',common.qige);//index::123
common.print();
console.log('index::',common.qige);//index::123
setTimeout(()=>{
const common2 = require('./commonJs.js')
console.log(common===common2);//true
console.log(common);
})
common打印如下:
由于修改commonJs文件内部的原始值类型变量qige=456和内部对象obj.count++之后,再打印响已输出的外部的common.qige依然是123,外部的common.obj.count的值为2。
得出结论1: 引入的是模块exports对象的浅拷贝。
由于common===common2为true;
得出结论2 commonJs有缓存机制;
由于common2是在定时器中加载的
得出结论3 commonJs是运行时加载;
4、官方ES6模块化 ES6 在语言标准的层面上,实现了模块化。 ES6中一个文件就是一个独立的模块,模块内所有的变量对其他模块是不可见的,如果当前模块的变量需要对外可见,需要export导出,外部文件需要用到该模块导出的变量需要import导入。
1、export导出对外的变量;
2、import导入其他模块提供的变量;
1、export 变量声明/函数声明/l类的名称
// module_a.js
export var moduleName='qige';
export function gets() {
return 'hello world'
}
export class Good{
constructor(arg) {
console.log(arguments);
}
}
import * as moduleA from "./module_a.js"
import {moduleName,gets,Good} from "./module_a.js"
2、export{变量1、变量2、变量3},花括号里面的变量名必须模块中的全局变量,不能是局部变量
// module_a.js
export{moduleName,gets,Good}
//index.js
import * as moduleA from "./module_a.js"
import {moduleName,gets,Good} from "./module_a.js"
3、export default 变量名/函数声明/类声明/{变量1、变量2、变量3},一个模块中只能有一个default
// module_a.js
function gets() {
return 'hello world'
}
var moduleName='qige'
var firstName = 'foo',
lastName = 'bar'
export { firstName, lastName }
export default class Good{
constructor(arg) {
console.log(arguments);
}
}
//index.js
import Good, * as moduleA from "./module_a.js"
moduleA是一个对象,包含module_a.js模块导出的变量。
注意:export/import只能在模块的全局作用域中。导出/导入都可以通过as 关键字为变量重命名。
//ES6.js
var obj = { count: 1 };
var qige = 123;
function print() {
qige = 456;
obj.count++;
console.log('ES6::', qige); //456
}
export {print,obj,qige}
//index.js
import * as es6 from './ES6.js'
console.log('index::',es6.qige);//index::123
es6.print();
console.log(es6);
console.log('index::',es6.qige);//index::123
由于内部修改原始值类型变量,外部获取到也修改了,
得出结论1:ES6 模块输出的是值的引用
三、ES6模块与commonJs模块的区别
1、 commonJs模块是在运行是加载,ES6是在编译时加载;
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。
ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
2、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用 ; CommonJSa加载完就缓存起来。
JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
CommonJS 模块导入的对象的属性可以随意修改。由于时副本它的修改对内部不影响。
ES6 模块导出的时对外接口,读外部提供只读权限,由于外部拿到的时内部的引用,如果A模块和B模块都引用了该模块,如果允许修改导致数据混乱。
4、CommonJS 模块的require是同步加载模块,ES6 模块的import命令是异步加载
ES6导入:commonJs模块
//commonJs.js
exports.common = {print,obj,qige}
//index.js
import {common} from './commonJs.js'
commonJs导入ES6模块
//ES6.js
export {print,obj,qige}
//index.js
const es6 = require('./ES6.js')