随着互联网的飞速发展,前端开发越来越复杂。本文将从实际项目中遇到的问题出发,讲述模块化能解决哪些问题,以及如何使用 Sea.js 进行前端的模块化开发。

恼人的命名冲突

我们从一个简单的习惯出发。我做项目时,常常会将一些通用的、底层的功能抽象出来,独立成一个个函数,比如

function each(arr) {// 实现代码
}function log(str) {// 实现代码
}

并像模像样地把这些函数统一放在 util.js 里。需要用到时,引入该文件就行。这一切工作得很好,同事也很感激我提供了这么便利的工具包。

直到团队越来越大,开始有人抱怨。

小杨:我想定义一个 each 方法遍历对象,但页头的 util.js 里已经定义了一个,我的只能叫 eachObject 了,好无奈。

小高:我自定义了一个 log 方法,为什么小明写的代码就出问题了呢?谁来帮帮我。

抱怨越来越多。团队经过一番激烈的讨论,决定参照 Java 的方式,引入命名空间来解决。于是 util.js 里的代码变成了

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;
}

通过命名空间,的确能极大缓解冲突。但每每看到上面的代码,都忍不住充满同情。为了调用一个简单的方法,需要记住如此长的命名空间,这增加了记忆负担,同时剥夺了不少编码的乐趣。

作为前端业界的标杆,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 无法正常工作。同样不要以为我上面的故事是虚构的,在我待过的公司里,至今依旧有类似的脚本报错,特别是在各种快速制作的营销页面中。

上面的文件依赖还在可控范围内。当项目越来越复杂,众多文件之间的依赖经常会让人抓狂。下面这些问题,我相信每天都在真实地发生着。

  1. 通用组更新了前端基础类库,却很难推动全站升级。
  2. 业务组想用某个新的通用组件,但发现无法简单通过几行代码搞定。
  3. 一个老产品要上新功能,最后评估只能基于老的类库继续开发。
  4. 公司整合业务,某两个产品线要合并。结果发现前端代码冲突。
  5. ……

以上很多问题都是因为文件依赖没有很好的管理起来。在前端页面里,大部分脚本的依赖目前依旧是通过人肉的方式保证。当团队比较小时,这不会有什么问题。当团队越来越大,公司业务越来越复杂后,依赖问题如果不解决,就会成为大问题。

文件的依赖,目前在绝大部分类库框架里,比如国外的 YUI3 框架、国内的 KISSY 等类库,目前是通过配置的方式来解决。

YUI.add('my-module', function (Y) {// ...
}, '0.0.1', {requires: ['node', 'event']
});

上面的代码,通过 requires 等方式来指定当前模块的依赖。这很大程度上可以解决依赖问题,但不够优雅。当模块很多,依赖很复杂时,烦琐的配置会带来不少隐患。

命名冲突和文件依赖,是前端开发过程中的两个经典问题。下来我们看如何通过模块化开发来解决。为了方便描述,我们使用 Sea.js 来作为模块化开发框架。

使用 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# 等等语言,都有 includeimport 等功能。JavaScript 语言本身也有类似功能,但目前还处于草案阶段,需要等到 ES6 标准得到主流浏览器支持后才能使用。

这样,在页面中使用 dialog.js 将变得非常简单。

<script src="sea.js"></script>
<script>
seajs.use('dialog', function(Dialog) {  Dialog.init(/* 传入配置 */);
});
</script>

首先要在页面中引入 sea.js 文件,这一般通过页头全局把控,也方便更新维护。想在页面中使用某个组件时,只要通过 seajs.use 方法调用。

好好琢磨以上代码,我相信你已经看到了 Sea.js 带来的两大好处:

  1. 通过 exports 暴露接口。这意味着不需要命名空间了,更不需要全局变量。这是一种彻底的命名冲突解决方案。

  2. 通过 require 引入依赖。这可以让依赖内置,开发者只需关心当前模块的依赖,其他事情 Sea.js 都会自动处理好。对模块开发者来说,这是一种很好的 关注度分离,能让程序员更多地享受编码的乐趣。

小结

除了解决命名冲突和依赖管理,使用 Sea.js 进行模块化开发还可以带来很多好处:

  1. 模块的版本管理。通过别名等配置,配合构建工具,可以比较轻松地实现模块的版本管理。

  2. 提高可维护性。模块化可以让每个文件的职责单一,非常有利于代码的维护。Sea.js 还提供了 nocache、debug 等插件,拥有在线调试等功能,能比较明显地提升效率。

  3. 前端性能优化。Sea.js 通过异步加载模块,这对页面性能非常有益。Sea.js 还提供了 combo、flush 等插件,配合服务端,可以很好地对页面性能进行调优。

  4. 跨环境共享模块。CMD 模块定义规范与 Node.js 的模块规范非常相近。通过 Sea.js 的 Node.js 版本,可以很方便实现模块的跨服务器和浏览器共享。

模块化开发并不是新鲜事物,但在 Web 领域,前端开发是新生岗位,一直处于比较原始的刀耕火种时代。直到最近两三年,随着 Dojo、YUI3、Node.js 等社区的推广和流行,前端的模块化开发理念才逐步深入人心。

