代码 github 链接 https://github.com/Flower-F/front-end-basis/tree/main/JS%E6%A8%A1%E5%9D%97%E5%8C%96

本文参考视频链接:https://www.bilibili.com/video/BV18s411E7Tj?p=1

什么是模块化

将一个复杂的程序依据一定的规则封装成几个块(或者说几个文件),并进行组合在一起。模块内部的数据与实现是私有的,只是向外部暴露一些接口来与其他模块通信。

为什么要模块化

为什么不能把所有js代码写在一个文件呢

1.代码耦合度太高,功能点不明确,不方便后期维护。

2.容易污染全局环境。

模块化出现的原因

1.app的普及度越来越高

2.随着项目业务拓展,编码的复杂度越来越高

3.对JS代码高耦合低内聚的要求逐渐出现

4.在部署环节需要优化代码

模块化的好处

1.避免命名冲突(减少命名空间污染)

2.代码可以更好地分离,实现按需加载

3.代码具有更高的复用性

4.代码具有更高的维护性

模块化进化史

1.最早:几乎写在一个文件

function foo(){
	//...
}
function bar(){
    //...
}

缺点:全局容易被污染,很容易造成命名冲突

2.简单封装:namespace模式

var Namespace = {
    build: function (data) {
        this.private_data = data;
        this.foo = function () {
            console.log(this.private_data);
        }
    },
}

var instance = new Namespace.build('hello world');

instance.foo(); // hello world
console.log(instance.private_data); // hello world

优点:减少了全局的变量数目,避免了命名冲突

缺点:对象数据可以随便修改,不安全

3.匿名函数自调用(IIFE)

var Module = (function () {
    var private_data = "hello world";
    var foo = function () {
        console.log(private_data);
    }
    return { foo }
})()

Module.foo(); // hello world
console.log(Module.private_data); // undefined

优点:相对安全

4.IIFE 中引入依赖

var Module=(function(){
    var _$body=$('body');
    var foo=function(){
        console.log(_$body);
    }
    return { foo }
})(jQuery)

Module.foo(); // jQuery.fn.init

这就是现代模块实现的基石

模块化带来的问题

然而这里还有一个很现实的问题产生了

一个残酷的现实

script(src="jQuery.js")
script(src="jhash.js")
script(src="fastClick.js")
script(src="iScroll.js")
script(src="underscore.js")
script(src="handlebar.js")
script(src="datacenter.js")
script(src="deferred.js")
script(src="util/wxbridge.js")
script(src="util/login.js")
script(src="util/base.js")
script(src="util/city.js")
script(src="util/date.js")
script(src="util/cookie.js")
script(src="app.js")

模块化导致我们引入的文件变得很多很复杂。

各个文件的引入顺序非常重要,比如通常我们把jQuery的引入放在第一位。

同时,多个<script>标签带来的问题还有:

1.维护困难

2.依赖模糊

3.请求过多

只用<script>标签实现模块化引入,越来越难以满足前端发展的需求,模块规范化应运而生。

CommonJS

每个文件都可以当作一个模块

在服务器端:模块在运行时同步加载

在浏览器端:模块需要提前编译打包处理

基本语法

暴露模块:

module.exports = value

这样的exports后面出现exports会覆盖前面的

exports.xxx = value

不断地往对象上绑定新的方法/对象,后面的不会覆盖前面的

暴露的本质:就是暴露exports对象

引入模块:

require(xxx)

对于第三方模块,xxx为模块名

对于自定义模块,xxx为模块文件路径

使用示例

服务器端实现

即Node.js

创建一个文件夹module,内含module1.js,module2.js,module3.js。

module1.js

module.exports = {
    msg: 'module1',
    foo() {
        console.log(this.msg)
    }
}

module2.js

module.exports = function () {
    console.log('module2');
}

module3.js

exports.foo = function () {
    console.log('foo module3');
}

exports.bar = function () {
    console.log('bar module3');
}

exports.arr = [1, 2, 3, 4, 5, 6, 6, 3, 2, 4];

module文件夹同级下创建文件 app.js

const uniq = require('uniq'); // $ npm i uniq

const module1 = require("./modules/module1");
const module2 = require("./modules/module2");
const module3 = require("./modules/module3");

module1.foo();
module2();
module3.foo();
module3.bar();

let res = uniq(module3.arr);
console.log(res);

/**
 * module1
 * module2
 * foo module3
 * bar module3
 * [ 1, 2, 3, 4, 5, 6 ]
 */
浏览器端实现

Browserify是CommonJS的浏览器端的打包工具

http://browserify.org

$ npm i browserify -g
$ npm i browserify -D

打包命令

$ browserify js/src/app.js -o js/dist/bundle.js

然后引入 bundle.js 就可以在浏览器看到执行结果了

AMD

Asynchronous Module Definition(异步模块定义)

AMD专门用于浏览器端,模块的加载是异步的

基本语法

暴露模块的定义:

定义没有依赖的模块:

define(function(){
	return module;
})

定义有依赖的模块

// 专业名词叫做依赖注入
define(['module1','module2'],function(m1,m2){
	return module;
})

引入使用模块

// 非主模块require
require(['module1','module2'],function(m1,m2){
	/* 使用m1,m2 */
})

// 主模块requirejs
requirejs(['module1','module2'],function(m1,m2){
	/* 使用m1,m2的代码 */
})

Require.js

AMD 的实现需要引入 Require.js

下载地址

http://www.requirejs.org

Require.js是一个Javascript的module loader

使用示例

没有AMD的代码

在没有AMD规范的情况下,我们的实现可能会像下面这个样子。因为我们没有别的方法暴露模块,只能通过window来实现。

