首页 > 代码库 > javascript中模块化知识总结

javascript中模块化知识总结

JavaScript 模块化开发


1. 模块化介绍

掌握模块化基本概念以及使用模块化带来的好处

当你的网站开发越来越复杂的时候,会经常遇到什么问题?

  • 恼人的命名冲突
  • 繁琐的文件依赖

历史上,JavaScript一直没有模块(module)体系, 无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。 其他语言都有这项功能,比如Ruby的 require、Python的 import , 甚至就连CSS都有 @import , 但是JavaScript任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

如何解决不通过 script 标签就能加载 JavaScript 文件代码。

  • 什么是模块化

    • 一块儿一块儿的:JavaScript 也是一块儿一块儿
    • 模块化就是一种将复杂事物按照模块的方式简单化实现
    • 模块与模块之间互相协作构成了模块系统
  • 使用模块化开发的方式带来的好处

    • 生产效率高
    • 可维护性高

眼见他起高楼,眼见他宴宾客,眼见他楼塌了。


2. 模块化开发演变

体验刀耕火种的模块化解决方式

2.1 全局函数

  • 污染了全局变量
  • 模块成员之间看不出直接关系

2.2 命名空间

  • 理论意义上减少了变量冲突
  • 缺点1:暴露了模块中所有的成员,内部状态可以被外部改写,不安全
  • 缺点2:命名空间会越来越长

2.3 私有空间

  • 私有空间的变量和函数不会影响全局作用域
  • 公开公有方法,隐藏私有属性

2.4 模块的维护和扩展

  • 开闭原则
  • 可维护性好

2.5 模块的第三方依赖

  • 保证模块的独立性
  • 模块之间的依赖关系变得明显

2.6 总结

以后如果不使用第三方规范的情况下, 如果写模块可以采用下面这种方式

```js ; (function (形参模块名, 依赖项, 依赖项) { // 通过 形参模块名 修改模块

window.模块名 = 形参模块名 })(window.模块名 || {}, 依赖项, 依赖项) ```

下面是一个关于模块化的面试题,一起观察和分析这段代码的作用:

