ES6 模块化

259 阅读7分钟

理解模块化

Good authors divide their books into chapters and sections; good programmers divide their programs into modules

优秀的作家会把自己写的书分成许多部分和章节;优秀的程序员则会把自己写的程序分成许多模块

我觉得这句话非常形象地指出了,程序模块化的意义。写程序就好像写作,都是从一行行代码、一个个字句累积而成。这个累积的过程现得尤为重要;我从没见过一篇文章几万字却不分章节,也从来没见过一个程序几万行代码都写在一个文件里。

模块化的意义在于,让庞大而紧密的东西变得小巧与松散,然后这些小巧而松散的东西就具有了独立性、可维护性、可复用性等等优良的特性,但是一个复杂的庞然大物是绝对不可能拥有这些特性的。模块化,能让你在构建大型软件项目时从容不迫,事半功倍!就像你汽车的发动机打不着火了,你不必换掉整个发动机,你只需要换掉那个发生故障的零件即可。

模块化的意义

可维护性

一个优秀的模块,一定是尽最大可能减小与主体的关联性,也就是保持最大化的独立性,主体失去这个模块,也不会瘫痪或者崩溃,最多就是失去这个模块的功能。当然了,模块不可能100%的独立,那样的话就产生了另一个主体了,我们在设计一个模块的时候,要尽可能将模块对主体的影响降到最小,这就够了!

我们可以来对比一下,使用模块化的编程思想和不使用的区别,现在有一个程序,它某一个功能出现了故障:

  • 不使用模块化
    1. 找到出现错误的代码位置,分析错误发生的原因
    2. 开始修复错误,修改相关的变量、函数
    3. 发现,故障代码其中一个变量是从另外一个函数计算给出的,于是重复第2步
    4. 又出现了第3步的情况,于是…..陷入了“牵一发而动全身”的困境
    5. ……………………………..
    6. n 次的查找变量、修改变量后,终于修复成功
  • 使用模块化
    1. 找到出现错误的代码位置并定位与之对应的模块,分析错误发生的原因
    2. 开始修复错误,修改相关的变量、函数
    3. 修复完成,重新将模块载入主体

现在你应该懂了:一个独立的模块,具有很高的可维护性,你可以自由修改这个模块,但是不影响其他的地方,同时你不会受到其他地方的影响,因为你的模块中,变量或函数都来自于自身

复用性

只要是程序,其中一定有某些功能在逻辑上是相似或者重复的。那么,你是愿意用一个功能,然后写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'