前端的模块化构建可分为两大类。一类是以 Dojo、YUI3、国内的 KISSY 等类库为代表的大教堂模式。在大教堂模式下,所有组件都是颗粒化、模块化的,各组件之间层层分级、环环相扣。另一类是以 jQuery、RequireJS、国内的 Sea.js、OzJS 等类库为基础的集市模式。在集市模式下,所有组件彼此独立、职责单一,各组件通过组合松耦合在一起,协同完成开发。

这两类模块化构建方式各有应用场景。从长远来看,小而美更具备宽容性和竞争力,更能形成有活力的生态圈。

总之,模块化能给前端开发带来很多好处。如果你还没有尝试,不妨从试用 Sea.js 开始。

------------------- ------------------- ------------------- ------------------- ------------------- ------------------- ------------------- ------------------- -------------------

模块话的原理和必要性: 来自某博主的分享和总结

如果你听过js模块化这个东西,那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯,我也听过,但之前也真的是听听而已。

现在就看看吧,这些规范到底是啥东西,干嘛的。

一、CommonJS

CommonJS就是为JS的表现来制定规范,因为js没有模块的功能所以CommonJS应运而生,它希望js可以在任何地方运行,不只是浏览器中。

CommonJS能有一定的影响力,我觉得绝对离不开Node的人气,不过喔,Node,CommonJS,浏览器甚至是W3C之间有什么关系呢,我找到了个贴切的图:

|---------------浏览器----- ------------------|        |--------------------------CommonJS----------------------------------|

|  BOM  |       | DOM |        | ECMAScript |         | FS |           | TCP |         | Stream |        | Buffer |          |........|

|-------W3C-----------|       |---------------------------------------Node--------------------------------------------------|

CommonJS定义的模块分为:{模块引用(require)} {模块定义(exports)} {模块标识(module)}

require()用来引入外部模块;exports对象用于导出当前模块的方法或变量,唯一的导出口;module对象就代表模块本身。

比如说我们就可以这样用了:

1 //sum.js
2 exports.sum = function(){...做加操作..};
3
4 //calculate.js
5 var math = require('sum');
6 exports.add = function(n){
7     return math.sum(val,n);
8 };

虽说Node遵循CommonJS的规范,但是相比也是做了一些取舍,填了一些新东西的。

不过,说了CommonJS也说了Node,那么我觉得也得先了解下NPM了。NPM作为Node的包管理器,不是为了帮助Node解决依赖包的安装问题嘛,那它肯定也要遵循CommonJS规范啦,它遵循包规范(还是理论)的。

CommonJS WIKI讲了它的历史,还介绍了modules和packages等。

二、AMD

CommonJS是主要为了JS在后端的表现制定的,他是不适合前端的,为什么这么说呢?

这需要分析一下浏览器端的js和服务器端js都主要做了哪些事,有什么不同了:

---------------------------------------服务器端JS   |    浏览器端JS-------------------------------------------

相同的代码需要多次执行  |    代码需要从一个服务器端分发到多个客户端执行

CPU和内存资源是瓶颈   |    带宽是瓶颈

加载时从磁盘中加载   |    加载时需要通过网络加载

---------------------------------------------------------------------------------------------------------------

于是乎,AMD(异步模块定义)出现了,它就主要为前端JS的表现制定规范。

AMD就只有一个接口:define(id?,dependencies?,factory);

它要在声明模块的时候制定所有的依赖(dep),并且还要当做形参传到factory中,像这样:

1 define(['dep1','dep2'],function(dep1,dep2){...});

要是没什么依赖,就定义简单的模块,下面这样就可以啦:

1 define(function(){
2     var exports = {};
3     exports.method = function(){...};
4     return exports;
5 });

咦,这里有define,把东西包装起来啦,那Node实现中怎么没看到有define关键字呢,它也要把东西包装起来呀,其实吧,只是Node隐式包装了而已.....

RequireJS就是实现了AMD规范的呢。

这有AMD的WIKI中文版,讲了很多蛮详细的东西,用到的时候可以查看:AMD的WIKI中文版

三、CMD

大名远扬的玉伯写了seajs,就是遵循他提出的CMD规范,与AMD蛮相近的,不过用起来感觉更加方便些,最重要的是中文版,应有尽有:seajs官方doc

1 define(function(require,exports,module){...});

用过seajs吧,这个不陌生吧,对吧。

前面说AMD,说RequireJS实现了AMD,CMD看起来与AMD好像呀,那RequireJS与SeaJS像不像呢?

虽然CMD与AMD蛮像的,但区别还是挺明显的,官方非官方都有阐述和理解,我觉得吧,说的都挺好:

官方阐述SeaJS与RequireJS异同

SeaJS与RequireJS的最大异同(这个说的也挺好)

如转载,敬请注明地址。

JS前端实用开发QQ群 :147250970  欢迎加入~!

入坑方式:   欢迎加入~!气氛热情,欢乐多,妹子多!

