前言
模块化开发JS已经逐渐开始普及,在RequireJS和SeaJS中犹豫了2天,最后选择从SeaJS入手.网上有不少教程和使用方法,不过理论东西太多了,我是标准的实用主义和拿来主义者,所以直接用代码说话.
正文
先上一个最简单的Demo,比官网上的简单无数倍:
从官网下载最新版本的Sea.js文件,我下载的是2014年3月19的2.2.1版:https://github.com/seajs/seajs/archive/2.2.1.zip.
我们就需要dist中的sea.js即可,接下来在dist文件夹同目录下创建demo.html,util.js,componet_one.js这三个文件.
util.js文件中定义一个功能,单独作为一个模块:
//util.js define(function (require,exports,module) { function print (str) { // body... alert(str); } exports.print = print; });
然后定义componet_one.js,它的实现依赖于util.js:
//componet_one.js define(function (require,exports,module) { var util = require("./util.js"); var ComponetOne = { doSth : function(){ util.print("I'm a String.") } } return ComponetOne; })
最后在index.html页面上,直接开始调用:
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>SeaJS</title> </head> <body> <script type="text/javascript" src="dist/sea.js"></script> <script type="text/javascript"> seajs.use("./componet_one.js",function(componet_one){ componet_one.doSth(); }); </script> </body> </html>
页面上显示的结果是弹出一个对话框上面写着:
"I'm a String."
示例完毕,感觉如何,我认为即使对模块化或者Sea.js和RequireJS一点概念都没有,只要有一定的Javascript基础都能理解或者说猜出上面代码的功能和内容.作为理解模块化的入门以及Sea.js应该算是最简单的代码了.
代码虽少,不过还是可以很直观的看出一些东西的,所以通过这几段简单代码来引入一些Sea.js中的API和使用方法.因为Sea.js遵循的是CommonJS规范,所以它的API使用起来虽然看着和RequireJS很像,但还是有所不同,并且它们内部实现也不一样.
(1) CMD模块定义规范
在上面的util.js中通过define()定义了一个模块,可以看到全局函数define就接受了一个函数参数.其实define还可接受对象或者字符串为参数,这时表示模块的接口就是这对象、字符串.
define({"foo" : "bar}); define("It's a template");
这都是可以的,大多数的情况下还是以函数为参数,表示模块的构造方法.执行这个构造方法可以得到模块向外提供的接口,比如上面utils.js中就是如此.这种情况下方法执行时,默认传入三个参数:require、exports以及module.
如果你不知道是否可以在当前开发项目中使用define,可以通过define.cmd进行判断当前页面是否有CMD模块加载器:
if (typeof define === "function" && define.cmd){ //此时表示存在Sea.js等CMD模块加载器 }
(2) 通过exports对象向外提供模块接口
在util.js中define定义的方法体内,有一行代码是exports.print = print;什么都不看的解释是将方法体内定义的print函数指向exports的print属性.
exports是define()定义模块时,Function传入的三个默认参数之一,通过它可以对外提供属性或者方法.
除了给exports对象增加成员,也可以通过return直接向外提供接口,将util.js改为return的方式就如下:
define(function (require,exports,module) { return { print : function(str) { // body... alert(str); } } });
还可以通过module.exports进行赋值,module是define()的参数为函数参数时的函数参数的一个函数.这么说有点费劲,那么我对之前说的define进行一点补充.
标准说法的define,应该是define(factory)作为define的参数,factory可以使一个函数,也可以是一个对象或者字符串.所以说require、exports和module都是factory的默认参数.
util.js通过module.exports的写法如下,效果是一样的:
define(function (require,exports,module) { module.exports = { print : function(str) { // body... alert(str); } } });
exports仅仅是module.exports的一个引用,在factory内部给exports重新赋值时,不会改变module.exports的值,因此给exports赋值是没有效果的,不能用来改变模块接口.这个地方exports和module.exports的关系和Nodejs中是完全一样的.
(3) 存储当前模块属性和方法的module
既然上面以及提到了module,这里就接着往下说,module中最有意义也是需要掌握的就是module.exports,这个在上面说exports的时候提到了.传给factory构造方法的exports参数是module.exports对象的一个引用.仅仅通过exports参数提供接口有时无法满足需求.
例如当模块的接口是某个类的实例时,就要通过module.exports来实现:
define(function (require,exports,module) { //exports 是 module.exports 的一个引用 console.log(module.exports === exports); //true module.exports = new SomeClass(); console.log(module.exports === exports); //false });
(4) 通过require获取模块
通过define、exports以及module的介绍相信util.js中的代码是什么意思并不难理解,然后看一下componet_one.js文件,它也是一个通过define定义的模块,在factory中第一行:var util = require("./util.js");
如果之前用过Nodejs,那么对于require就很熟悉了.它通过一个模块标识作为唯一参数,用来获取模块提供的接口.这里就是获取到了util模块,可以使用util.js文件中定义的内容,这点没什么疑问.
require还有一个实现就是require.async方法用来在模块内部异步加载模块,并在加载完成后执行回调函数.
define(function(require,exports,module){ //异步加载一个模块,加载完成后执行callback require.async('./b',function(b){ b.dosth(); }); //异步加载多个模块,然后执行callback require.async(['./c','./d'],function(c,d){ c.dosth(); d.dosth(); }); });
结尾
类似RequireJS和SeaJS这类模块加载器,它们的实质是为了弥补Javascript在设计之处的不足,可以想象就连Javascript的发明者也没有预料到今天Javascript的火热和普及程度,所以在设计上没有做好文件组织管理这方面的功能.
因为早期它看起来确实就是一种简单的脚本语言,一个页面也就一个JS文件直接引入就好了.但是随着Javascript火爆的发展以及大量的应用,这明显成为其短板,所以广大优秀的Coder们想出了各种办法进行解决.
模块加载主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑.
后续会逐渐写一些更实用的东西,欢迎留言给我,一起学习~