js var UTIL = (function (parent, $) { // 动态的给一个不存在的对象挂载一个子对象 var my = parent.ajax = parent.ajax || {} my.get = function (url, params, callback) { return $.getJSON(url, params, callback) } return parent }(UTIL || {}, jQuery))


3. 模块化规范

3.1 模块系统理解

自然界生态系统、计算机操作系统、软件办公系统,还有教育系统、金融系统、网络系统、 理论系统等等。究竟什么是系统呢?

维基百科:系统泛指由一群有关连的个体组成,根据预先编排好的规则工作, 能完成个别元件不能单独完成的工作的群体。 系统分为自然系统与人为系统两大类。

简单来说,系统有两个基本特性:

  1. 系统由个体组成
  2. 个体之间有关联,按照规则协同完成任务

系统之间的个体可以成为系统成员,要构建一个系统,最基本的层面需要做两件事:

  1. 定义系统成员:确定成员是什么
    • 模块是一个 JavaScript 文件
    • 每一个模块都使用 define 函数去定义
  2. 约定系统通讯:确定成员之间如何交互,遵循的规则是什么
    • 一个 SeaJS 模块默认就是私有作用域
    • 如果想要被外部文件模块所访问,就必须把要公开的属性挂载给 module.exports 对象接口
    • 使用 require 函数可以加载一个指定的模块,得到该模块代码中暴露的接口对象
  3. 如何启动整个模块系统
    • 在 html 页面中使用 seajs.use() 方法,指定一个入口文件模块

Sea.js 是一个适用于 Web 浏览器端的模块加载器。 在 Sea.js 里,一切皆是模块,所有模块协同构建成模块系统。 Sea.js 首要要解决的是模块系统的基本问题:

  1. 模块是什么?
  2. 模块之间如何交互?

在前端开发领域,一个模块,可以是JS 模块,也可以是 CSS 模块,或是 Template 等模块。 而 Sea.js 则专注于 JS 文件模块:

  1. 模块是一段 JavaScript 代码,具有统一的 基本书写格式
  2. 模块之间通过基本 交互规则 ,能彼此引用,协同工作

把上面两点中提及的基本书写格式和基本交互规则描述清楚,就能构建出一个模块系统。 对书写格式和交互规则的详细描述,就是模块定义规范(Module Definition Specification)。

比如 CommonJS 社区的 Modules 1.1.1 规范, 以及 NodeJS 的 Modules 规范, 还有 RequireJS 提出的 AMD 规范等等。

Sea.js 遵循的是 CMD 规范。

3.2 常见的 JavaScript 模块化规范

  • CommonJS
    • Node.js
  • AMD
    • RequireJS
  • CMD Common Module Definition
    • CMD 就是 SeaJS 这个模块加载器在推广的过程中定义的一个模块规范
  • ECMAScript
    • ECMAScript 6
  • UMD

CMD、AMD、CommonJS 都是社区制定出来的模块规范, 他们的目的都是为了解决 JavaScript 没有模块化系统的问题。 他们都有如何定模块成员,以及模块成员之间如何进行通信交互的规则。

AngularJS Module 也是 ng 框架本身提供的一个模块系统解决方案。 ng定义模块需要起名,即便你去加载或者依赖一个模块的时候, 这个被依赖的模块也需要通过 script 标签引入到 html 页面中。

2015 年 9 月份,ECMAScript 官方推出了 ECMAScript 6 语言标准。 在最新的 ES6 语言规范标准中,已经制定了 JavaScript 模块化规范。

export import

前端界的风气:都喜欢追赶新技术,甚至把没有推出来的标准直接干到了生产环境。

这个社区太浮躁了。


4. SeaJS

A Module Loader for the Web, Enjoy the fun of programming.

  • 提供简单、极致的模块化开发体验
  • A Module Loader for the Web
  • JavaScript 模块加载器
  • 可以实现 在 JavaScript 代码中去加载另一个 JavaScript 代码。

4.1 SeaJS 介绍

  • 关于 SeaJS

    • SeaJS 是一个适用于浏览器环境的 JavaScript 模块加载器
    • 一个库文件,类似于 jQuery
    • 使用这个库提供的规范的模块化的方式来编写 JavaScript 代码
    • 只关心 JavaScript 文件代码模块如何组织
      • 只关心 JavaScript 文件之间如何相互协议、引用、依赖
    • SeaJS 的作者是阿里巴巴支付宝前端架构师,花名:玉伯,玉伯也叫射雕
    • Sea.js创始人玉伯的前端开发之路
    • SeaJS
    • SeaJS -github
  • 为什么学习和使用 SeaJS ?

    • 简单友好的模块定义规范:SeaJS 遵循 CMD 规范,可以像 Node 一样书写模块代码
    • 自然直观的代码组织方式:依赖的自动加载、配置简洁清晰,可以让我们更多的享受编码的乐趣
    • SeaJS兼容性非常好,几乎可以运行在任何浏览器引擎上
    • 注1:SeaJS 只是实现模块化开发的一种方式或者说一种工具而已,重在模块化思想的理解
    • 注2:因为 SeaJS 采用的 CMD 模块规范和 Node 中的 CommonJS 模块规范非常一致,所以有利于我们学习 Node 中的模块化编程
  • 谁在用?

    • 淘宝网、支付宝、京东、爱奇艺。。。
  • SeaJS 适用场景

    • 没有使用任何框架,例如 AngularJS
    • 例如 只写写 原生 JavaScript 或者用了一些第三方库
    • SeaJS 不提供任何功能性 API,只提供了解决 JavaScript 代码的命名污染和文件依赖的问题

4.2 快速上手(Getting Started)

  1. 下载 sea.js 库文件
    • SeaJS - Release
    • bower install seajs
    • npm install seajs
  2. 在页面中引入 sea.js
  3. 使用 define 函数定义模块
  4. 使用 require 函数加载模块
  5. 使用 module.exports 对外暴露接口对象
  6. 使用 seajs.use 函数启动模块系统

4.3 API 详解

4.3.1 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 中的模块代码里

4.3.2 define(factory)

define 是一个全局函数,用来定义模块。

define 接受 factory 参数,factory 可以是一个函数,也可以是一个对象或字符串。

factory 为对象、字符串时,表示模块的接口就是该对象、字符串。

  • factory 是一个对象时

    • define({})
  • factory 是一个字符串时

    • define(‘hello‘)
  • factory 是一个函数时

    • define(function(require, exports, module){})

4.3.3 require

require 用来加载一个 js 文件模块, require 用来获取指定模块的接口对象 module.exports

require 在加载和执行的时候,js 会按照同步的方式和执行。

使用注意:

  • 正确拼写
    • 模块 factory 构造方法的第一个参数 必须 命名为 require
  • 不要修改
    • 不要重命名 require 函数,或在任何作用域中给 require 重新赋值
  • 使用字符串直接量
    • require 的参数值 必须 是字符串直接量

Tips: 把 require 看做是语法关键字就好啦

4.3.4 模块标识

模块标识是一个字符串,用来标识模块。

  • 模块标识可以不包含文件后缀名,比如 .js

    • seajs 推荐不加 .js 文件模块后缀
  • 模块标识可以是 相对 或 顶级 标识

  • 相对标识

相对标识以 . 开头,永远相对于当前模块所处的路径来解析。

  • 顶级标识

顶级标识不以 . 或 / 开始,会相对模块系统的基础路径(base路径,默认是 sea.js 文件所属的路径)。 可以手动配置 base 路径。

js seajs.config({ base: ‘./js‘ })

  • 普通路径

除了相对和顶级标识之外的标识都是普通路径。 普通路径的解析规则,会相对当前页面解析。

```js // 假设当前页面是 http://example.com/path/to/page/index.html

// 绝对路径是普通路径: require.resolve(‘http://cdn.com/js/a‘); // => http://cdn.com/js/a.js

// 根路径是普通路径: require.resolve(‘/js/b‘); // => http://example.com/js/b.js

// use 中的相对路径始终是普通路径: seajs.use(‘./c‘); // => 加载的是 http://example.com/path/to/page/c.js

seajs.use(‘../d‘); // => 加载的是 http://example.com/path/to/d.js ```

Tips:

  • 顶级标识始终相对 base 基础路径解析。
    • 如果不设置,base 路径默认就是 sea.js 库文件所属的路径
    • 可以通过 seajs.config({ base: ‘基础路径‘ }) 来配置基础路径
  • 绝对路径和根路径始终相对当前页面解析。
  • 相对标识永远相对于当前文件
  • seajs.use 中的相对路径始终相对当前页面来解析。

4.3.5 module

module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

  • module.id
    • 模块的唯一标识,可以通过 define 方法的第一个参数来指定,默认为该模块文件的绝对路径
  • module.uri
    • 模块的绝对路径
  • module.dependencies
    • dependencies 是一个数组,表示当前模块的依赖
  • module.exports
    • 当前模块对外提供的接口对象
    • 相当于每个模块内部最终都执行了这么一句话:return module.exports
    • 模块与模块之间的通信接口

4.3.6 exports

exports 仅仅是 module.exports 的一个引用。 也就是说修改了 exports 就相当于修改了 module.exports。

但是一旦在 factory 内部给 exports 重新赋值,并不会改变 module.exports 的值。 因此给 exports 赋值是无效的。

4.4 exports 和 module.exports 的区别

  • 每个模块内部对外到处的接口对象始终都是 module.exports
  • 可以通过修改 module.exports 或给它赋值改变模块接口对象
  • exports 是 module.exports 的一个引用,就好比在每一个模块定义最开始的地方写了这么一句代码:var exports = module.exports

关于这俩哥们儿的区别请分析一下代码:

```js var module = { exports: {} }

function changeExports (exports, module) { // var exports = module.exports exports.foo = ‘bar‘

// 这里赋值拿不到,不要使用使用 // exports = function () {} return module.exports }

changeExports(module.exports, module) ```

那为啥要有 exports ?

为了开发体验,API更友好,使用 exports 的时候,可以少写一个点儿。

如果你实在分不清楚 exports 和 module.exports 之间的区别,就只记得 module.exports 就可以了。

javascript中模块化知识总结