js文件夹下,有两个文件 alerter.js 和 dataDevice.js

// alerter.js
// 定义一个有依赖的模块
(function (window, dataService) {
    let msg = "alerter";
    function showMessage() {
        console.log(msg, ' ', dataService.getName());
    }
    window.alerter = { showMessage };
})(window, dataService)
// dataDevice.js
// 定义一个没有依赖的模块
(function (window) {
    let name = 'dataService';
    function getName() {
        return name;
    }
    window.dataService = { getName };
})(window)

js文件夹同级目录下

// app.js
(function (alerter) {
    alerter.showMessage();
})(alerter)
<!-- index.html -->
<!-- 此处的引用顺序是关键 -->
<script src="./js/dataService.js"></script>
<script src="./js/alerter.js"></script>
<script src="./app.js"></script>

可见需要发送3次请求,效率较慢。

有AMD的代码

https://requirejs.org/docs/api.html

由上方网址我们可以得到两个重要的内容的使用方法:

1.入口文件

<script data-main="js/app.js" src="js/require.js"></script>

2.配置路径

requirejs.config({
    baseUrl: 'js/lib',
    paths: {
        app: '../app'
    }
});

使用RequireJS重新实现上面的案例

// alerter.js
// 定义有依赖的模块
define(['dataService'], function (dataService) {
    let msg = 'alerter';
    function showMessage() {
        console.log(msg, ' ' + dataService.getName())
    }

    return { showMessage };
});
// dataService.js
// 定义没有依赖的模块
define(function () {
    let name = 'dataService';
    function getName() {
        return name;
    }

    //暴露模块
    return { getName };
});
// app.js
// 使用 IIFE 确保安全性
(function () {

    //配置文件
    requirejs.config({
        // baseUrl: 'js/lib',
        paths: {
            // 配置路径
            // 此处不能加后缀名
            dataService:'./modules/dataService',
            alerter:'./modules/alerter'
        }
    });

    requirejs(['alerter'], function (alerter) {
        alerter.showMessage();
    })
})()

CMD

Common Module Definition

CMD专门用于浏览器端,模块加载是异步的。

基本语法

定义暴露模块:给我的感觉就好像是AMD和CommandJS的结合

定义没有依赖的块

define(function(require,exports,module){
	exports.xx=value;
	module.exports=value;
})

定义有依赖的块

define(function(require,exports,module){
	//引入依赖模块(同步)
	var module2=require('./module');
	//引入依赖模块(异步)
	require.async('./module3',function(m3){
	})
	//暴露模块
	exports.xxx=value;
})

引入使用模块:跟AMD一样

require(['module1','module2'],function(m1,m2){
	/* 使用m1,m2的代码 */
})

Sea.js

下载地址

https://seajs.github.io/seajs/docs/#downloads

使用示例

示例的内容没什么好说的,照着应用就行了,唯一的区别是多了个同步异步。

// module1.js
define(function (require, exports, module) {
    let msg = "module1";
    function foo() {
        return msg;
    }
    module.exports = { foo };
})
// module2.js
define(function (require, exports, module) {
    let msg = "module1";
    function foo() {
        return msg;
    }
    module.exports = { foo };
})
// module3.js
define(function (require, exports, module) {
    let msg = "module3";
    function fun() {
        console.log(msg);
    }
    exports.obj = { fun };
})
// module4.js
define(function (require, exports, module) {
    let msg = 'module4';
    // 同步引入
    let module2 = require('./module2');
    module2();
    // 异步引入
    require.async('./module3', function (module3) {
        module3.obj.fun();
    })
    function fun2() {
        console.log(msg);
    }
    exports.fun2 = fun2;
})
// main.js
define(function (require) {
    let module1 = require('./module1');
    console.log(module1.foo());
    let module4 = require('./module4');
    module4.fun2();
})

// 输出结果
// module1
// module2
// module4
// module3
<!-- index.html -->
<script src="./libs/sea.js"></script>
<script>
	seajs.use('./modules/main.js')
</script>

ES6

ES6的依赖模块也需要编译打包处理。

基本语法

导出模块:

  • 常规暴露:export
  • 默认暴露:export default

导入模块:import

  • 常规暴露:解构赋值
  • 默认暴露:直接接收

实现(浏览器端):

Babel:https://babeljs.cn

安装babel-cli, babel-preset-es2015, browserify

$ npm install babel-cli -g
$ npm install babel-preset-es2015 -D

配置文件 .babelrc

{
    "presets": ["es2015"]
}

使用babel编译为ES5语法:

$ babel js/src -d js/lib

使用browserify编译CommandJS语法:

$ browserify js/lib/main.js -o js/lib/bundle.js

使用示例

这部分也没什么好说的,大家学习 JS 的过程中应该都用过

// module1.js
// 分别暴露
export function foo() {
    console.log('foo module1')
}

export function bar() {
    console.log('bar module1')
}

export let arr = [1, 2, 3, 4, 5];
// module2.js
// 统一暴露
function fun() {
    console.log('fun module2');
}

function fun2() {
    console.log('fun2 module2');
}

export { fun, fun2 }
// 默认暴露 可以暴露任意数据类型
// 默认暴露只能写一次

export default {
    msg: '默认暴露 module3',
    showMessage() {
        console.log(this.msg);
    }
}
// main.js
// 引入其他模块

import { foo, bar } from './module1'
import { fun, fun2 } from './module2'
import module3 from './module3'

foo();
bar();
fun();
fun2();
module3.showMessage();

// foo module1
// bar module1
// fun module2
// fun2 module2
// 默认暴露 module3
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