前端模块化
这里复习了一个知识点,就是在一个html页面中有两个相同的事件监听函数时,后面那个会覆盖掉前面那个,就像是变量重新赋值一样。
<script src="js/main.js"></script>
<script src="js/xiaoming.js"></script>
<script src="js/xiaohon.js"></script>
window.onload = function(){
var name = '小明';
console.log(name);
};
window.onload = function(){
var name = '小红';
console.log(name);
};
最终控制台打印的是小红
一、为什么前端需要模块化开发?
我们首先需要知道的是,在HTML文件中引入js文件,就相当于是将这个js文件拷贝到了引入的标签的地方。
<script src="js/main.js"></script>
<script src="js/xiaoming.js"></script>
<script src="js/xiaohon.js"></script>
就相当于这样
<script src="js/main.js"></script>
var name = '小明';
console.log(name);
console.log(name);
var name = '小红';
console.log(name);
你会发现若是团队开发的时候很容易就出现了全局变量命名重复的问题,比如说这个案例,小明原来定义name是自己的名字,然后小红也定义了一个name,并且改成了自己的名字。后来小明在做一些操作的时候,使用到这个name,结果完全没有达到自己想要的效果,这就尴尬了。
在js中甚至你变量命名重复都不会报错,只是在编译器中有个warning而已
<script src="js/main.js"></script>
<script src="js/xiaoming.js"></script>
<script src="js/xiaohon.js"></script>
<script src="js/xiaoming1.js"></script>
if("小明" === name){
console.log("小明是天才,哈哈哈哈");
}
由于name被小红改成自己的名字了,小明这段话不可能打印的出来了。所以,全局变量命名重复是个大问题。由于es5中只有全局作用域和函数作用域,所以这个问题的解决方式就只能是使用匿名函数
(function(){
var name = '小明';
console.log(name);
})();
就像是这样,但是这又会出现问题了,就是小明写的东西就不能重复利用了,就像是时间格式化的函数toLocalDate这样常用的函数,小明在这个闭包中写了,那到了别的地方想用了,却用不了,难道复制粘贴一份?那成本太高了。
所以,上面不是出现全局变量命名冲突就是会出现代码无法复用的问题,这就是为什么需要使用模块化的概念,就像是java中,你定义了一个工具类,那之后就不需要再重复定义了,直接import就可以了。
二、自己实现模块化
在闭包内部可以创建一个对象,将需要在别的地方使用的东西给存入这个对象中,然后在匿名函数外面创建一个模块对象去接收这个导出的对象,由于模块对象是全局作用域的,那么在别的地方就可以使用了。
var modelA = (function(){
var obj = {};
var name = '小明';
console.log(name);
obj.toLocalDate = function (){
var date = new Date();
return date.getFullYear()+"/"+(date.getMonth()+1)+"/"+date.getDate()+" "+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
};
console.log(obj.toLocalDate());
obj.name = name;
return obj;
})();
if("小明" === modelA.name){
console.log("小明是天才,哈哈哈哈");
}
console.log(modelA.toLocalDate());
这时候在别的地方就能使用之前写的东西了,这就是模块化思想,解决了全局变量命名重复和代码复用
三、使用定义好的模块化规范
前端发展到了现在,已经有了很多模块化的规范了,之后使用模块化都是按照别人的规范来进行模块化开发。现在常见的模块化规范有:CommonJS 、AMD、CMD、和ES6的Modules,其中CommonJS和ES6的模块化比较常用。其实,不管规范怎么定义的,他们都有两个核心的点是不变的,就是导入和导出,就像是我们自己实现的模块化一样,你得先把一个闭包的东西给导出,然后才能在别的地方导入使用吧。下面就来学习一下CommonJS和ES6的模块化的导入和导出语法。
1. CommonJS,大名鼎鼎的Node.js就是实现了CommonJS的规范
导出
var name = '小明';
console.log(name);
function toLocalDate(){
var date = new Date();
return date.getFullYear()+"/"+(date.getMonth()+1)+"/"+date.getDate()+" "+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
}
module.exports = {
//对象字面量的增强写法
name,
toLocalDate
};
你会发现,这里没有使用闭包,因为在模块化规范中,一个JS就是一个模块了,它会有自己的作用域。
导入
//CommonJS的导入方法
//{name,toLocalDate}这叫做对象的解构
//等同于 分别定义了两个变量name,toLocalDate
//然后把require返回的对象中的name和toLocalDate分别赋值给他们
let {name,toLocalDate} = require('./xiaoming.js');
if("小明" === name){
console.log("小明是天才,哈哈哈哈");
}
console.log(toLocalDate());
CommonJS直接肯定是使用不了的,因为需要有实现了它规范的东西给你的代码做支撑,不然连exports和require关键字都找不到。
2. ES6的模块化
使用了模块化之后,每一个JS文件就是一个独立的模块,即使不使用闭包也不会出现全局变量命名冲突的问题
<script src="js/xiaoming.js" type="module"></script>
<script src="js/xiaohon.js" type="module"></script>
<script src="js/xiaoming1.js" type="module"></script>
这样就是声明了这些JS文件是通过模块化的思想开发的。
xiaoming.js
var name = '小明';
console.log(name);
function toLocalDate(){
var date = new Date();
return date.getFullYear()+"/"+(date.getMonth()+1)+"/"+date.getDate()+" "+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
}
xiaoming1.js
console.log(toLocalDate());
由于使用了模块化开发的规范,所以在别的地方使用就自己报错了,因为在这个模块中还没有定义,很明显命名冲突的问题解决了。
Uncaught ReferenceError: toLocalDate is not defined
ES模块化的导入和导出
导出
//ES6导出
var name = '小明';
//1.直接导出
export var flag = true;
console.log(name);
function toLocalDate(){
var date = new Date();
return date.getFullYear()+"/"+(date.getMonth()+1)+"/"+date.getDate()+" "+date.getHours()+":"+date.getMinutes()+":"+date.getSeconds();
}
//以前定义类也是使用function,ES6后直接使用class定义类了,还能定义构造函数
export class Person{
constructor(name) {
this.name = name;
}
sayName(){
console.log(this.name);
}
}
//2.某些时候,我们只是导出一个功能,而且想要由使用者来为他命名,那就得使用export default来进行导出,
//每个模块中只能有一个export default,若是有多个的话,导入的时候就不知道哪个是哪个了,
// 因为都是没有名字的
// export default function (person) {
// person.sayName();
// }
//注意:需要先定义这个变量才能export default,不然会报错的,不能写在一行
const height = 1.88;
export default height;
//3.导出一个对象
export {
name,toLocalDate
}
导出有三种语法,一种是定义的时候直接在前面export导出,或者在下面export导出一个对象,还可以export default,让别人能够在导入的时候自定义名字,不过每个模块中只能export default一个东西,不然别人导入的时候就不知道导入的是什么了。
导入
//ES6导入第一种方式:import {} from "path";
//若是导入的东西很多的话,这样写就很麻烦,而且
// 导入的东西还可能和自己定义的东西重名又冲突了,
// 所以还有一种方式,
//import * as 自定义对象名 from "path";就是把
// path这个文件中所有导出的东西放到你自定义的对象中
import {name,toLocalDate,flag,Person} from "./xiaoming.js";
if("小明" === name){
console.log("小明是天才,哈哈哈哈");
}
console.log(toLocalDate());
if(flag){
console.log("小明是傻子,哈哈哈哈");
}
let xiaoming = new Person("小明");
xiaoming.sayName();
// import sayName from "./xiaoming.js";
// sayName(xiaoming);
import h from "./xiaoming.js";
console.log(h);
//*******************************************************
import * as xmModel from "./xiaoming.js";
if("小明" === xmModel.name){
console.log("小明是天才,哈哈哈哈");
}
console.log(xmModel.toLocalDate());
if(flag){
console.log("小明是傻子,哈哈哈哈");
}
let xiaoming1 = new xmModel.Person("小明");
xiaoming1.sayName();
//export default就是将这个变量或者是函数什么的放到xmModel里面的一个default变量中
console.log(xmModel.default);
在style标签中导入文件的时候语法这样
@import + 文件路径
在使用路由懒加载的时候这样导入
import + (+文件路径+),这个路径要使用括号包起来