|
| 1 | + |
| 2 | + |
| 3 | +## 1. 模块化的理解 |
| 4 | + |
| 5 | +### 1.1. 什么是模块化 |
| 6 | + |
| 7 | +* 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 |
| 8 | +* 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信 |
| 9 | +* 模块化开发的好处 |
| 10 | + * 避免命名冲突 |
| 11 | + * 更好地分离, 降低了耦合 |
| 12 | + * 提高代码的复用性 |
| 13 | + * 提高代码的可维护性 |
| 14 | + |
| 15 | +### 1.2. 模块化的演进 |
| 16 | + |
| 17 | +* 全局函数模式 |
| 18 | + |
| 19 | + * 将不同的功能封装成不同的全局函数 |
| 20 | + * 问题: 污染全局命名空间, 容易引起命名冲突,而且模块成员之间看不出直接关系 |
| 21 | + |
| 22 | +```js |
| 23 | +function m1() { |
| 24 | + //... |
| 25 | +} |
| 26 | + |
| 27 | +function m2() { |
| 28 | + //... |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +* namespace模式 |
| 33 | + |
| 34 | + * 将不同的功能封装不同的对象中 |
| 35 | + * 作用:减少了全局变量,解决命名冲突 |
| 36 | + * 问题: 数据不安全(外部可以直接修改模块内部的数据) |
| 37 | + |
| 38 | +```js |
| 39 | +let myModule = { |
| 40 | + data: 'www.baidu.com', |
| 41 | + foo() { |
| 42 | + console.log(`foo() ${this.data}`) |
| 43 | + }, |
| 44 | + bar() { |
| 45 | + console.log(`bar() ${this.data}`) |
| 46 | + } |
| 47 | +} |
| 48 | +myModule.data = 'other data' //能直接修改模块内部的数据 |
| 49 | +myModule.foo() // foo() other data |
| 50 | +``` |
| 51 | + |
| 52 | +* IIFE模式 |
| 53 | + |
| 54 | + * 使用立即执行函数表达式(IIFE)创建一个独立作用域 |
| 55 | + * IIFE中封装不同的模块功能 |
| 56 | + * 问题: 模块成员之间看不出直接关系 |
| 57 | + |
| 58 | +```html |
| 59 | +// index.html文件 |
| 60 | +<script type="text/javascript" src="module.js"></script> |
| 61 | +<script type="text/javascript"> |
| 62 | + myModule.foo() |
| 63 | + myModule.bar() |
| 64 | + console.log(myModule.data) //undefined 不能访问模块内部数据 |
| 65 | + myModule.data = 'xxxx' //不是修改的模块内部的data |
| 66 | + myModule.foo() //没有改变 |
| 67 | +</script> |
| 68 | +``` |
| 69 | + |
| 70 | +```js |
| 71 | +// module.js |
| 72 | +(function (window) { |
| 73 | + let data = 'www.baidu.com' |
| 74 | + //操作数据的函数 |
| 75 | + function foo() { |
| 76 | + //用于暴露函数 |
| 77 | + console.log(`foo() ${data}`) |
| 78 | + } |
| 79 | + function bar() { |
| 80 | + //用于暴露有函数 |
| 81 | + console.log(`bar() ${data}`) |
| 82 | + otherFun() //内部调用 |
| 83 | + } |
| 84 | + function otherFun() { |
| 85 | + //内部私有的函数 |
| 86 | + console.log('otherFun()') |
| 87 | + } |
| 88 | + //暴露模块 |
| 89 | + window.myModule = { foo, bar } |
| 90 | +})(window) |
| 91 | +``` |
| 92 | + |
| 93 | +* IIFE模式增强 |
| 94 | + |
| 95 | +这就是现代模块实现的基石 |
| 96 | + |
| 97 | +```js |
| 98 | +// module.js文件 |
| 99 | +(function(window, $) { |
| 100 | + let data = 'www.baidu.com' |
| 101 | + //操作数据的函数 |
| 102 | + function foo() { |
| 103 | + //用于暴露有函数 |
| 104 | + console.log(`foo() ${data}`) |
| 105 | + $('body').css('background', 'red') |
| 106 | + } |
| 107 | + function bar() { |
| 108 | + //用于暴露有函数 |
| 109 | + console.log(`bar() ${data}`) |
| 110 | + otherFun() //内部调用 |
| 111 | + } |
| 112 | + function otherFun() { |
| 113 | + //内部私有的函数 |
| 114 | + console.log('otherFun()') |
| 115 | + } |
| 116 | + //暴露行为 |
| 117 | + window.myModule = { foo, bar } |
| 118 | +})(window, jQuery) |
| 119 | +``` |
| 120 | + |
| 121 | +```html |
| 122 | + // index.html文件 |
| 123 | + <!-- 引入的js必须有一定顺序 --> |
| 124 | + <script type="text/javascript" src="jquery-1.10.1.js"></script> |
| 125 | + <script type="text/javascript" src="module.js"></script> |
| 126 | + <script type="text/javascript"> |
| 127 | + myModule.foo() |
| 128 | + </script> |
| 129 | +``` |
| 130 | + |
| 131 | +上例子通过jquery方法将页面的背景颜色改成红色,所以必须先引入jQuery库,就把这个库当作参数传入。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。 |
| 132 | + |
| 133 | +### 1.3. 引入多个`<script>`后出现出现问题 |
| 134 | +* 请求过多 |
| 135 | + |
| 136 | + 首先我们要依赖多个模块,那样就会发送多个请求,导致请求过多 |
| 137 | + |
| 138 | +* 依赖模糊 |
| 139 | + |
| 140 | + 我们不知道他们的具体依赖关系是什么,也就是说很容易因为不了解他们之间的依赖关系导致加载先后顺序出错。 |
| 141 | + |
| 142 | +* 难以维护 |
| 143 | + |
| 144 | + 以上两种原因就导致了很难维护,很可能出现牵一发而动全身的情况导致项目出现严重的问题。 |
| 145 | +模块化固然有多个好处,然而一个页面需要引入多个js文件,就会出现以上这些问题。而这些问题可以通过模块化规范来解决,下面介绍开发中最流行的commonjs, AMD, ES6, CMD规范。 |
| 146 | + |
| 147 | +## 2. 模块化规范 |
| 148 | + |
| 149 | +### 2.1. CommonJS(CJS) |
| 150 | +* **概述** |
| 151 | + |
| 152 | + Node 应用由模块组成,采用 CommonJS 模块规范。每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。在服务器端,模块的加载是运行时同步加载的;在浏览器端,模块需要提前编译打包处理。 |
| 153 | + |
| 154 | +* **特点** |
| 155 | + |
| 156 | + * 所有代码都运行在模块作用域,不会污染全局作用域。 |
| 157 | + * 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存。 |
| 158 | + * 模块加载的顺序,按照其在代码中出现的顺序。 |
| 159 | + |
| 160 | +* **基本语法** |
| 161 | + |
| 162 | + * 暴露模块:`module.exports = value` 或 `exports.xxx = value` |
| 163 | + * 引入模块:`require(xxx)`,如果是第三方模块,xxx为模块名;如果是自定义模块,xxx为模块文件路径 |
| 164 | + |
| 165 | +CommonJS规范规定,每个模块内部,module变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。加载某个模块,其实是加载该模块的module.exports属性。 |
| 166 | + |
| 167 | +```js |
| 168 | +// example.js |
| 169 | +var x = 5; |
| 170 | +var addX = function (value) { |
| 171 | + return value + x; |
| 172 | +}; |
| 173 | +module.exports.x = x; |
| 174 | +module.exports.addX = addX; |
| 175 | +``` |
| 176 | + |
| 177 | +上面代码通过module.exports输出两个方法:x和addX。其他文件加载这个模块,就可以使用这两个方法。加载该模块的写法如下。 |
| 178 | + |
| 179 | +```js |
| 180 | +var example = require('./example.js'); |
| 181 | +console.log(example.x); // 5 |
| 182 | +console.log(example.addX(1)); // 6 |
| 183 | +``` |
| 184 | + |
| 185 | +require方法用于加载模块。 |
| 186 | + |
| 187 | +* **模块加载机制** |
| 188 | + |
| 189 | +CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与ES6模块化有重大差异(下文会介绍),请看下面这个例子: |
| 190 | + |
| 191 | +```js |
| 192 | +// lib.js |
| 193 | +var counter = 3; |
| 194 | +function incCounter() { |
| 195 | + counter++; |
| 196 | +} |
| 197 | +module.exports = { |
| 198 | + counter: counter, |
| 199 | + incCounter: incCounter, |
| 200 | +}; |
| 201 | +``` |
| 202 | + |
| 203 | +上面代码输出内部变量counter和改写这个变量的内部方法incCounter。 |
| 204 | + |
| 205 | +```js |
| 206 | +// main.js |
| 207 | +var counter = require('./lib').counter; |
| 208 | +var incCounter = require('./lib').incCounter; |
| 209 | + |
| 210 | +console.log(counter); // 3 |
| 211 | +incCounter(); |
| 212 | +console.log(counter); // 3 |
| 213 | +``` |
| 214 | + |
| 215 | +上面代码说明,counter输出以后,lib.js模块内部的变化就影响不到counter了。这是因为counter是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。 |
0 commit comments