什么是模块化
什么是模块化,说简单那点,其实就是将程序依据一定的规则拆分变成了多个文件
。拆分出来的每一个文件都是一个模块
,每个模块里面的内容都是私有的,即各个模块间都是隔离的。同时也可以通过一些手段
将各个模块的内容导出
。
为什么进行模块化
1.全局污染问题
比如说我们在html
里面导入js
文件的时候后面的文件会覆盖前面的文件。
2.依赖混乱问题
我们在html
里面导入js
文件的时候,因为依赖调用问题,一般顺序都不得出错。
3.数据安全问题
我们在一个html
文件里面引入一个js
文件,里面的方法
和变量
我们都可以在控制台访问,
模块化规范
说完了两个什么
,接下来就该规范了,随着NodeJs
的推出,目前的主流两种规范分别是CommonJS
和ES6
模块化。其中前者主要适用于服务端,后者更适用于浏览器端。
CommonJS规范
先来个student.js
文件:
javascript
const name = '小鸡'
const slogan = '为崛起而独树'
function getphone() {
return '13523766168'
}
function getHobby() {
return ['吸烟','喝酒','装杯']
}
再来个school.js
文件:
javascript
const name = '大鸡'
const motto = '先成人后成才'
function getphone() {
return '01853-357668'
}
最后我们写个总代码块index.js
文件:
javascript
//可以使用require()方式来导入模块
const school = require("./school.js")
const student = require("./student.js")
console.log(school);
console.log(student);
//控制台打印的结果是{}
但是大家运行后就会发现控制台打印的是个空对象,里面啥也没有。那么原因是什么呢?是因为在每个js文件里面都有一个空对象,而this,exports,module.exports都是指向了这个空对象。我们并没有向这三个对象赋值,因此得到的是空对象。
不信大家可以打印查看:
javascript
console.log(exports === module.exports) // true
console.log(exports === this) // true
然后再比如我们添加属性的话,因为指向了同一个对象,所以属性都可以得到,代码如下:
javascript
exports.name = 'xixi'; //添加属性
console.log(module.exports.name); //xixi
console.log(this.name); //xixi
console.log(exports.name); //xixi
console.log(exports === module.exports); //true
console.log(exports === this); //true
导出
因为最后导出的对象实际上是module.exports
,因此如果重新赋值就不管exports
的事了。直接看代码理解:
javascript
const nam = '小鸡'
//重新赋值
exports = {a:nam}
module.exports = {slogan:slogan}
//打印
console.log(exports); //{ a: '小鸡' }
console.log(module.exports);//{ slogan: '为崛起而独树' }
console.log(exports === module.exports);//false
导出的话其实主要就exports和module.exports两种方式。
导入
导入的话就更简单了,先说最简单的,那就是require()
方式,代码如下:
javascript
const school = require("./school.js")
const student = require("./student.js")
console.log(school); //{ name: '大鸡' }
console.log(student); //{ name: '小鸡' }
这种方式有效避免了全局污染
的问题,那么又有新的问题,假如是下面这样,该怎么办:
javascript
const {name} = require("./school.js")
const {name} = require("./student.js")
console.log(name);
console.log(name);
上面的两个模块里面都有name
属性,最后显然会报错,Identifier 'name' has already been declared
,解决方法很简单,如下所示:
javascript
const {name,getphone} = require("./school.js")
const {name:studentName,getphone:getph} = require("./student.js") //将getPhone方法的返回值赋予给了getph
console.log(name);
console.log(studentName);
console.log(getphone());
console.log(getph());
这样就有效避免了全局污染
的问题。
以上就为CommonJS 的全部导入和导出了,但是我们知道,CommonJS 主要用于服务端,难道不能用于浏览端嘛,其实是可以的,虽然默认不支持,但是我们可以安装 Browserify来解决。
第一步:安装 Browserify
npm install -g browserify
第二步:使用 Browserify打包入口文件index.js
browserify index.js -o bundle.js
第三步:在 HTML 文件中引入打包后的文件
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript" src="./bundle.js"></script> //将src里面的内容由./index.js改变成了./bundle.js
</body>
</html>
ES6规范
上面的CommonJS
是民间
的,那么接下来的ES6模块化规范
可就是纯正的官方血统了,当今的主流前端框架,都是按照这个ES6
模块规范化去写的,废话不多说,直接看代码:
模块student.js如下:
javascript
export const slogan = '为崛起而独树'
export const name = '小鸡'
export function getphone() {
return '13523766168'
}
export function getHobby() {
return ['吸烟','喝酒','装杯']
}
模块school.js如下:
javascript
export const name = '大鸡'
export const motto = '先成人后成才'
export function getphone() {
return '01853-357668'
}
可以看出,上面的模块数据前面都有关键字export,意味着将指定的数据导出。
入口文件index.js如下:
javascript
import * as school from './school.js'
import * as student from './student.js'
console.log(student);
console.log(school);
index.html文件如下所示:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module" src="./index.js"></script> //前面的type必须要改。
</body>
</html>
拓展
要想使得ES6规范 适用于服务端,我们需要在目标目录下创建一个package.json文件,且里面得写上这样一组数据:
javascript{ "type": "module" }
这样子入口文件index.js
就可以在服务端运行了。
导出数据
1.分别导出
分别导入就是我刚刚说的那些,如下所示:
javascript
export const name = '大鸡'
export const motto = '先成人后成才'
export function getphone() {
return '01853-357668'
}
2.全部导出
入口文件index.js如下:
javascript
import {name,getphone,motto} from './school.js'
console.log(name);
console.log(motto);
console.log(getphone());
模块文件school.js如下:
javascript
const name = '大鸡鸡'
const motto = '先成人后成才'
function getphone() {
return '01853-357668'
}
export {name,motto,getphone}
3.默认导出
入口文件index.js如下:
javascript
import school from './school.js'
console.log(school);
模块文件school.js如下:
javascript
const name = '大鸡鸡'
const motto = '先成人后成才'
function getphone() {
return '01853-357668'
}
export default {name,motto,getphone}
另外哈,上面三种导出方式可以混合使用。
导入数据
1.全部导入
javascript
import * as school from './school.js'
console.log(school);
console.log(school.name);
console.log(school.getphone());
这个全部导入方法是万能的,所有导出方式均可以使用。
2.命名导入(全部导出和分别导出)
javascript
import {name,getphone,motto} from './school.js'
console.log(name);
console.log(motto);
console.log(getphone());
3.默认导入
javascript
import school from './school.js'
console.log(school);
console.log(school.name);
console.log(school.getphone());
这个是适用于默认导出方法的。
4.命名导入和默认导入可以混合使用
javascript
import school,{name,motto} from './school.js'
console.log(school);
console.log(name);
console.log(motto);
console.log(school.getphone());
5.动态导入
student.js
模块化文件如下:
javascript
export const slogan = '为崛起而独树'
export const name = '小鸡鸡'
export function getphone() {
return '13523766168'
}
export function getHobby() {
return ['吸烟','喝酒','装杯']
}
index.js
入口文件如下:
javascript
const button = document.getElementById('btm');
// async关键字声明是一个返回promise的异步函数,且里面可以使用await关键字。
button.onclick = async() => {
// 使用await关键字必须用于promise函数,作用就是当作用的promise函数执行完才可以向下执行.
const result = await import('./student.js')
console.log(result);
}
index.html
文件如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module" src="./index.js"></script>
<button id="btm">导入数据</button>
</body>
</html>
额外知识
在CommonJS
和ES6
规范化里面有两个显著的差别,后者是每个变量共享一个内存块,但是前者不是。我说的很简便明了了,直接看代码吧:
CommonJS
的代码如下:
模块代码:
javascript
let name = 1
function increment() {
name += 1
return '完成了'
}
module.exports = {name,increment}
入口文件代码:
javascript
const {name,increment} = require('./school.js');
console.log(name);// 1
increment();
increment();
console.log(name);// 1
可以看得出来打印结果都是为1,这是因为我们直接将变量拿过来了,后续的执行函数并没有对我们拿来的变量发生改变,那么如果是ES6规范都不一样了
。
模块代码:
javascript
let name = 1
function increment() {
name += 1
return '完成了'
}
export {name,increment}
入口文件代码:
javascript
import {name,increment} from './school.js';
console.log(name);// 1
increment();
increment();
console.log(name);// 3
可以看到结果不一样,这就是因为在ES6规范里面
的相同调用变量都指向了同一个内存块。那么,就没法解决吗?答案是可以的,我们可以将数据类型由let
更改为const
就完全可以杜绝这种情形了,因为const
不允许变量发生改变了。
最后,上面的CommonJS 和ES6 这两种模块化是当今的主流,至于其他的CMD 和AMD稍做一下了解就可以了。