理解模块化
Good authors divide their books into chapters and sections; good programmers divide their programs into modules
优秀的作家会把自己写的书分成许多部分和章节;优秀的程序员则会把自己写的程序分成许多模块
我觉得这句话非常形象地指出了,程序模块化的意义。写程序就好像写作,都是从一行行代码、一个个字句累积而成。这个累积的过程现得尤为重要;我从没见过一篇文章几万字却不分章节,也从来没见过一个程序几万行代码都写在一个文件里。
模块化的意义在于,让庞大而紧密的东西变得小巧与松散,然后这些小巧而松散的东西就具有了独立性、可维护性、可复用性等等优良的特性,但是一个复杂的庞然大物是绝对不可能拥有这些特性的。模块化,能让你在构建大型软件项目时从容不迫,事半功倍!就像你汽车的发动机打不着火了,你不必换掉整个发动机,你只需要换掉那个发生故障的零件即可。
模块化的意义
可维护性
一个优秀的模块,一定是尽最大可能减小与主体的关联性,也就是保持最大化的独立性,主体失去这个模块,也不会瘫痪或者崩溃,最多就是失去这个模块的功能。当然了,模块不可能100%的独立,那样的话就产生了另一个主体了,我们在设计一个模块的时候,要尽可能将模块对主体的影响降到最小,这就够了!
我们可以来对比一下,使用模块化的编程思想和不使用的区别,现在有一个程序,它某一个功能出现了故障:
- 不使用模块化
- 找到出现错误的代码位置,分析错误发生的原因
- 开始修复错误,修改相关的变量、函数
- 发现,故障代码其中一个变量是从另外一个函数计算给出的,于是重复第2步
- 又出现了第3步的情况,于是…..陷入了“牵一发而动全身”的困境
- ……………………………..
- n 次的查找变量、修改变量后,终于修复成功
- 使用模块化
- 找到出现错误的代码位置并定位与之对应的模块,分析错误发生的原因
- 开始修复错误,修改相关的变量、函数
- 修复完成,重新将模块载入主体
现在你应该懂了:一个独立的模块,具有很高的可维护性,你可以自由修改这个模块,但是不影响其他的地方,同时你不会受到其他地方的影响,因为你的模块中,变量或函数都来自于自身
复用性
只要是程序,其中一定有某些功能在逻辑上是相似或者重复的。那么,你是愿意用一个功能,然后写100次?还是写一个功能,用100次?
模块的复用性是我们写出冗余代码最少、定义重复功能最少的程序的重要保证。我一直认为,模块的使用,概念上与函数相似,因为如果一个模块具有很好的复用性——可以被程序多次调用,那么我们的程序将非常简洁,性能也会非常高!因为我们把重复的工作都省掉了,给机器的负担就会比较小。
命名空间
命名空间是相对于 Javascript 所运行的宿主来说的,Javascript 运行在浏览器上,对于浏览器来说,全局环境就是 window 对象,对象的属性无法同名,这是常识。所以,在全局环境中,一定不能有相同的命名,否则就会发生异常
一个班级里如果有两个学生都叫小明,那老师点名的时候会怎样?两个小明都会站起来,然后一脸茫然的看着老师,不知道点的到底是不是自己;同理,一个程序运行时,如果发现了两个相同名称的变量,它毫无疑问的会发生错误,因为其中一个变量一定是无效的,可想而知引用了这个变量的函数或者表达式,将会报出多少错误!
但是,只要变量在全局环境中定义,不可避免地会遇到同名的情况(就像你不能阻止世界上两个人起了相同的名字),于是全局环境就会被污染
模块则可以保证命名空间不被污染——它根本就不与全局环境产生联系。
每个模块都是一个单独的文件,模块在运行时都会创建自己独立的作用域(不受全局环境的约束,也不存在于在全局环境中),这个作用域外界无法访问, 它只对外界暴露出来那些需要被用到的接口,外界只管调用就行了,而不用管模块内部如何实现的(实际上也管不到)
ES6 模块化
概述
如果你用过 Python 你一定知道 Python 中的 import 命令,它可以帮你导入你需要的第三方库,非常的方便;然而Javascript却一直没有模块的机制,直到ES6,Javascript才拥有了导入(import)和导出(export)的能力。ES6 之前为了 “仿模块化” 常常采用闭包,闭包常常难以理解,而现在的 import 和 export 新语法在使用上就非常简单,让人一目了然。
ES6 的模块机制(Module)与 CommonJS、AMD等都不同。CommonJS 和 AMD 都是在运行时确定变量的值,但是ES6 Module 却可以在静态编译时确定变量的值,这样做的意义就是——实现静态编译优化,显著提高性能
export
ES6中,一个模块就是一个独立的 js 文件。该文件内部的所有变量,外部无法获取。需要使用export关键字导出你想暴露给外界的变量或函数
使用一对大括号来包裹要导出的变量,变量之间用逗号隔开
// out.js 文件
// 导出变量
export const msg = 'Hello World'
// 导出函数
export function hi(){
console.log('Hello World')
}
// 导出对象
export class Person{
.......
}
// 推荐的写法
export {msg, hi, Person}
as
export 导出的变量,可以使用 as 来重命名
function f1() { ... }
function f2() { ... }
export {
f1 as myFunctionOne,
f2 as myFunctionTwo
}
default
当外部使用 import 导入模块提供的变量时,会先看看模块导出了哪些变量,然后再根据这些变量名一一导入;有时候,导入的一方不想看另一方提供的导出的变量的名单,而是想省事,直接一下子都导入,那么 export default就可以满足这个“小要求”。export default 可以导出模块默认提供的变量,也就是说 import 的时候不用再查看哪些变量是可以导入的了
// 导出单个变量
export default function(){}
// 导出多个变量
export default{
const msg = 123,
function hi(){}
}
export default 语句的本质是:将导出的变量,赋值给一个名为 default 的对象,所以它后面不能跟变量声明语句,而且,一个模块中只能有一个 export default 语句。
import
可以把 import 和 export 想象成一对互逆的操作,它们的用法也是大同小异。
- 使用 import 导入模块时,要用 from 加上路径(绝对路径或相对路径)
- 可以省略路径中文件的
.js后缀 - import 命令会被提升,所以可以先使用,后声明
最重要的是, import 导入的变量都是只读的,不可以修改它!
import {msg, hi, Person} from './out'
as
跟 export 的 as 一样,也是用来给导入的变量重命名的
import {
myFunctionOne as f1,
myFunctionTwo as f2
} from './out'
*
如果要导入一个模块提供的所有变量,可以使用一个星号 *
import * from './out'