SeaJS入门教程系列之使用SeaJS(二)


Posted in Javascript onMarch 03, 2014

下载及安装

要在项目中使用SeaJS,你所有需要做的准备工作就是下载sea.js然后放到你项目的某个位置。
SeaJS项目目前托管在GitHub上,主页为 https://github.com/seajs/seajs/ 。可以到其git库的build目录下下载sea.js(已压缩)或sea-debug.js(未压缩)。
下载完成后放到项目的相应位置,然后在页面中通过<script>标签引入,你就可以使用SeaJS了。

SeaJS基本开发原则

在讨论SeaJS的具体使用前,先介绍一下SeaJS的模块化理念和开发原则。
使用SeaJS开发JavaScript的基本原则就是:一切皆为模块。引入SeaJS后,编写JavaScript代码就变成了编写一个又一个模块,SeaJS中模块的概念有点类似于面向对象中的类——模块可以拥有数据和方法,数据和方法可以定义为公共或私有,公共数据和方法可以供别的模块调用。
另外,每个模块应该都定义在一个单独js文件中,即一个对应一个模块。
下面介绍模块的编写和调用。

模块的定义及编写

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

/**
* 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的地方都只传递一个工厂函数进去,类似与如下代码:

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时(推荐写法),这个参数就是工厂函数,此时工厂函数的三个参数分别是:
1.require——模块加载函数,用于记载依赖模块。
2.exports——接口点,将数据或方法定义在其上则将其暴露给外部调用。
3.module——模块的元数据。
这三个参数可以根据需要选择是否需要显示指定。
下面说一下module。module是一个对象,存储了模块的元信息,具体如下:
1.module.id——模块的ID。
2.module.dependencies——一个数组,存储了此模块依赖的所有模块的ID列表。
3.module.exports——与exports指向同一个对象。

三种编写模块的模式

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

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上,也可以直接返回一个对象表示模块,如下面的代码与上面的代码功能相同:

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

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

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

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

模块的载入和引用

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

如:

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

就代表载入 http://example/js/a.js 。
相对地址——用相对调用载入函数所在js文件的相对地址寻找模块。
例如在 http://example/js/b.js 中载入
require("./c");

则载入 http://example/js/c.js 。
基址地址——如果载入字符串标识既不是绝对路径也不是以”./”开头,则相对SeaJS全局配置中的“base”来寻址,这种方法稍后讨论。
注意上面在载入模块时都不用传递后缀名“.js”,SeaJS会自动添加“.js”。但是下面三种情况下不会添加:
载入css时,如:
require("./module1-style.css");

路径中含有”?”时,如:
require(<a href="http://example/js/a.json?cb=func">http://example/js/a.json?cb=func</a>);

路径以”#”结尾时,如:
require("http://example/js/a.json#");

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

seajs.use

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

//单一模式
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也可以改为如下写法:
<!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加载:

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

这里简要介绍一下SeaJS的自动加载机制。上文说过,使用SeaJS后html只要包含sea.js即可,那么其它js文件是如何加载进来的呢?SeaJS会首先下载入口模块,然后顺着入口模块使用正则表达式匹配代码中所有的require,再根据require中的文件路径标识下载相应的js文件,对下载来的js文件再迭代进行类似操作。整个过程类似图的遍历操作(因为可能存在交叉循环依赖所以整个依赖数据结构是一个图而不是树)。
明白了上面这一点,下面的规则就很好理解了:
传给require的路径标识必须是字符串字面量,不能是表达式,如下面使用require的方法是错误的:
require('module' + '1');
require('Module'.toLowerCase());

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

require.async

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

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

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

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

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/ ,则:
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的封装方法:

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原理后,完全可以自己写一个符合自己项目特征的合并打包脚本。

 

Javascript 相关文章推荐
不用ajax实现点击文字即可编辑的方法
Dec 16 Javascript
js正则表达式验证邮件地址
Nov 12 Javascript
通过设置CSS中的position属性来固定层的位置
Dec 14 Javascript
D3.js实现直方图的方法详解
Sep 25 Javascript
jQuery展示表格点击变色、全选、删除
Jan 05 Javascript
原生js轮播(仿慕课网)
Feb 15 Javascript
Map.vue基于百度地图组件重构笔记分享
Apr 17 Javascript
Vue中如何实现轮播图的示例代码
Jul 27 Javascript
Vue监听事件实现计数点击依次增加的方法
Sep 26 Javascript
微信小程序实现多个按钮的颜色状态转换
Feb 15 Javascript
使用next.js开发网址缩短服务的方法
Jun 17 Javascript
JavaScript实现轮播图效果
Oct 30 Javascript
Jquery.Form 异步提交表单的简单实例
Mar 03 #Javascript
jquery实现ajax提交form表单的方法总结
Mar 03 #Javascript
SeaJS入门教程系列之SeaJS介绍(一)
Mar 03 #Javascript
js的hasownproperty使用示例
Mar 02 #Javascript
javascript对象的使用和属性操作示例详解
Mar 02 #Javascript
使用js显示当前时间示例
Mar 02 #Javascript
jquery实现背景墙聚光灯效果示例分享
Mar 02 #Javascript
You might like
屏蔽机器人从你的网站搜取email地址的php代码
2012/11/14 PHP
php实现获取及设置用户访问页面语言类
2014/09/24 PHP
PHP实现的简单网络硬盘
2015/07/29 PHP
百万级别知乎用户数据抓取与分析之PHP开发
2015/09/28 PHP
php注册和登录界面的实现案例(推荐)
2016/10/24 PHP
PHP如何根据文件头检测文件类型实例代码
2018/10/14 PHP
PHPStudy下如何为Apache安装SSL证书的方法步骤
2019/01/23 PHP
Ajax+PHP实现的分类列表框功能示例
2019/02/11 PHP
javascript中typeof的使用示例
2013/12/19 Javascript
jQuery中clearQueue()方法用法实例
2014/12/29 Javascript
在JavaScript中操作时间之setYear()方法的使用
2015/06/12 Javascript
jQuery插件zepto.js简单实现tab切换
2015/06/16 Javascript
多种JQuery循环滚动文字图片效果代码
2020/06/23 Javascript
angularJS之$http:与服务器交互示例
2017/03/17 Javascript
微信小程序之数据双向绑定与数据操作
2017/05/12 Javascript
Async Validator 异步验证使用说明
2017/07/03 Javascript
react项目实践之webpack-dev-serve
2018/09/14 Javascript
JS实现分页导航效果
2020/02/19 Javascript
使用npm命令提示: 'npm' 不是内部或外部命令,也不是可运行的程序的处理方法
2020/05/14 Javascript
[01:30]2016国际邀请赛中国区预选赛神秘商店火爆开启
2016/06/26 DOTA
[32:30]夜魇凡尔赛茶话会 第一期01:谁是卧底
2021/03/11 DOTA
Python中使用语句导入模块或包的机制研究
2015/03/30 Python
Python多进程并发(multiprocessing)用法实例详解
2015/06/02 Python
tensorflow学习笔记之简单的神经网络训练和测试
2018/04/15 Python
对python实现合并两个排序链表的方法详解
2019/01/23 Python
numpy.random模块用法总结
2019/05/27 Python
PyTorch学习:动态图和静态图的例子
2020/01/06 Python
详解Pycharm安装及Django安装配置指南
2020/09/15 Python
python 基于DDT实现数据驱动测试
2021/02/18 Python
用HTML5实现手机摇一摇的功能的教程
2012/10/30 HTML / CSS
欧洲当代手工玻璃和瓷器的领先品牌:LSA International
2018/06/03 全球购物
C#可否对内存进行直接的操作
2015/02/26 面试题
百年校庆节目主持词
2014/03/27 职场文书
群众路线领导干部个人对照检查材料(集锦)
2014/09/23 职场文书
政风行风评议工作总结
2014/10/21 职场文书
vue生命周期钩子函数以及触发时机
2022/04/26 Vue.js