历史上,JavaScript一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的 require
、Python 的 import
,甚至就连 CSS 都有 @import
,c 语言中的 include
,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
JavaScript 模块化开发
模块化介绍
当你的网站开发越来越复杂的时候,会经常遇到什么问题?
1、模块化思想可以让开发更高效
2、实现模块化需要解决一个任务,这个任务就是依赖关系
3、浏览器端 js 是天然不能实现模块
4、有一些库弥补了浏览器端 js 的一些缺陷,实现了模块化并解决了依赖关系
将这种库称为模块加载器,RequireJS、SeaJS
这些模块加载器定义了自己的规范,必须尊早这些规范才能正常工作。
以 SeaJS 为例子:
- 通过 define() 方法来定义模块
- 通过 use() 方法来加载/执行模块
- 通过 require() 方法来引入模块
- 通过 exports/module.exports 暴漏模块功能
Sea.js
可以解决命名空间污染、文件依赖的问题。
- 模块的作用就是:私有空间
可以加载,可以导出
什么是模块化
- 模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。
- 解决复杂问题的一种方式而已
- 电脑:CPU、主板、显示器、内存、硬盘、输入与输出设备
使用模块化开发的方式带来的好处
- 生产效率高
- 可维护性高
模块化开发演变
全局函数
- 污染了全局变量
- 模块成员之间看不出直接关系
命名空间
- 理论意义上减少了变量冲突
- 缺点1:暴露了模块中所有的成员,内部状态可以被外部改写,不安全
- 缺点2:命名空间会越来越长
私有空间
- 私有空间的变量和函数不会影响全局作用域
- 公开公有方法,隐藏私有属性
模块的维护和扩展
- 开闭原则
- 可维护性好
模块的第三方依赖
- 保证模块的独立性
- 模块之间的依赖关系变得明显
总结
以后如果不使用第三方规范的情况下,如果写模块可以采用下面这种方式:
|
|
模块化规范
模块系统理解
自然界生态系统、计算机操作系统、软件办公系统,还有教育系统、金融系统、网络系统、理论系统等等。究竟什么是系统呢?
简单来说,系统有两个基本特性:
- 系统由个体组成
- 个体之间有关联,按照规则协同完成任务
系统之间的个体可以成为系统成员,要构建一个系统,最基本的层面需要做两件事:
- 定义系统成员:确定成员是什么
- 模块是一个 JavaScript 文件
- 每一个模块都使用
define
函数去定义
- 约定系统通讯:确定成员之间如何交互,遵循的规则是什么
- 一个 SeaJS 模块默认就是私有作用域
- 如果想要被外部文件模块所访问,就必须把要公开的属性挂载给
module.exports
对象接口 - 使用
require
函数可以加载一个指定的模块,得到该模块代码中暴露的接口对象
- 如何启动整个模块系统
- 在 html 页面中使用
seajs.use()
方法,指定一个入口文件模块
- 在 html 页面中使用
Sea.js 是一个适用于 Web 浏览器端的模块加载器。在 Sea.js 里,一切皆是模块,所有模块协同构建成模块系统。Sea.js 首要要解决的是模块系统的基本问题:
- 模块是什么?
- 模块之间如何交互?
在前端开发领域,一个模块,可以是JS 模块,也可以是 CSS 模块,或是 Template 等模块。而 Sea.js 则专注于 JS 文件模块:
- 模块是一段 JavaScript 代码,具有统一的 基本书写格式
- 模块之间通过基本 交互规则 ,能彼此引用,协同工作
把上面两点中提及的基本书写格式和基本交互规则描述清楚,就能构建出一个模块系统。对书写格式和交互规则的详细描述,就是模块定义规范(Module Definition Specification)。
比如 CommonJS 社区的 Modules 1.1.1
规范,以及 NodeJS 的 Modules
规范,还有 RequireJS 提出的 AMD
规范等等。
Sea.js 遵循的是 CMD
规范。
常见的 JavaScript 模块化规范
规范其实就是这些库在推广的过程中逐渐形成的一套规则。
所谓的规范也就是:
- 定义了模块的书写格式
以及模块之间的交互规则
Node 环境
- CommonJS
- 这里先放在这里,Node 还没有学习,学到 Node 的时候,再说这个问题
- 浏览器环境
- AMD
- RequireJS
- CMD Common Module Definition
- CMD 就是 SeaJS 这个模块加载器在推广的过程中定义的一个模块规范
- AMD
- ECMAScript
- ECMAScript 6
- UMD
CMD、AMD、CommonJS 都是社区制定出来的模块规范,他们的目的都是为了解决 JavaScript 没有模块化系统的问题。他们都有如何定义模块成员,以及模块成员之间如何进行通信交互的规则。
2015 年 9 月份,ECMAScript 官方推出了 ECMAScript 6 语言标准。在最新的 ES6 语言规范标准中制定了 JavaScript 模块化规范,通过 export
和 import
两个关键字来作为交互规则。
ES6 才是未来的趋势,以后的大一统。
前端发展非常快,不是说出了新技术马上就用,而是这个破玩儿还没发布正式版,都已经怼到生产环境了。所有任何功能,都可以使用 js 来实现。
- electron
- 使用 HTML+CSS+JavaScript+Node 构建跨平台桌面应用程序
SeaJS
A Module Loader for the Web, Enjoy the fun of programming.
- 提供简单、极致的模块化开发体验
- A Module Loader for the Web
- JavaScript 模块加载器
- 可以实现 在 JavaScript 代码中去加载另一个 JavaScript 代码。
SeaJS 介绍
SeaJS 带来的最大好处是:提升代码的可维护性。如果一个网站的 JS 文件超过 3 个,就适合用 SeaJS 来组织和维护代码。涉及的 JS 文件越多,SeaJS 就越适合。
关于 SeaJS
- SeaJS 是一个适用于浏览器环境的 JavaScript 模块加载器
- 一个库文件,类似于 jQuery
- 使用这个库提供的规范的模块化的方式来编写 JavaScript 代码
- 只关心 JavaScript 文件代码模块如何组织
- 只关心 JavaScript 文件之间如何相互协议、引用、依赖
- SeaJS 的作者是阿里巴巴支付宝前端架构师:玉伯
- SeaJS
- SeaJS -github
- SeaJS 是一个适用于浏览器环境的 JavaScript 模块加载器
为什么学习和使用 SeaJS ?
- 简单友好的模块定义规范:SeaJS 遵循 CMD 规范,可以像 Node 一样书写模块代码
- 自然直观的代码组织方式:依赖的自动加载、配置简洁清晰,可以让我们更多的享受编码的乐趣
- SeaJS兼容性非常好,几乎可以运行在任何浏览器引擎上
- 注1:SeaJS 只是实现模块化开发的一种方式或者说一种工具而已,重在模块化思想的理解
- 注2:因为 SeaJS 采用的 CMD 模块规范和 Node 中的 CommonJS 模块规范非常一致,所以有利于我们学习 Node 中的模块化编程
谁在用?
- 淘宝网、支付宝、京东、爱奇艺。。。
SeaJS 使用场景
- SeaJS 不提供任何功能性 API,只提供了解决 JavaScript 代码的命名污染和文件依赖的问题
- 所以 SeaJS 可以和 jQuery、underscore 等库结合使用
- 例如 只写写 原生 JavaScript 或者用了一些第三方库
快速上手(Getting Started)
- 下载 sea.js 库文件
- SeaJS - Release
bower install seajs
npm install seajs
- 在页面中引入 sea.js
- 使用
define
函数定义模块 - 使用
require
函数加载模块 - 使用
module.exports
对外暴露接口对象 - 使用
seajs.use
函数启动模块系统
API 详解
seajs.use
加载模块-普通路径
加载模块,启动模块系统。
- 加载一个模块
seajs.use('id')
- 加载一个模块,在加载完成时,执行回调
seajs.use('id', callback)
加载多个模块,加载完成时,执行回调
seajs.use(['id1','id2',...],callback)
注意:
- 在调用 seajs.use 之前,需要先引入 sea.js 文件
- seajs.use 与
DOM ready
事件没有任何关系。如果某些操作要确保在DOM ready
后执行,需要使用 jquery 等类库来保证 - seajs.use 理论上只用于加载启动,不应该出现在
define
中的模块代码里
define(factory)
define
是一个全局函数,用来定义模块。define
接受factory
参数,factory
可以是一个函数,也可以是一个对象或字符串。factory
为对象、字符串时,表示模块的接口就是该对象、字符串。- factory 是一个对象
define({})
- factory 是一个字符串时
define('hello')
- factory 是一个函数时
define(function(require, exports, module){})
require
解决依赖
相对路径:相对于当前模块来说的
- require 用来加载一个 js 文件模块,相对路径:相对于当前模块来说的
- require 用来获取指定模块的接口对象
module.exports
。 - require 在加载和执行的时候,js 会按照同步的方式和执行。
使用注意:
- 正确拼写
- 模块 factory 构造方法的第一个参数
必须
命名为 require
- 模块 factory 构造方法的第一个参数
- 不要修改
- 不要重命名 require 函数,或在任何作用域中给 require 重新赋值
- 使用字符串直接量
- require 的参数值 必须 是字符串直接量
模块标识
模块标识是一个字符串,用来标识模块。
- 模块标识可以不包含文件后缀名,比如
.js
- seajs 推荐不加 .js 文件模块后缀
- 模块标识可以是 相对 或 顶级 标识
- 相对标识
相对标识以 .
开头,永远相对于当前模块所处的路径来解析。
- 顶级标识
顶级标识不以 .
或 /
开始,会相对模块系统的基础路径(base路径,默认是 sea.js 文件所属的路径)。可以手动配置 base 路径。
|
|
- 普通路径(相对于 html 路径来说的)
除了相对和顶级标识之外的标识都是普通路径。普通路径的解析规则,会相对当前页面解析。
|
|
Tips:
- 顶级标识始终相对
base
基础路径解析。- 如果不设置,base 路径默认就是 sea.js 库文件所属的路径
- 可以通过
seajs.config({ base: '基础路径' })
来配置基础路径
- 绝对路径和根路径始终相对当前页面解析。
- 相对标识永远相对于当前文件
seajs.use
中的相对路径始终相对当前页面来解析。
module
module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。
module.id
- 模块的唯一标识,可以通过
define
方法的第一个参数来指定,默认为该模块文件的绝对路径
- 模块的唯一标识,可以通过
module.uri
- 模块的绝对路径
module.dependencies
- dependencies 是一个数组,表示当前模块的依赖
module.exports
- 当前模块对外提供的接口对象
- 相当于每个模块内部最终都执行了这么一句话:
return module.exports
- 模块与模块之间的通信接口
exports
exports 仅仅是 module.exports 的一个引用。也就是说修改了 exports 就相当于修改了 module.exports。
但是一旦在 factory 内部给 exports 重新赋值,并不会改变 module.exports 的值。因此给 exports 赋值是无效的。
return 也是暴露,等于 module.exports
例如 jQuery 模块化
|
|
jQuery 插件包装成模块
|
|
exports 和 module.exports 的区别
- 每个模块内部对外到处的接口对象始终都是
module.exports
- 可以通过修改
module.exports
或给它赋值改变模块接口对象 exports
是module.exports
的一个引用,就好比在每一个模块定义最开始的地方写了这么一句代码:var exports = module.exports
分析下面代码:
|
|
如何将一个普通的模块文件改造为兼容 CMD 规范的模块
|
|
高级配置 seajs.config(options)
可以对 Sea.js 进行配置,让模块编写、开发调试更方便。
|
|
使用 SeaJS 开发计算器案例
- index.html
|
|
- main.js 文件
|
|
- index.js
|
|
- add.js
|
|
- sub.js
|
|