`
usenrong
  • 浏览: 515048 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

使用SeaJS实现模块化JavaScript开发

 
阅读更多
SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。与jQuery等JavaScript框架不同,SeaJS不会扩展封装语言特性,而只是实现JavaScript的模块化及按模块加载。SeaJS的主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载,将前端工程师从繁重的JavaScript文件及对象依赖处理中解放出来,可以专注于代码本身的逻辑。SeaJS可以与jQuery这类框架完美集成。使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决目前JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护。

SeaJS的作者是淘宝前端工程师玉伯。

SeaJS本身遵循KISS(Keep It Simple, Stupid)理念进行开发,其本身仅有个位数的API,因此学习起来毫无压力。在学习SeaJS的过程中,处处能感受到KISS原则的精髓——仅做一件事,做好一件事。

本文首先通过一个例子直观对比传统JavaScript编程和使用SeaJS的模块化JavaScript编程,然后详细讨论SeaJS的使用方法,最后给出一些与SeaJS相关的资料。

前言

传统模式 vs SeaJS模块化

        传统开发

        SeaJS模块化开发

使用SeaJS

        下载及安装

        SeaJS基本开发原则

        模块的定义及编写

        模块的载入及引用

        SeaJS的全局配置

        SeaJS如何与现有JS库配合使用

        SeaJS项目打包部署

一个完整的例子

主要参考文献&SeaJS学习资源

传统模式 vs SeaJS模块化
假设我们现在正在开发一个Web应用TinyApp,我们决定在TinyApp中使用jQuery框架。TinyApp的首页会用到module1.js,module1.js依赖module2.js和module3.js,同时module3.js依赖module4.js。

传统开发
使用传统的开发方法,各个js文件代码如下:

?123456789101112131415161718192021222324252627 //module1.js var module1 = {     run: function() {         return $.merge(['module1'], $.merge(module2.run(), module3.run()));     } }   //module2.js var module1 = {     run: function() {         return ['module2'];     } }   //module3.js var module3 = {     run: function() {         return $.merge(['module3'], module4.run());     } }   //module4.js var module4 = {     run: function() {         return ['module4'];     } }

此时index.html需要引用module1.js及其所有下层依赖(注意顺序):

?123456789101112131415161718 <!DOCTYPE HTML> <html lang="zh-CN"> <head>     <meta charset="UTF-8">     <title>TinyApp</title>     <script src="./jquery-min.js"></script>     <script src="./module4.js"></script>     <script src="./module2.js"></script>     <script src="./module3.js"></script>     <script src="./module1.js"></script> </head> <body>     <p class="content"></p>     <script>         $('.content').html(module1.run());     </script> </body> </html>

随着项目的进行,js文件会越来越多,依赖关系也会越来越复杂,使得js代码和html里的script列表往往变得难以维护。

SeaJS模块化开发
下面看看如何使用SeaJS实现相同的功能。

首先是index.html:

?12345678910111213141516 <!DOCTYPE HTML> <html lang="zh-CN"> <head>     <meta charset="UTF-8">     <title>TinyApp</title> </head> <body>     <p class="content"></p>     <script src="./sea.js"></script>     <script>         seajs.use('./init', function(init) {             init.initPage();         });     </script> </body> </html>

可以看到html页面不再需要引入所有依赖的js文件,而只是引入一个sea.js,sea.js会处理所有依赖,加载相应的js文件,加载策略可以选择在渲染页面时一次性加载所有js文件,也可以按需加载(用到时才加载响应js),具体加载策略使用方法下文讨论。

index.html加载了init模块,并使用此模块的initPage方法初始化页面数据,这里先不讨论代码细节。

下面看一下模块化后JavaScript的写法:

?12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 //jquery.js define(function(require, exports, module) = {       //原jquery.js代码...       module.exports = $.noConflict(true); });   //init.js define(function(require, exports, module) = {     var $ = require('jquery');     var m1 = require('module1');           exports.initPage = function() {         $('.content').html(m1.run());         } });   //module1.js define(function(require, exports, module) = {     var $ = require('jquery');     var m2 = require('module2');     var m3 = require('module3');           exports.run = function() {         return $.merge(['module1'], $.merge(m2.run(), m3.run()));         } });   //module2.js define(function(require, exports, module) = {     exports.run = function() {         return ['module2'];     } });   //module3.js define(function(require, exports, module) = {     var $ = require('jquery');     var m4 = require('module4');           exports.run = function() {         return $.merge(['module3'], m4.run());         } });   //module4.js define(function(require, exports, module) = {     exports.run = function() {         return ['module4'];     } });

乍看之下代码似乎变多变复杂了,这是因为这个例子太简单,如果是大型项目,SeaJS代码的优势就会显现出来。不过从这里我们还是能窥探到一些SeaJS的特性:

一是html页面不用再维护冗长的script标签列表,只要引入一个sea.js即可。

二是js代码以模块进行组织,各个模块通过require引入自己依赖的模块,代码清晰明了。

通过这个例子朋友们应该对SeaJS有了一个直观的印象,下面本文具体讨论SeaJS的使用。

使用SeaJS
下载及安装
要在项目中使用SeaJS,你所有需要做的准备工作就是下载sea.js然后放到你项目的某个位置。

SeaJS项目目前托管在GitHub上,主页为https://github.com/seajs/seajs/。可以到其git库的build目录下(https://github.com/seajs/seajs/tree/master/build)下载sea.js(已压缩)或sea-debug.js(未压缩)。

下载完成后放到项目的相应位置,然后在页面中通过<script>标签引入,你就可以使用SeaJS了。

SeaJS基本开发原则
在讨论SeaJS的具体使用前,先介绍一下SeaJS的模块化理念和开发原则。

使用SeaJS开发JavaScript的基本原则就是:一切皆为模块。引入SeaJS后,编写JavaScript代码就变成了编写一个又一个模块,SeaJS中模块的概念有点类似于面向对象中的类——模块可以拥有数据和方法,数据和方法可以定义为公共或私有,公共数据和方法可以供别的模块调用。

另外,每个模块应该都定义在一个单独js文件中,即一个对应一个模块。

下面介绍模块的编写和调用。

模块的定义及编写
模块定义函数define
SeaJS中使用“define”函数定义一个模块。因为SeaJS的文档并没有关于define的完整参考,所以我阅读了SeaJS源代码,发现define可以接收三个参数:

?123456789 /** * Defines a module. * @param {string=} id The module id. * @param {Array.|string=} deps The module dependencies. * @param {function()|Object} factory The module factory function. */fn.define = function(id, deps, factory) {     //code of function… }

上面是我从SeaJS源码中摘录出来的,define可以接收的参数分别是模块ID,依赖模块数组及工厂函数。我阅读源代码后发现define对于不同参数个数的解析规则如下:

如果只有一个参数,则赋值给factory。

如果有两个参数,第二个赋值给factory;第一个如果是array则赋值给deps,否则赋值给id。

如果有三个参数,则分别赋值给id,deps和factory。

但是,包括SeaJS的官方示例在内几乎所有用到define的地方都只传递一个工厂函数进去,类似与如下代码:

?123 define(function(require, exports, module) {     //code of the module... });

个人建议遵循SeaJS官方示例的标准,用一个参数的define定义模块。那么id和deps会怎么处理呢?

id是一个模块的标识字符串,define只有一个参数时,id会被默认赋值为此js文件的绝对路径。如example.com下的a.js文件中使用define定义模块,则这个模块的ID会赋值为“http://example.com/a.js”,没有特别的必要建议不要传入id。deps一般也不需要传入,需要用到的模块用require加载即可。

工厂函数factory解析
工厂函数是模块的主体和重点。在只传递一个参数给define时(推荐写法),这个参数就是工厂函数,此时工厂函数的三个参数分别是:

require——模块加载函数,用于记载依赖模块。

exports——接口点,将数据或方法定义在其上则将其暴露给外部调用。

module——模块的元数据。

这三个参数可以根据需要选择是否需要显示指定。

下面说一下module。module是一个对象,存储了模块的元信息,具体如下:

module.id——模块的ID。

module.dependencies——一个数组,存储了此模块依赖的所有模块的ID列表。

module.exports——与exports指向同一个对象。

三种编写模块的模式
第一种定义模块的模式是基于exports的模式:

?12345678910111213141516 define(function(require, exports, module) {     var a = require('a'); //引入a模块     var b = require('b'); //引入b模块       var data1 = 1; //私有数据       var func1 = function() { //私有方法         return a.run(data1);     }       exports.data2 = 2; //公共数据       exports.func2 = function() { //公共方法         return 'hello';     } });

上面是一种比较“正宗”的模块定义模式。除了将公共数据和方法附加在exports上,也可以直接返回一个对象表示模块,如下面的代码与上面的代码功能相同:

?1234567891011121314151617 define(function(require) {     var a = require('a'); //引入a模块     var b = require('b'); //引入b模块       var data1 = 1; //私有数据       var func1 = function() { //私有方法         return a.run(data1);     }       return {         data2: 2,         func2: function() {             return 'hello';         }     }; });

如果模块定义没有其它代码,只返回一个对象,还可以有如下简化写法:

?123456 define({     data: 1,     func: function() {         return 'hello';     } });

第三种方法对于定义纯JSON数据的模块非常合适。

模块的载入和引用
模块的寻址算法
上文说过一个模块对应一个js文件,而载入模块时一般都是提供一个字符串参数告诉载入函数需要的模块,所以就需要有一套从字符串标识到实际模块所在文件路径的解析算法。SeaJS支持如下标识:

绝对地址——给出js文件的绝对路径。



?1 require("http://example/js/a");

就代表载入“http://example/js/a.js”。

相对地址——用相对调用载入函数所在js文件的相对地址寻找模块。

例如在“http://example/js/b.js”中载入

?1 require("./c");

则载入“http://example/js/c.js”。

基址地址——如果载入字符串标识既不是绝对路径也不是以”./”开头,则相对SeaJS全局配置中的“base”来寻址,这种方法稍后讨论。

注意上面在载入模块时都不用传递后缀名“.js”,SeaJS会自动添加“.js”。但是下面三种情况下不会添加:

载入css时,如

?1 require("./module1-style.css");

路径中含有”?”时,如

?1 require(<A href="http://example/js/a.json?cb=func">http://example/js/a.json?cb=func</A>);

路径以”#”结尾时,如

?1 require("http://example/js/a.json#");

根据应用场景的不同,SeaJS提供了三个载入模块的API,分别是seajs.use,require和require.async,下面分别介绍。

seajs.use
seajs.use主要用于载入入口模块。入口模块相当于C程序的main函数,同时也是整个模块依赖树的根。上面在TinyApp小例子中,init就是入口模块。seajs.use用法如下:

?12345678910111213 //单一模式 seajs.use('./a');   //回调模式 seajs.use('./a', function(a) {   a.run(); });   //多模块模式 seajs.use(['./a', './b'], function(a, b) {   a.run();   b.run(); });

一般seajs.use只用在页面载入入口模块,SeaJS会顺着入口模块解析所有依赖模块并将它们加载。如果入口模块只有一个,也可以通过给引入sea.js的script标签加入”data-main”属性来省略seajs.use,例如,上面TinyApp的index.html也可以改为如下写法:

?1234567891011 <!DOCTYPE HTML> <html lang="zh-CN"> <head>     <meta charset="UTF-8">     <title>TinyApp</title> </head> <body>     <p class="content"></p>     <script src="./sea.js" data-main="./init"></script> </body> </html>

这种写法会令html更加简洁。

require
require是SeaJS主要的模块加载方法,当在一个模块中需要用到其它模块时一般用require加载:

?1 var m = require('/path/to/module/file');

这里简要介绍一下SeaJS的自动加载机制。上文说过,使用SeaJS后html只要包含sea.js即可,那么其它js文件是如何加载进来的呢?SeaJS会首先下载入口模块,然后顺着入口模块使用正则表达式匹配代码中所有的require,再根据require中的文件路径标识下载相应的js文件,对下载来的js文件再迭代进行类似操作。整个过程类似图的遍历操作(因为可能存在交叉循环依赖所以整个依赖数据结构是一个图而不是树)。

明白了上面这一点,下面的规则就很好理解了:

传给require的路径标识必须是字符串字面量,不能是表达式,如下面使用require的方法是错误的:

?123 require('module' + '1');   require('Module'.toLowerCase());

这都会造成SeaJS无法进行正确的正则匹配以下载相应的js文件。

require.async
上文说过SeaJS会在html页面打开时通过静态分析一次性记载所有需要的js文件,如果想要某个js文件在用到时才下载,可以使用require.async:

?123 require.async('/path/to/module/file', function(m) {     //code of callback... });

这样只有在用到这个模块时,对应的js文件才会被下载,也就实现了JavaScript代码的按需加载。

SeaJS的全局配置
SeaJS提供了一个seajs.config方法可以设置全局配置,接收一个表示全局配置的配置对象。具体使用方法如下:

?123456789 seajs.config({     base: 'path/to/jslib/',     alias: {       'app': 'path/to/app/'    },     charset: 'utf-8',     timeout: 20000,     debug: false});

其中base表示基址寻址时的基址路径。例如base设置为”http://example.com/js/3-party/”,则

?1 var $ = require('jquery');

会载入”http://example.com/js/3-party/jquery.js”。

alias可以对较长的常用路径设置缩写。

charset表示下载js时script标签的charset属性。

timeout表示下载文件的最大时长,以毫秒为单位。

debug表示是否工作在调试模式下。

SeaJS如何与现有JS库配合使用
要将现有JS库如jQuery与SeaJS一起使用,只需根据SeaJS的的模块定义规则对现有库进行一个封装。例如,下面是对jQuery的封装方法:

?1234567891011121314151617181920212223 define(function() {   //{{{jQuery原有代码开始 /*!    * jQuery JavaScript Library v1.6.1  * http://jquery.com/  *  * Copyright 2011, John Resig  * Dual licensed under the MIT or GPL Version 2 licenses.  * http://jquery.org/license  *  * Includes Sizzle.js  * http://sizzlejs.com/  * Copyright 2011, The Dojo Foundation  * Released under the MIT, BSD, and GPL Licenses.  *  * Date: Thu May 12 15:04:36 2011 -0400  *///... //}}}jQuery原有代码结束   return $.noConflict(); });

SeaJS项目的打包部署
SeaJS本来集成了一个打包部署工具spm,后来作者为了更KISS一点,将spm拆出了SeaJS而成为了一个单独的项目。spm的核心思想是将所有模块的代码都合并压缩后并入入口模块,由于SeaJS本身的特性,html不需要做任何改动就可以很方便的在开发环境和生产环境间切换。但是由于spm目前并没有发布正式版本,所以本文不打算详细介绍,有兴趣的朋友可以参看其github项目主页https://github.com/seajs/spm/。

其实,由于每个项目所用的JS合并和压缩工具不尽相同,所以spm可能并不是完全适合每个项目。在了解了SeaJS原理后,完全可以自己写一个符合自己项目特征的合并打包脚本。

一个完整的例子
上文说了那么多,知识点比较分散,所以最后我打算用一个完整的SeaJS例子把这些知识点串起来,方便朋友们归纳回顾。这个例子包含如下文件:

index.html——主页面。

sea.js——SeaJS脚本。

init.js——init模块,入口模块,依赖data、jquery、style三个模块。由主页面载入。

data.js——data模块,纯json数据模块,由init载入。

jquery.js——jquery模块,对 jQuery库的模块化封装,由init载入。

style.css——CSS样式表,作为style模块由init载入。

sea.js和jquery.js的代码属于库代码,就不赘述,这里只给出自己编写的文件的代码。

html:

?123456789101112131415 <!DOCTYPE HTML> <html lang="zh-CN"> <head>     <meta charset="UTF-8">     <title></title> </head> <body> <div id="content">     <p class="author"></p>     <p class="blog"><a href="#">Blog</a></p> </div><   <script src="./sea.js" data-main="./init"></script> </body> </html>

javascript:

?123456789101112131415 //init.js define(function(require, exports, module) {     var $ = require('./jquery');     var data = require('./data');     var css = require('./style.css');       $('.author').html(data.author);     $('.blog').attr('href', data.blog); });   //data.js define({     author: 'ZhangYang',     blog: 'http://leoo2sk.cnblogs.com'});

css:

?12 .author{color:red;font-size:10pt;} .blog{font-size:10pt;}

运行效果如下:



主要参考文献&SeaJS学习资源
[1] SeaJS主页 – http://seajs.com

[2] SeaJS的GitHub库(可获取源码) – https://github.com/seajs/seajs

[3] SeaJS作者玉伯的博客 - http://lifesinger.wordpress.com/
分享到:
评论

相关推荐

    模仿seajs模块化加载的自行实现练习

    这是自己编写的模仿seajs模块加载的模块加载器,用于学习交流之用。大致模仿seajs的模块化加载实现。

    JavaScript模块化开发库之SeaJS

    总结,SeaJS是一个强大的JavaScript模块化开发库,通过CMD规范实现了模块化编程,帮助开发者更好地组织和管理代码。了解并熟练运用SeaJS,可以提升项目的可维护性和开发效率,尤其是在大型项目中。通过深入学习其...

    seajs js 模块加载器

    **正文** SeaJS 是一款专为Web端设计的JavaScript模块加载器,它的出现是为了解决JavaScript在浏览器环境中的组织和管理问题。...对于大型Web项目而言,使用SeaJS进行模块化开发无疑是一个明智的选择。

    Javascript模块化编程(三):模块化编程实战,试用SeaJS

    本篇文章将深入探讨JavaScript模块化编程的实战应用,特别是通过试用SeaJS这一工具来实现。SeaJS是一款专注于浏览器端的模块加载器,它遵循CommonJS规范,旨在解决JavaScript在浏览器环境下的模块化问题。 1. 模块...

    通过seajs实现JavaScript的模块开发及按模块加载

    seajs实现了JavaScript 的 模块开发及按模块加载。用来解决繁琐的js命名冲突,文件依赖等问题,其主要目的是令JavaScript开发模块化并可以轻松愉悦进行加载。下面我们来一起深入学习下吧

    SeaJS快速入门,让js代码模块化 - 2011-09-09修订,新添参考资料

    以上是对 SeaJS 的快速入门介绍,通过理解并熟练运用这些概念和方法,开发者可以在浏览器端实现高效、模块化的 JavaScript 开发。在实际项目中,可以进一步探索 SeaJS 的高级特性,如插件、命令行工具等,以提升开发...

    SeaJS(Javascript模块加载框架)v0.9.1免费版

    SeaJS是中国开源社区贡献的一款JavaScript模块加载框架,其主要目标是为Web开发提供一种遵循CommonJS规范的模块化解决方案。这个框架的版本v0.9.1是一个免费版,适用于各种项目开发,帮助开发者更有效地组织和管理...

    SeaJS(Javascript 模块加载框架) v1.0.2.zip

    SeaJS 正是这样一个工具,它遵循了 CommonJS 规范,允许开发者在浏览器环境中实现类似服务器端 Node.js 的模块化开发体验。 CommonJS 是一套定义了模块规范的接口标准,主要应用于服务器端编程,但 SeaJS 将这一...

    JavaScript模块化开发之SeaJS

      SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。使用SeaJS可以提高JavaScript代码的可读性和清晰度,解决目前JavaScript编程中普遍存在的依赖关系混乱和代码...

    SeaJS(Javascript 模块加载框架) v1.0.2

    SeaJS 的出现,使得在前端开发中可以像在服务器端使用Node.js那样,方便地进行模块化的编程。 SeaJS的核心功能包括以下几个方面: 1. **模块定义**:SeaJS 使用 `seajs.use` 和 `define` 方法来定义和加载模块。`...

    seajs下载以及规范

    Seajs作为一个轻量级的前端模块加载器,通过CMD规范实现了模块化开发,有效地解决了JavaScript的组织和依赖管理问题。通过合理配置和使用,可以提高前端开发效率,增强代码可维护性。理解并掌握Seajs的下载、配置、...

    简易实现seajs模块转换成esmodules模块的脚本

    Seajs和ES Modules(ESM)是两种不同的模块加载规范,分别代表了JavaScript模块化发展的不同阶段。Seajs是早期广泛使用的CMD(Common Module Definition)规范的实现,而ES Modules则是现代浏览器和Node.js原生支持...

    JavaScript模块化编程

    尽管JavaScript本身并未直接提供模块化的支持,但社区通过多种方式实现了类似的功能,例如使用`require`、`seajs`等工具和库。 ##### 1.3 模块开发写法 模块开发的不同写法反映了JavaScript模块化演进的过程: 1....

    JavaScript 模块化开发实例详解【seajs、requirejs库使用】

    Seajs和RequireJS是两种常见的JavaScript模块化解决方案库,它们都是基于AMD(Asynchronous Module Definition)规范的实现。 Seajs是一个轻量级的模块加载器,它的核心特性包括: 1. **模块定义**:使用`define`...

    把jQuery的类、插件封装成seajs的模块的方法

    在JavaScript的世界里,Seajs是一个流行的模块加载器,它遵循CommonJS规范,使得代码组织更加模块化。然而,jQuery作为一个广泛使用的库,它的类和插件通常不是为模块化设计的。本文将介绍如何将jQuery及其相关的类...

    seajs-5分钟上手

    Seajs是中国开源社区推出的一款浏览器端的模块加载器,它借鉴了CommonJS的规范,但针对浏览器环境进行了优化,使得JavaScript在浏览器端也能实现模块化的开发。本教程将带你快速了解并掌握Seajs的使用,让你在5分钟...

    seajs官方小实例

    Seajs 是一个用于浏览器端的模块加载器,它遵循 CommonJS 规范,让 JavaScript 开发者能够在浏览器环境中实现模块化开发,提高代码的可维护性和复用性。Seajs 的核心理念是通过模块化解决 JavaScript 开发中的依赖...

    seajs模块加载框架

    SeaJS是一个遵循CommonJS规范的JavaScript模块加载框架,可以实现JavaScript的模块化开发及加载机制。

    seajs源码阅读

    Seajs是一款轻量级的JavaScript模块加载器,它遵循CommonJS规范,使得在浏览器端也能实现模块化的开发。通过阅读Seajs的源码,我们可以深入理解JavaScript模块化的工作原理,以及如何实现一个模块加载器。 在开始...

Global site tag (gtag.js) - Google Analytics