如何理解前端模块化
前端模块化就是复杂的文件编程一个一个独立的模块,比如js文件等等,分成独立的模块有利于重用和维护,这样会引来模块之间相互依赖的问题,变量名重复的问题。所以有了commonJS规范,AMD,CMD规范等等。
前端模块化雏形
1.模块化的输出,但是你在html 中进行加载的时候还是要保证按照顺序来
2.每个js文件中自己定义一个作用域,这样就能够防止变量名重复,导致出现问题。
//test.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script type="text/javascript" src="aaa.js"></script>
<script type="text/javascript" src="mmm.js"></script>
</body>
</html>
//aaa.js
var moduleA=(function(){
//导出的对象
var obj={};
//小明
var name="小明"
var age=22
function sum(num1,num2)
{
return num1+num2;
}
var flag=true;
if(flag){console.log(sum(10,20))}
obj.flag=flag;
obj.sum=sum;
return obj;
})()
//mmm.js
(function(){
//1.想使用flag
if(moduleA.flag){console.log("小明是天才,哈哈哈哈")}
//2.使用sum函数
console.log(moduleA.sum(40,50))
})()
commonJS规范(nodejs)
//1个js就是一个模块,不需要再写一个作用域了
//test.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script type="text/javascript" src="aaa.js"></script>
<script type="text/javascript" src="mmm.js"></script>
</body>
</html>
//aaa.js
var name="小明"
var age=22
function sum(num1,num2){
return num1+num2
}
var flag=true;
if(flag){
console.log(sum(10,20))
}
module.exports={
flag:flag,
sum:sum
}
//mmm.js
var {flag,sum}=require("./aaa.js");
sum(20,30)
ES6的模块化
//test.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<script type="text/javascript" src="aaa.js" type="module"></script>
<script type="text/javascript" src="mmm.js" type="module"></script>
</body>
</html>
//module的意思就是将js模块化,这样就能够防止变量命名的冲突
//aaa.js
var name="小明"
var age=18
var flag=true
function sum(num1,num2)
{
return num1+num2
}
if (flag)
{
console.log(sum(20,30));
}
//1.导出方式一:
export{
flag,sum
}
//2.导出方式二:
export var num1=1000;
export var height=1.88;
//3.导出方式三函数/类:
export function mul(num1,num2){
return num1+num2;
}
export class Person{
run(){console.log("在奔跑")}
}
//4.方式四export default
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名,一个js文件只能有一个export default
这个时候就可以使用export default
const address="北京市"
export default address
export default function(argument){
console.log(argument);
}
//mmm.js
import{flag,sum}from "./aaa.js";
方式二:import {num1,height} from "./aaa.js";
方式三:import {mul,Person} from "./aaa.js";
方式四:import addr from "./aaa.js"
if(flag)
{
console.log("小明是天才,哈哈哈")
}
sum(20,30)
方式二:console.log(num1);
console.log(height);
方式三:
mul(30,40);
const p=new Person();
p.run();//在奔跑
方式四:
import addr from "./aaa.js";//名字可以随便起了,就是默认的那一个,也不需要加花括号了。
console.log(addr);
//如果导出的是个函数
addr("您好啊!");
//统一导出非常多的情况下
import {flag,num,num1,height,Person,mul,sum} from "./aaa.js";
import * as aaa from "./aaa.js"
console.log(aaa.flag);//拿变量的话
1.defer与async的区别
前者要等到整个页面正常渲染结束才会执行,而后者一旦下载完成后,渲染引擎就会中断渲染,执行这个脚本以后继续渲染。如果有多个defer脚本,则会按照它们在页面出现的顺序执行,而多个async脚本不能保证加载顺序的。
2.加载规则
浏览器加载ES6模块时也使用了
3.ES6模块与CommonJS模块的差异
1.CommonJS模块输出的是一个值的复制,ES6模块输出的是一个值的引用
CommonJS模块输出的是一个值的复制,一旦这个值输出以后,模块内部怎么变化就影响不到这个值。
//lib.js
var counter=3;
function inCounter(){counter++;}
module.exports={counter:counter,inCounter:inCounter,}
//main.js
var mod=require("./lib")
console.log(mod.counter);//3
mod.inCounter()
console.log(mod.counter);//3
这时因为mod.counter是原始类型的值会被缓存
除非写成一个函数,得到内部变动的值
module.exports={get counter(){return counter},inCounter:inCounter,}
//es6模块
//lib.js
export let counter=3;
export function inCounter(){counter++;}
//main.js
import {counter,inCounter}from './lib'
console.log(counter)//3
inCounter();
console.log(counter)//4
2.CommonJS模块是运行时加载,Es6模块是编译时输出接口。
CommonJS加载的是一个对象(module.exports属性),该对象只有在脚本运行结束时才会生成。Es6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
4.CommonJS模块的加载原理
CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存生成一个对象
{
id:"…",
exports:(…),
loaded:true,
…
}
上面的代码就是node内部加载模块后生成的一个对象。该对象的id属性时模块名称,exports属性时模块输出的各个接口,loaded属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有别的属性,这里省略了。
以后需要用到这个模块时就会到exports属性上取值。即使再次执行require命令,也不会在执行该模块,而是直接从缓存中取值,除非手动清除系统缓存。
4.ES6模块的加载原理
js引擎对脚本静态分析的时候,遇到模块加载命令import 就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用到被加载的模块中取值。原始值变了,import加载的值也会变,es6模块是动态引用,并且不会缓存值。
5.CommonJS和AMD和CMD
CommonJS开始于服务器端的模块化,同步定义的模块化。每个模块都是一个单独的作用域,模块输出,modules.exports,模块的加载require引入模块。
AMD是异步模块定义。由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD是RequireJS在推广过程中对模块定义的规范化的产出。
RequireJS主要解决两个问题
1.多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器
2.js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
RequireJs也采用require()语句加载模块,但是不同于CommonJS,它要求两个参数:
第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback,则是加载成功之后的回调函数。math.add()与math模块加载不是同步的,浏览器不会发生假死。
require([module], callback);
require([increment'], function (increment) {
increment.add(1);
});
define()函数
RequireJS定义了一个函数 define,它是全局变量,用来定义模块:
define(id?, dependencies?, factory);
参数说明:
id:指定义中模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
依赖dependencies:是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。
依赖参数是可选的,如果忽略此参数,它应该默认为[“require”, “exports”, “module”]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。
// 定义模块 myModule.js
define(['dependency'], function(){
var name = 'Byron';
function printName(){
console.log(name);
}
return {
printName: printName
};
});
// 加载模块
require(['myModule'], function (my){
my.printName();
});
CMD是规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现Sea.js,Sea.js要解决的问题和require.js一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同。
在CMD规范中,一个模块就是一个文件。代码的书写格式如下:
define(function(require, exports, module) {
// 模块代码
});
require是可以把其他模块导入进来的一个参数;而exports是可以把模块内的一些属性和方法导出的;module是一个对象,上面存储了与当前模块相关联的一些属性和方法。
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此处略去 100 行
var b = require('./b') // 依赖可以就近书写
b.doSomething()
// ...
})
// AMD 默认推荐的是
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
a.doSomething()
// 此处略去 100 行
b.doSomething()
...
})
AMD是依赖关系前置,在定义模块的时候就要声明其依赖的模块。
CMD是按需加载依赖就近,只有在用到某个模块的时候在去require
AMD/CMD区别,虽然都是并行加载js文件,但还是有所区别,AMD是预加载,在并行加载js文件同时,还会解析执行该模块(因为还需要执行,所以在加载某个模块前,这个模块的依赖模块需要先加载完成);而CMD是懒加载,虽然会一开始就并行加载js文件,但是不会执行,而是在需要的时候才执行。