什么是模块化?
- 将若干功能进行封装,以备将来被重复使用。
为什么要前端模块化?
- 将公共功能进行封装实现复用
- 灵活解决依赖
- 解决全局变量污染
如何实现前端模块化?
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <!-- 通过标签加载js是不能做到模块化的 --> <script src="./js/jquery.min.js"></script> <script src="./js/a.js"></script> <script src="./js/b.js"></script> </body> </html>
Javascript 语言本身是不具备模块化能力的,需要自已进行封装来实现模块的定义以及加载,其实现遵照一定的规范(COMMONJS)
Nodejs 是运行在服务端的Javascript,它是按着commonjs的规范实现的模块化
经过实践发现浏览器端Javascript按着commonjs的规范实现模块化时,有很不足之处,此时就有人在commonjs的基础上重新定了一个规范AMD(Async Module Define),其代表就是require.js
随前端开发演变,又有人定义另外一个浏览器端模块化的规范CMD(Common Module Define),淘宝的玉伯定义的,其代表是seajs
前端模块化使用requirejs
- requirejs 本身是一个js文件,它按照AMD规范实来实现模块的定义和加载规则,应用在浏览器端。
定义模块
模块是以文件形式存在的(一个文件对应一个模块也可以一个文件对应多个模块),通过define()方法来定义一个模块。
- 基本定义
define(function () { // 回调函数中即模块逻辑 var str = 'a'; alert(str); // code.... });
- 依赖注入(解决依赖的一种方式)
// 通过一个数组来声明,模块的依赖 define(['./a'], function (a) { var str = 'b'; // 通过形式参数a来接收a模块的返回值 // a模块返回值(对象)包含了方法now a.now(); // a模块返回值(对象)包含了方法sayHi a.sayHi(a.name); console.log(str); // code.... });
- 返回值
定义模块时,可以有返回值,但并不是必须,返回数据类型没有任何约束,通常返回值是对象更有意义。
define(function () { // 回调函数中即模块逻辑 var str = 'a'; // console.log(str); // 直接返回字符串 // return str; // 还可以是一个函数 // return function () { // console.log('现在是' + new Date); // } // 也可以是一个对象 return { name: '小明', sayHi: function (name) { console.log('你好' + name); }, now: function () { console.log('现在是' + new Date); } } // 返回值可以是任意类型,对象更有意义 // code.... });
加载模块
通过 require() 或者 requirejs() 方法来加载一个模块
- 基本使用
<!-- 引入requirejs --> <script src="./libs/require.js"></script> <script> // 加载模块 // 同时加载两个模块,执行顺序与书写顺序无关 require(['./a', './b']); </script>
- 加载有依赖的模块
<!-- 引入requirejs --> <script src="./libs/require.js"></script> <script> // 加载模块 // b和c模块同时依赖了a模块 // 当加载b和c模块时,a模块可以自动被加载进来 require(['./b', './c']); </script>
- 依赖注入
<script> require(['./a'], function (a) { console.log(a); a.sayHi(a.name) a.now(); }); </script>
入口文件
- 通过为引入require的标签加入 data-main 属性,其属性值为某一个模块的路径,此模块可以自动被加载并执行。
<!-- 使用自定义属性指定的模块,称为入口文件 --> <!-- 此文件会自动被执行 --> <script src="./libs/require.js" data-main="./index.js"></script>
加载路径
requirejs加载模块时,路径是遵照一些规则的,分成以下几种情况
- 当没有入口文件时,加载路径以引入requirejs的页面为准
- 当存在入口文件时,加载路径以入口文件所在目录为准
- 通过配置可以自定义加载路径
<h2>配置requirejs的的路径</h2> <script src="./libs/require.js" data-main="./main.js"></script> <script> // 相对于当前页面 // require(['./src/a']); // 假如使用入口文件, // 路径参照的是入口文件所在的目录 // require(['./src/src/a']); </script>
配置项
- 配置基础路径
// 通过config方法可以对requirejs进行配置 // 其中通过baseUrl来配置加载路径 require.config({ baseUrl: './public' });
- 配置路径
通过path属性可以配置模块真实路径(baseUrl + path),这样配置后可以使得路径调整变的灵活。
// 通过config方法可以对requirejs进行配置 // 其中通过baseUrl来配置加载路径 require.config({ baseUrl: './public', // 使用path可以为模块真实路径起个"别名" paths: { jquery: 'assets/jquery/jquery.min' } }); // a和b模块都依赖于jquery,当改变jquery路径时 // 只需要改变配置里的path就可以了 require(['./src/a', 'src/b']);
- 配置不支持模块的插件
在现实开发中并不是所有的JS库都是按着模块化形式开发,但是我们又不得不使用这些JS库,requirejs提供了解决方案。
为了帮助理解,总结了模块的两个特点“舍与得”
a) “舍”指模块将自身模块的功能提供给其它模块使用
define(function () { var obj = { name: '小明', sayHi: function () { //code... }, now: function () { // code... } } // 将自身功能提供给其它模块 return obj; })
b) “得”指模块将其它模块提供的功能直接拿来用
define(['demo'], function (demo) { // 通过数组形式提定依赖的模块 // 以形参的形式使用其它模块提供的功能 });
对于非模块requirejs也有解决方案,通过另外一种形式来满足“舍与得”的特点,如下
// 通过config方法可以对requirejs进行配置 // 其中通过baseUrl来配置加载路径 require.config({ baseUrl: './public', // 使用path可以为模块真实路径起个"别名" paths: { jquery: 'assets/jquery/jquery.min', c: 'src/c', demo: 'assets/custom/demo' }, shim: { c: { // 指定当前c模块依赖于demo // 相当于标准模块里的 // define(['demo'], function () {}); deps: ['demo'], exports: 'newobj' }, demo: { // 指明当前demo模块的返回值 // 相当于标准模块里的 // define(function () {return obj;}); exports: 'obj' } } }); // require(['./src/a', 'src/b', 'c']); require(['c']);
匿名模块和具名模块
// 匿名 define([], function () { // code... // return }); // 具名 define('demo', [], function () { // code... });
先写到这里吧,有机会再补充