JavaScript模块化

什么是模块化

什么是模块化,说简单那点,其实就是将程序依据一定的规则拆分变成了多个文件。拆分出来的每一个文件都是一个模块,每个模块里面的内容都是私有的,即各个模块间都是隔离的。同时也可以通过一些手段将各个模块的内容导出

为什么进行模块化

1.全局污染问题

比如说我们在html里面导入js文件的时候后面的文件会覆盖前面的文件。

2.依赖混乱问题

我们在html里面导入js文件的时候,因为依赖调用问题,一般顺序都不得出错。

3.数据安全问题

我们在一个html文件里面引入一个js文件,里面的方法变量我们都可以在控制台访问,

模块化规范

说完了两个什么,接下来就该规范了,随着NodeJs的推出,目前的主流两种规范分别是CommonJSES6模块化。其中前者主要适用于服务端,后者更适用于浏览器端。

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>

额外知识

CommonJSES6规范化里面有两个显著的差别,后者是每个变量共享一个内存块,但是前者不是。我说的很简便明了了,直接看代码吧:

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不允许变量发生改变了。

最后,上面的CommonJSES6 这两种模块化是当今的主流,至于其他的CMDAMD稍做一下了解就可以了。

相关推荐
Dread_lxy12 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
JerryXZR2 小时前
JavaScript核心编程 - 原型链 作用域 与 执行上下文
开发语言·javascript·原型模式
帅帅哥的兜兜2 小时前
CSS:导航栏三角箭头
javascript·css3
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript
peachSoda72 小时前
随手记:简单实现纯前端文件导出(XLSX)
前端·javascript·vue.js