web前端 聚集地,汇聚了全国顶尖的web前端热爱者,最新技术,最炫潮流,最靠谱的话题:
  做好现在!技术只是为了改变生活!JS前端实用开发QQ群 :147250970

 扫描屏幕下方的二维码,可以关注 我的前端公众号 。听说妹子挺多的,及时更新一些前端解惑和段子

再谈 JS中的模块规范(CommonJS,AMD,CMD)来自玉伯的seajs分析相关推荐

  1. 关于JavaScript的模块(CommonJS, AMD, CMD, ES6模块)的理解

    Javascript模块化就是解决将代码进行分隔,作用域隔离,模块之间的依赖管理等多个方面问题. 这样的优点不言而喻:1.可维护性2.命名空间私有化,可以避免污染全局环境3.代码重用,通过模块可以方便 ...

  2. 从ES规范和引擎细谈 js 中 parseInt 和 parseFloat 的执行机制

    从ES规范和引擎细谈 js 中 parseInt 和 parseFloat 的执行机制 parseInt()和parseFloat()这两个常用 API 其实还是有很多"坑"的,以 ...

  3. js架构设计模式——由项目浅谈JS中MVVM模式

    1.    背景 最近项目原因使用了durandal.js和knockout.js,颇有受益.决定写一个比较浅显的总结. 之前一直在用SpringMVC框架写后台,前台是用JSP+JS+标签库,算是很 ...

  4. Javascript模块规范(CommonJS规范AMD规范)

    Javascript模块化编程(AMD&CommonJS) 前端模块化开发的价值:https://github.com/seajs/seajs/issues/547 模块的写法 查看 AMD规 ...

  5. 由项目浅谈JS中MVVM模式

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.    背景 最近项目原因使用了durandal.js和knock ...

  6. node.js中模块_在Node.js中需要模块:您需要知道的一切

    node.js中模块 by Samer Buna 通过Samer Buna 在Node.js中需要模块:您需要知道的一切 (Requiring modules in Node.js: Everythi ...

  7. Node.js模块化开发||Node.js中模块化开发规范

    JavaScript开发弊端 a.js b.js JavaScript在使用时存在两大问题,文件依赖和命名冲突. 生活中的模块化开发 软件中的模块化开发 app.j user.一个功能就是一个模块,多 ...

  8. python如何导入requests模块_浅谈python中requests模块导入的问题

    浅谈python中requests模块导入的问题 今天使用Pycharm来抓取网页图片时候,要导入requests模块,但是在pycharm中import requests 时候报错. 原因: pyt ...

  9. 浅谈JS中常见的问题(三)

    往期文章目录 浅谈JS中常见的问题(一) 浅谈JS中常见的问题(二) JS知识总结 往期文章目录 前言 11. 同步和异步的区别 12. JS 判断变量类型的几种方法 13. 如何阻止事件冒泡与默认事 ...

最新文章

  1. styleable cannot be resolved or is not a field
  2. SQL Server Replication 中关于视图的点滴
  3. 数据结构源码笔记(C语言):分块法查找
  4. Scala主构造方法和辅助构造方法
  5. 处理数字_10_计算某列重复最多的值
  6. 配置三台服务器组成的ELK集群(二)
  7. mysql数据库常用备份、恢复命令
  8. ActiveMQ 消息游标(Message Cursors)
  9. capslock键英语怎么读_改造你的CapsLock键,让你效率提升近10倍!
  10. 安装VS2017报错:程序包清单签名验证失败
  11. 修改服务器的maxpostsize的值,maxPostSize属性改变tomcat的post请求的请求体大小设置...
  12. Head First Java(中文版)
  13. 虎牙tv是用php写的吗,huya虎牙php_麦麦同学
  14. 关于Euler-Poisson积分的几种解法
  15. 堆积木——GBQ4.0设置“统一设置安装费用”中的“高层建筑增加费”
  16. Self-Attention with Relative Position Representations(2018)
  17. 最优服务次序问题 水 NOJ1254
  18. MAC 常用终端命令
  19. 19秋计算机应用基础在线作业2,计算机应用基础19秋在线作业2 Windows 下能浏览并管理文件 驱动器及网络连接的强大工具是...
  20. 2019牛客暑期多校训练营(第八场) CDMA

热门文章

  1. 【前端小白教程】资料汇总:VUE、React
  2. java网格布袋布局例子_我的世界Java版 20w45a 发布 加入收纳袋,蜡烛,望远镜等全新道具...
  3. linux shell 当前时间、过去、未来几天的时间设置
  4. 5G和wifi区别,不要搞混了,5G贵,WiFi免费的多
  5. 5Ghz/5.8Ghz 小尺寸SMD贴片天线方案 支持WIFI5G、5.8G无线图传、C-V2X等
  6. 2022浙江嘉兴市南湖区东栅街道招聘社区专职网格员精选试题及答案
  7. js提取新浪邮箱的信用卡
  8. 【源码】螳螂觅食优化MRFO算法仿真
  9. dota里属于英雄们自己的爱情
  10. Borland C++建立工程菜鸟教程