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 相关文章推荐
刷新页面实现方式总结(HTML,ASP,JS)
Nov 13 Javascript
javascript基础之查找元素的详细介绍(访问节点)
Jul 05 Javascript
from表单多个按钮提交用onclick跳转不同action
Apr 24 Javascript
jquery.validate.js插件使用经验记录
Jul 02 Javascript
jQuery实现背景弹性滚动的导航效果
Jun 01 Javascript
JS实现可编辑的后台管理菜单功能【附demo源码下载】
Sep 13 Javascript
jQuery实现可拖拽的许愿墙效果【附demo源码下载】
Sep 14 Javascript
学习使用Bootstrap页面排版样式
May 11 Javascript
Angular中$broadcast和$emit的使用方法详解
May 22 Javascript
javascript深拷贝、浅拷贝和循环引用深入理解
May 27 Javascript
编写更好的JavaScript条件式和匹配条件的技巧(小结)
Jun 27 Javascript
vue实现axios图片上传功能
Aug 20 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
PHP获取文件夹内文件数的方法
2015/03/12 PHP
PHP使用pear自带的mail类库发邮件的方法
2015/07/08 PHP
PHP编写文件多服务器同步程序
2016/07/02 PHP
关于ThinkPhp 框架表单验证及ajax验证问题
2017/07/19 PHP
JavaScript 事件属性绑定带参数的函数
2009/03/13 Javascript
javascript两段代码,两个小技巧
2010/02/04 Javascript
JavaScript Event学习第三章 早期的事件处理程序
2010/02/07 Javascript
JS弹出窗口代码大全(详细整理)
2012/12/21 Javascript
jQuery contains过滤器实现精确匹配使用方法
2013/04/12 Javascript
用html+css+js实现的一个简单的图片切换特效
2014/05/28 Javascript
javascript面向对象特性代码实例
2014/06/12 Javascript
JS实用的动画弹出层效果实例
2015/05/05 Javascript
jQuery Dialog对话框事件用法实例分析
2016/05/10 Javascript
一次$.getJSON不执行的简单记录
2016/07/19 Javascript
JavaScript闭包的简单应用
2017/09/01 Javascript
nuxt+axios实现打包后动态修改请求地址的方法
2020/04/22 Javascript
Vue 请求传公共参数的操作
2020/07/31 Javascript
[02:42]岂曰无衣,与子同袍!DOTA2致敬每一位守护人
2020/02/17 DOTA
python遍历数组的方法小结
2015/04/30 Python
python开发之函数定义实例分析
2015/11/12 Python
Python基于列表模拟堆栈和队列功能示例
2018/01/05 Python
使用python 爬虫抓站的一些技巧总结
2018/01/10 Python
pandas按若干个列的组合条件筛选数据的方法
2018/04/11 Python
python 串口读取+存储+输出处理实例
2019/12/26 Python
python使用多线程+socket实现端口扫描
2020/05/28 Python
django模型类中,null=True,blank=True用法说明
2020/07/09 Python
html2canvas生成清晰的图片实现打印的示例代码
2019/09/30 HTML / CSS
瑞典耳机品牌:URBANISTA
2019/12/03 全球购物
幼儿园教师培训制度
2014/01/16 职场文书
关于赌博的检讨书
2014/01/24 职场文书
优秀团员个人事迹材料
2014/01/29 职场文书
产品质量保证书
2014/04/29 职场文书
大学社团计划书
2014/05/01 职场文书
科学发展观活动总结
2014/08/28 职场文书
2014年涉外离婚协议书范本
2014/11/20 职场文书
2014年教研员工作总结
2014/12/23 职场文书