当前位置: 首页 > news >正文

30分钟教你学会前端模块化开发

目录

●   一、前端模块化概要

  ⭕   1.1、模块概要

  ⭕   1.2、函数封装

  ⭕   1.3、对象封装

  ⭕   1.4、立即执行函数表达式(IIFE)

  ⭕   1.5、模块化规范

             ⚪   1.5.1、CommonJS

             ⚪   1.5.2、AMD((Asynchromous Module Definition) 异步模块定义

             ⚪   1.5.3、CMD(Common Module Definition)通用模块定义

             ⚪   1.5.4、UMD

             ⚪   1.5.5、原生JS模块化(Native JS)

             ⚪   1.5.6、小结

●   二、CommonJS

  ⭕   2.1、NodeJS中使用CommonJS模块管理

  ⭕   2.2、在浏览器中使用CommonJS 模块管理

●   三、AMD

  ⭕   3.1、概要

  ⭕   3.1、require.js

  ⭕   3.3、使用技巧

             ⚪   3.3.1、data-main属性

             ⚪   3.3.2、require.config() 配置

             ⚪   3.3.3、define()函数

             ⚪   3.3.4、require()函数

  ⭕   3.4、简单示例

  ⭕   3.5、加载 JavaScript 文件

             ⚪   3.5.1、路径处理

             ⚪   3.5.2、依赖第三方的库(AMD依赖jQuery)

  ⭕   3.6、data-main 入口点

  ⭕   3.7、定义模块

             ⚪   3.7.1、简单的值对

             ⚪   3.7.2、函数式定义

             ⚪   3.7.3、存在依赖的函数式定义

             ⚪   3.7.4、将模块定义为一个函数

             ⚪   3.7.5、简单包装CommonJS来定义模块

             ⚪   3.7.6、定义一个命名模块

             ⚪   3.7.7、依赖非AMD模块

             ⚪   3.7.8、注意事项

●   四、CMD

  ⭕   4.1、Seajs

  ⭕   4.2、seajs示例

  ⭕   4.3、官方文档

  ⭕   4.3.1、入门

             ⚪   4.3.2、基础

             ⚪   4.3.3、插件

             ⚪   4.3.4、进阶

             ⚪   4.3.5、探讨

●   五、原生模块化(ECMAScript模块化)

  ⭕   5.1、ES6模块化特点

  ⭕   5.2、在Chrome浏览器使用Module

  ⭕   5.3、在Node.js中使用Module

             ⚪   5.3.1、方法一

             ⚪   5.3.2、方法二:experimental-modules

  ⭕  5.4、Babel

             ⚪   5.4.1、配置环境

             ⚪   5.4.2、转换ES6为ES5

             ⚪   5.4.3、使用babel-node运行ES6模块化代码

  ⭕  5.5、模块(Modules)

             ⚪  5.5.1、导出方式一

             ⚪  5.5.2、导出方式二

             ⚪  5.5.3、导出方式三

             ⚪  5.5.4、导出方式四

             ⚪  5.5.5、导出方式五

             ⚪  5.5.6、导出方式六

  ⭕  5.6、模块加载器(Module Loaders)

●   六、UMD(通用的模块定义)

  ⭕  6.1、UMD示例

             ⚪  6.1.1、定义模块Utils.js

●   七、NodeJS包管理器

  ⭕  7.1、npm概要

  ⭕  7.2、包(package)

  ⭕  7.3、模块(module)

  ⭕  7.4、包和模块的关系

  ⭕  7.5.npm的生态系统

早期的javascript版本没有块级作用域、没有类、没有包、也没有模块,这样会带来一些问题,如复用、依赖、冲突、代码组织混乱等,随着前端的膨胀,模块化显得非常迫切。

 

前端模块化规范如下:

 

一、前端模块化概要

1.1、模块概要

JavaScript在早期的设计中就没有模块、包、类的概念,开发者需要模拟出类似的功能,来隔离、组织复杂的JavaScript代码,我们称为模块化。

模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块。

模块化开发的四点好处:

  (1)、 避免变量污染,命名冲突

  (2)、提高代码复用率

  (3)、提高了可维护性

  (4)、方便依赖关系管理

为了避免缺少模块带来的问题,我们可以看看程序员应对的历程:

1.2、函数封装

我们在讲函数的时候提到,函数一个功能就是实现特定逻辑的一组语句打包,而且JavaScript的作用域就是基于函数的,所以把函数作为模块化的第一步是很自然的事情,在一个文件里面编写几个相关函数就是最开始的模块了

 

//函数1
function fn1(){
  //statement
}
//函数2
function fn2(){
  //statement
} 

 

这样在需要的以后夹在函数所在文件,调用函数就可以了

缺点:

污染了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间没什么关系

1.3、对象封装

为了解决上面问题,对象的写法应运而生,可以把所有的模块成员封装在一个对象中

 

var myModule = {
var1: 1,

var2: 2,

fn1: function(){

},

fn2: function(){

}
}

 

这样我们在希望调用模块的时候引用对应文件,然后

myModule.fn2();

这样避免了变量污染,只要保证模块名唯一即可,同时同一模块内的成员也有了关系

缺陷:外部可以随意修改内部成员,这样就会产生意外的安全问题

myModel.var1 = 100;

1.4、立即执行函数表达式(IIFE)

可以通过立即执行函数表达式(IIFE),来达到隐藏细节的目的

 

var myModule = (function(){
var var1 = 1;
var var2 = 2;

function fn1(){

}

function fn2(){

}

return {
fn1: fn1,
fn2: fn2
};
})();

 

这样在模块外部无法修改我们没有暴露出来的变量、函数

缺点:功能相对较弱,封装过程增加了工作量、仍会导致命名空间污染可能、闭包是有成本的。

JavaScript最初的作用仅仅是验证表单,后来会添加一些动画,但是这些js代码很多在一个文件中就可以完成了,所以,我们只需要在html文件中添加一个script标签。

后来,随着前端复杂度提高,为了能够提高项目代码的可读性、可扩展性等,我们的js文件逐渐多了起来,不再是一个js文件就可以解决的了,而是把每一个js文件当做一个模块。那么,这时的js引入方式是怎样的呢?大概是下面这样:

 

  <script src="jquery.js"></script>
  <script src="jquery.artDialog.js"></script>
  <script src="main.js"></script>
  <script src="app1.js"></script>
  <script src="app2.js"></script>
  <script src="app3.js"></script>

 

即简单的将所有的js文件统统放在一起。但是这些文件的顺序还不能出错,比如jquery需要先引入,才能引入jquery插件,才能在其他的文件中使用jquery。

优点:

相比于使用一个js文件,这种多个js文件实现最简单的模块化的思想是进步的。 

缺点:

污染全局作用域。 因为每一个模块都是暴露在全局的,简单的使用,会导致全局变量命名冲突,当然,我们也可以使用命名空间的方式来解决。

对于大型项目,各种js很多,开发人员必须手动解决模块和代码库的依赖关系,后期维护成本较高。

依赖关系不明显,不利于维护。 比如main.js需要使用jquery,但是,从上面的文件中,我们是看不出来的,如果jquery忘记了,那么就会报错。

1.5、模块化规范

常见的的JavaScript模块规范有:CommonJS、AMD、CMD、UMD、原生模块化

1.5.1、CommonJS

CommonJs 是服务器端模块的规范,Node.js采用了这个规范。

根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。

例如:

 

// foobar.js

//私有变量
var test = 123;

//公有方法
function foobar () {

this.foo = function () {
// do someing ...
}
this.bar = function () {
//do someing ...
}
}

//exports对象上的方法和变量是公有的
var foobar = new foobar();
exports.foobar = foobar;
//require方法默认读取js文件,所以可以省略js后缀
var test = require('./foobar').foobar;

test.bar();

 

CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。像Node.js主要用于服务器的编程,加载的模块文件一般都已经存在本地硬盘,所以加载起来比较快,不用考虑异步加载的方式,所以CommonJS规范比较适用。但如果是浏览器环境,要从服务器加载模块,这是就必须采用异步模式。所以就有了 AMD CMD 解决方案。

1.5.2、AMD((Asynchromous Module Definition) 异步模块定义

AMD 是 RequireJS 在推广过程中对模块定义的规范化产出

AMD异步加载模块。它的模块支持对象 函数 构造器 字符串 JSON等各种类型的模块。

适用AMD规范适用define方法定义模块。

 

//通过数组引入依赖 ,回调函数通过形参传入依赖
define(['someModule1', ‘someModule2’], function (someModule1, someModule2) {

function foo () {
/// someing
someModule1.test();
}

return {foo: foo}
});

 

AMD规范允许输出模块兼容CommonJS规范,这时define方法如下:

 

define(function (require, exports, module) {

var reqModule = require("./someModule");
requModule.test();

exports.asplode = function () {
//someing
}
});

 

1.5.3、CMD(Common Module Definition)通用模块定义

CMD是SeaJS 在推广过程中对模块定义的规范化产出

CMD和AMD的区别有以下几点:

1.对于依赖的模块AMD是提前执行,CMD是延迟执行。不过RequireJS从2.0开始,也改成可以延迟执行(根据写法不同,处理方式不通过)。

2.CMD推崇依赖就近,AMD推崇依赖前置。

 

//AMD
define(['./a','./b'], function (a, b) {

//依赖一开始就写好
a.test();
b.test();
});

//CMD
define(function (requie, exports, module) {

//依赖可以就近书写
var a = require('./a');
a.test();

...
//软依赖
if (status) {

var b = requie('./b');
b.test();
}
});

 

虽然 AMD也支持CMD写法,但依赖前置是官方文档的默认模块定义写法。

3.AMD的api默认是一个当多个用,CMD严格的区分推崇职责单一。例如:AMD里require分全局的和局部的。CMD里面没有全局的 require,提供 seajs.use()来实现模块系统的加载启动。CMD里每个API都简单纯粹。

SeaJS 和 RequireJS的主要区别 在此有解释

1.5.4、UMD

UMD是AMD和CommonJS的综合产物。

AMD 浏览器第一的原则发展 异步加载模块。

CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。

这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。

在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

 

(function (window, factory) {
if (typeof exports === 'object') {

module.exports = factory();
} else if (typeof define === 'function' && define.amd) {

define(factory);
} else {

window.eventUtil = factory();
}
})(this, function () {
//module ...
});

 

1.5.5、原生JS模块化(Native JS)

上述的模块都不是原生 JavaScript 模块。它们只不过是我们用模块模式(module pattern)、CommonJS 或 AMD 模仿的模块系统。

JavaScript标准制定者在 TC39(该标准定义了 ECMAScript 的语法与语义)已经为 ECMAScript 6(ES6)引入内置的模块系统了。

ES6 为导入(importing)导出(exporting)模块带来了很多可能性。下面是很好的资源:

http://jsmodules.io/

http://exploringjs.com/

相对于 CommonJS 或 AMD,ES6 模块如何设法提供两全其美的实现方案:简洁紧凑的声明式语法和异步加载,另外能更好地支持循环依赖。

1.5.6、小结

AMD(异步模块定义) 是 RequireJS 在推广过程中对模块定义的规范化产出,CMD(通用模块定义)是SeaJS 在推广过程中被广泛认知。RequireJs出自dojo加载器的作者James Burke,SeaJs出自国内前端大师玉伯。

两者的区别如下:

 

RequireJS 和 SeaJS 都是很不错的模块加载器,两者区别如下:

1. 两者定位有差异。RequireJS 想成为浏览器端的模块加载器,同时也想成为 Rhino / Node 等环境的模块加载器。SeaJS 则专注于 Web 浏览器端,同时通过 Node 扩展的方式可以很方便跑在 Node 服务器端

2. 两者遵循的标准有差异。RequireJS 遵循的是 AMD(异步模块定义)规范,SeaJS 遵循的是 CMD (通用模块定义)规范。规范的不同,导致了两者 API 的不同。SeaJS 更简洁优雅,更贴近 CommonJS Modules/1.1 和 Node Modules 规范。

3. 两者社区理念有差异。RequireJS 在尝试让第三方类库修改自身来支持 RequireJS,目前只有少数社区采纳。SeaJS 不强推,而采用自主封装的方式来“海纳百川”,目前已有较成熟的封装策略。

4. 两者代码质量有差异。RequireJS 是没有明显的 bug,SeaJS 是明显没有 bug。

5. 两者对调试等的支持有差异。SeaJS 通过插件,可以实现 Fiddler 中自动映射的功能,还可以实现自动 combo 等功能,非常方便便捷。RequireJS 无这方面的支持。

6. 两者的插件机制有差异。RequireJS 采取的是在源码中预留接口的形式,源码中留有为插件而写的代码。SeaJS 采取的插件机制则与 Node 的方式一致:开放自身,让插件开发者可直接访问或修改,从而非常灵活,可以实现各种类型的插件。

 

二、CommonJS

CommonJS就是一个JavaScript模块化的规范,该规范最初是用在服务器端NodeJS中,前端的webpack也是对CommonJS原生支持的。

根据这个规范,每一个文件就是一个模块,其内部定义的变量是属于这个模块的,不会对外暴露,也就是说不会污染全局变量。

CommonJS的核心思想就是通过 require 方法来同步加载所要依赖的其他模块,然后通过 exports 或者 module.exports 来导出需要暴露的接口。

CommonJS API编写应用程序,然后这些应用可以运行在不同的JavaScript解释器和不同的主机环境中。

2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程。这标志"Javascript模块化编程"正式诞生。因为老实说,在浏览器环境下,以前没有模块也不是特别大的问题,毕竟网页程序的复杂性有限;但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。NodeJS是CommonJS规范的实现,webpack 也是以CommonJS的形式来书写。

CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}
//require()用来引入外部模块;
//exports对象用于导出当前模块的方法或变量,唯一的导出口;
//module对象就代表模块本身。

Nodejs的模块是基于CommonJS规范实现的,通过转换也可以运行在浏览器端。

 

特点:

1、所有代码都运行在模块作用域,不会污染全局作用域。

2、模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。

3、模块加载的顺序,按照其在代码中出现的顺序。

2.1、NodeJS中使用CommonJS模块管理

1、模块定义

根据commonJS规范,一个单独的文件是一个模块,每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非为global对象的属性。

模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象。

mathLib.js模块定义

var message="Hello CommonJS!";

module.exports.message=message;
module.exports.add=(m,n)=>console.log(m+n);

2、模块依赖

加载模块用require方法,该方法读取一个文件并且执行,返回文件内部的module.exports对象。

myApp.js 模块依赖

var math=require('./mathLib');
console.log(math.message);
math.add(333,888);

3、测试运行

安装好node.JS

打开控制台,可以使用cmd命令,也可以直接在开发工具中访问

 

运行

 

2.2、在浏览器中使用CommonJS 模块管理

由于浏览器不支持 CommonJS 格式。要想让浏览器用上这些模块,必须转换格式。浏览器不兼容CommonJS的根本原因,在于缺少四个Node.js环境的变量(module、exports、require、global)。只要能够提供这四个变量,浏览器就能加载 CommonJS 模块。

var math = require('math');
math.add(2, 3);

第二行math.add(2, 3),在第一行require('math')之后运行,因此必须等math.js加载完成。也就是说,如果加载时间很长,整个应用就会停在那里等。这对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态

而browserify这样的一个工具,可以把nodejs的模块编译成浏览器可用的模块,解决上面提到的问题。本文将详细介绍Browserify实现Browserify是目前最常用的CommonJS格式转换的工具

请看一个例子,b.js模块加载a.js模块

 

// a.js
var a = 100;
module.exports.a = a;

// b.js
var result = require('./a');
console.log(result.a);

 

index.html直接引用b.js会报错,提示require没有被定义

//index.html

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="myApp_01.js"></script>
</body>
</html>

这时,就要使用Browserify了

【安装】

使用下列命令安装browserify

npm install -g browserify

【转换】

使用下面的命令,就能将b.js转为浏览器可用的格式bb.js

$ browserify myApp.js > myApp_01.js

转换结果:

查看myapp_01.js,browserify将mathLib.js和myApp.js这两个文件打包为MyApp01.js,使其在浏览器端可以运行

 

(function () {
    function r(e, n, t) {
        function o(i, f) {
            if (!n[i]) {
                if (!e[i]) {
                    var c = "function" == typeof require && require;
                    if (!f && c) return c(i, !0);
                    if (u) return u(i, !0);
                    var a = new Error("Cannot find module '" + i + "'");
                    throw a.code = "MODULE_NOT_FOUND", a
                }
                var p = n[i] = {exports: {}};
                e[i][0].call(p.exports, function (r) {
                    var n = e[i][1][r];
                    return o(n || r)
                }, p, p.exports, r, e, n, t)
            }
            return n[i].exports
        }

        for (var u = "function" == typeof require && require, i = 0; i < t.length; i++) o(t[i]);
        return o
    }

    return r
})()({
    1: [function (require, module, exports) {
        var message = "Hello CommonJS!";

        module.exports.message = message;
        module.exports.add = (m, n) => console.log(m + n);


    }, {}], 2: [function (require, module, exports) {
        var math = require('./mathLib');
        console.log(math.message);
        math.add(333, 888);
    }, {"./mathLib": 1}]
}, {}, [2]);

 

index.html引用bb.js,控制台显示100

 

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="bb.js"></script> 
</body>
</html> 

 

运行结果:

 

虽然 Browserify 很强大,但不能在浏览器里操作,有时就很不方便。

 

纯浏览器的 CommonJS 模块加载器 require1k (https://github.com/Stuk/require1k)。完全不需要命令行,直接放进浏览器即可。

优点:

CommonJS规范在服务器端率先完成了JavaScript的模块化,解决了依赖、全局变量污染的问题,这也是js运行在服务器端的必要条件。

缺点:

此文主要是浏览器端js的模块化, 由于 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS是不太适用于浏览器端。

 

后续文章,请看首页。感谢您的阅读!

相关文章:

  • React 初学者需要知道的一些知识
  • 7 个沙雕又带有陷阱的 JS 面试题
  • 大厂前端如何基于 GitLab 进行自动化构建及发布
  • 前端这5个Ajax的坑,你踩过几个?别说都知道
  • Node.js 定时器详解
  • 三年经验前端开发面试总结
  • 框架设计:如何基于 Egg 设计 Node 的服务框架
  • 用好这 42 款 Chrome 插件,每年轻松省出一个年假
  • 解密国内BAT等大厂前端技术体系-百度篇(长文建议收藏)
  • 你总会用到的 JavaScript 工具函数大全(建议收藏)
  • 新手学习 react 迷惑的点(完整版)
  • React Hooks异步操作二三事
  • 三刷红宝书之 JavaScript 的引用类型
  • 20个常用的JavaScript字符串方法
  • webpack 插件机制分析及开发调试
  • centos安装java运行环境jdk+tomcat
  • classpath对获取配置文件的影响
  • JAVA SE 6 GC调优笔记
  • Java教程_软件开发基础
  • KMP算法及优化
  • Laravel核心解读--Facades
  • LintCode 31. partitionArray 数组划分
  • mysql 数据库四种事务隔离级别
  • nginx 配置多 域名 + 多 https
  • Rancher-k8s加速安装文档
  • Tornado学习笔记(1)
  • vue-cli在webpack的配置文件探究
  • XForms - 更强大的Form
  • 基于组件的设计工作流与界面抽象
  • 开源SQL-on-Hadoop系统一览
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 手机app有了短信验证码还有没必要有图片验证码?
  • 我的业余项目总结
  • 这几个编码小技巧将令你 PHP 代码更加简洁
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • (5)STL算法之复制
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (SpringBoot)第七章:SpringBoot日志文件
  • (二)pulsar安装在独立的docker中,python测试
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (强烈推荐)移动端音视频从零到上手(下)
  • (三)elasticsearch 源码之启动流程分析
  • (循环依赖问题)学习spring的第九天
  • (原創) 人會胖會瘦,都是自我要求的結果 (日記)
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • .equals()到底是什么意思?
  • .NET : 在VS2008中计算代码度量值
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .Net 应用中使用dot trace进行性能诊断
  • .NET的微型Web框架 Nancy
  • .NET中统一的存储过程调用方法(收藏)