随着互联网的飞速发展,前端开发愈来愈复杂。本文将从实际项目中遇到的问题出发,讲述模块化能解决哪些问题,以及如何使用 Sea.js 进行前端的模块化开发。javascript
咱们从一个简单的习惯出发。我作项目时,经常会将一些通用的、底层的功能抽象出来,独立成一个个函数,好比css
function each(arr) { // 实现代码 } function log(str) { // 实现代码 }
并像模像样地把这些函数统一放在 util.js 里。须要用到时,引入该文件就行。这一切工做得很好,同事也很感激我提供了这么便利的工具包。html
直到团队愈来愈大,开始有人抱怨。前端
小杨:我想定义一个 each 方法遍历对象,但页头的 util.js 里已经定义了一个,个人只能叫 eachObject 了,好无奈。java
小高:我自定义了一个 log 方法,为何小明写的代码就出问题了呢?谁来帮帮我。node
抱怨愈来愈多。团队通过一番激烈的讨论,决定参照 Java 的方式,引入命名空间来解决。因而 util.js 里的代码变成了git
var org = {}; org.CoolSite = {}; org.CoolSite.Utils = {}; org.CoolSite.Utils.each = function (arr) { // 实现代码 }; org.CoolSite.Utils.log = function (str) { // 实现代码 };
不要认为上面的代码是为了写这篇文章而故意捏造的。将命名空间的概念在前端中发扬光大,首推 Yahoo! 的 YUI2 项目。下面是一段真实代码,来自 Yahoo! 的一个开源项目。程序员
if (org.cometd.Utils.isString(response)) { return org.cometd.JSON.fromJSON(response); } if (org.cometd.Utils.isArray(response)) { return response; }
经过命名空间,的确能极大缓解冲突。但往往看到上面的代码,都忍不住充满同情。为了调用一个简单的方法,须要记住如此长的命名空间,这增长了记忆负担,同时剥夺了很多编码的乐趣。github
做为前端业界的标杆,YUI 团队下定决心解决这一问题。在 YUI3 项目中,引入了一种新的命名空间机制。后端
YUI().use('node', function (Y) { // Node 模块已加载好 // 下面能够经过 Y 来调用 var foo = Y.one('#foo'); });
YUI3 经过沙箱机制,很好的解决了命名空间过长的问题。然而,也带来了新问题。
YUI().use('a', 'b', function (Y) { Y.foo(); // foo 方法到底是模块 a 仍是 b 提供的? // 若是模块 a 和 b 都提供 foo 方法,如何避免冲突? });
看似简单的命名冲突,实际解决起来并不简单。如何更优雅地解决?咱们按下暂且不表,先来看另外一个常见问题。
继续上面的故事。基于 util.js,我开始开发 UI 层通用组件,这样项目组同事就不用重复造轮子了。
其中有一个最被你们喜欢的组件是 dialog.js,使用方式很简单。
<script src="util.js"></script> <script src="dialog.js"></script> <script> org.CoolSite.Dialog.init({ /* 传入配置 */ }); </script>
但是不管我怎么写文档,以及多么郑重地发邮件宣告,时不时总会有同事来询问为何 dialog.js 有问题。经过一番排查,发现致使错误的缘由常常是
<script src="dialog.js"></script> <script> org.CoolSite.Dialog.init({ /* 传入配置 */ }); </script>
在 dialog.js 前没有引入 util.js,所以 dialog.js 没法正常工做。一样不要觉得我上面的故事是虚构的,在我待过的公司里,至今依旧有相似的脚本报错,特别是在各类快速制做的营销页面中。
上面的文件依赖还在可控范围内。当项目愈来愈复杂,众多文件之间的依赖常常会让人抓狂。下面这些问题,我相信天天都在真实地发生着。
以上不少问题都是由于文件依赖没有很好的管理起来。在前端页面里,大部分脚本的依赖目前依旧是经过人肉的方式保证。当团队比较小时,这不会有什么问题。当团队愈来愈大,公司业务愈来愈复杂后,依赖问题若是不解决,就会成为大问题。
文件的依赖,目前在绝大部分类库框架里,好比国外的 YUI3 框架、国内的 KISSY 等类库,目前是经过配置的方式来解决。
YUI.add('my-module', function (Y) { // ... }, '0.0.1', { requires: ['node', 'event'] });
上面的代码,经过 requires
等方式来指定当前模块的依赖。这很大程度上能够解决依赖问题,但不够优雅。当模块不少,依赖很复杂时,烦琐的配置会带来很多隐患。
命名冲突和文件依赖,是前端开发过程当中的两个经典问题。下来咱们看如何经过模块化开发来解决。为了方便描述,咱们使用 Sea.js 来做为模块化开发框架。
Sea.js 是一个成熟的开源项目,核心目标是给前端开发提供简单、极致的模块化开发体验。这里很少作介绍,有兴趣的能够访问 seajs.org 查看官方文档。
使用 Sea.js,在书写文件时,须要遵照 CMD (Common Module Definition)模块定义规范。一个文件就是一个模块。前面例子中的 util.js 变成
define(function(require, exports) { exports.each = function (arr) { // 实现代码 }; exports.log = function (str) { // 实现代码 }; });
经过 exports
就能够向外提供接口。这样,dialog.js 的代码变成
define(function(require, exports) { var util = require('./util.js'); exports.init = function() { // 实现代码 }; });
关键部分到了!咱们经过 require('./util.js')
就能够拿到 util.js 中经过 exports
暴露的接口。这里的require 能够认为是 Sea.js 给 JavaScript 语言增长的一个 语法关键字,经过 require
能够获取其余模块提供的接口。
这其实一点也不神奇。做为前端工程师,对 CSS 代码必定也不陌生。
@import url("base.css"); #id { ... } .class { ... }
Sea.js 增长的 require
语法关键字,就如 CSS 文件中的 @import
同样,给咱们的源码赋予了依赖引入功能。
若是你是后端开发工程师,更不会陌生。Java、Python、C# 等等语言,都有 include
、import
等功能。JavaScript 语言自己也有相似功能,但目前还处于草案阶段,须要等到 ES6 标准获得主流浏览器支持后才能使用。
这样,在页面中使用 dialog.js 将变得很是简单。
<script src="sea.js"></script> <script> seajs.use('dialog', function(Dialog) { Dialog.init(/* 传入配置 */); }); </script>
首先要在页面中引入 sea.js 文件,这通常经过页头全局把控,也方便更新维护。想在页面中使用某个组件时,只要经过 seajs.use
方法调用。
好好琢磨以上代码,我相信你已经看到了 Sea.js 带来的两大好处:
经过 exports
暴露接口。这意味着不须要命名空间了,更不须要全局变量。这是一种完全的命名冲突解决方案。
经过 require
引入依赖。这可让依赖内置,开发者只需关心当前模块的依赖,其余事情 Sea.js 都会自动处理好。对模块开发者来讲,这是一种很好的 关注度分离,能让程序员更多地享受编码的乐趣。
除了解决命名冲突和依赖管理,使用 Sea.js 进行模块化开发还能够带来不少好处:
模块的版本管理。经过别名等配置,配合构建工具,能够比较轻松地实现模块的版本管理。
提升可维护性。模块化可让每一个文件的职责单一,很是有利于代码的维护。Sea.js 还提供了 nocache、debug 等插件,拥有在线调试等功能,能比较明显地提高效率。
前端性能优化。Sea.js 经过异步加载模块,这对页面性能很是有益。Sea.js 还提供了 combo、flush 等插件,配合服务端,能够很好地对页面性能进行调优。
跨环境共享模块。CMD 模块定义规范与 Node.js 的模块规范很是相近。经过 Sea.js 的 Node.js 版本,能够很方便实现模块的跨服务器和浏览器共享。
模块化开发并非新鲜事物,但在 Web 领域,前端开发是新生岗位,一直处于比较原始的刀耕火种时代。直到最近两三年,随着 Dojo、YUI三、Node.js 等社区的推广和流行,前端的模块化开发理念才逐步深刻人心。
前端的模块化构建可分为两大类。一类是以 Dojo、YUI三、国内的 KISSY 等类库为表明的大教堂模式。在大教堂模式下,全部组件都是颗粒化、模块化的,各组件之间层层分级、环环相扣。另外一类是以 jQuery、RequireJS、国内的 Sea.js、OzJS 等类库为基础的集市模式。在集市模式下,全部组件彼此独立、职责单一,各组件经过组合松耦合在一块儿,协同完成开发。
这两类模块化构建方式各有应用场景。从长远来看,小而美更具有宽容性和竞争力,更能造成有活力的生态圈。
总之,模块化能给前端开发带来不少好处。若是你尚未尝试,不妨从试用 Sea.js 开始。
https://github.com/seajs/seajs/issues/547