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 相关文章推荐
关于实现代码语法标亮 dp.SyntaxHighlighter
Feb 02 Javascript
jQuery 1.2.x 升? 1.3.x 注意事项
May 06 Javascript
jQuery给多个不同元素添加class样式的方法
Mar 26 Javascript
Bootstrap弹出框(modal)垂直居中的问题及解决方案详解
Jun 12 Javascript
微信小程序  简单实例(阅读器)的实例开发
Sep 29 Javascript
BootStrap 图标icon符号图标glyphicons不正常显示的快速解决办法
Dec 08 Javascript
Vue 2.0 服务端渲染入门介绍
Mar 29 Javascript
jQuery实现轮播图及其原理详解
Apr 12 jQuery
解决vue js IOS H5focus无法自动弹出键盘的问题
Aug 30 Javascript
在Vue组件中获取全局的点击事件方法
Sep 06 Javascript
layui前端时间戳转化实例
Nov 15 Javascript
Vue详细的入门笔记
May 10 Vue.js
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
帅气的琦玉老师
2020/03/02 日漫
php的GD库imagettftext函数解决中文乱码问题
2015/01/24 PHP
php操作redis缓存方法分享
2015/06/03 PHP
PHP实现连接设备、通讯和发送命令的方法
2015/10/13 PHP
利用Fix Rss Feeds插件修复WordPress的Feed显示错误
2015/12/19 PHP
PHP实现的链式队列结构示例
2017/09/15 PHP
php JWT在web端中的使用方法教程
2018/09/06 PHP
PHP实现简易计算器功能
2020/08/28 PHP
PHP实现读取文件夹及批量重命名文件操作示例
2019/04/15 PHP
javascript同页面多次调用弹出层具体实例代码
2013/08/16 Javascript
JavaScript中的正则表达式简明总结
2014/04/04 Javascript
jQuery晃动层特效实现方法
2015/03/09 Javascript
vue.js如何更改默认端口号8080为指定端口的方法
2017/07/14 Javascript
vuejs事件中心管理组件间的通信详解
2017/08/09 Javascript
JavaScript实现打印星型金字塔功能实例分析
2017/09/27 Javascript
React实现全局组件的Toast轻提示效果
2018/09/21 Javascript
如何手动实现es5中的bind方法详解
2018/12/07 Javascript
Nuxt默认模板、默认布局和自定义错误页面的实现
2020/05/11 Javascript
python发送邮件接收邮件示例分享
2014/01/21 Python
python利用7z批量解压rar的实现
2019/08/07 Python
Python udp网络程序实现发送、接收数据功能示例
2019/12/09 Python
python3跳出一个循环的实例操作
2020/08/18 Python
python创建文本文件的简单方法
2020/08/30 Python
浅析Python 责任链设计模式
2020/09/11 Python
python中uuid模块实例浅析
2020/12/29 Python
纯CSS3实现扇形动画菜单(简化版)实例源码
2017/01/17 HTML / CSS
班长岗位职责
2013/11/10 职场文书
函授自我鉴定范文
2014/02/06 职场文书
上班玩手机检讨书
2014/02/17 职场文书
2014法院四风问题对照检查材料思想汇报
2014/10/04 职场文书
大客户经理岗位职责
2015/04/09 职场文书
pytorch MSELoss计算平均的实现方法
2021/05/12 Python
Python机器学习之PCA降维算法详解
2021/05/19 Python
面试中canvas绘制图片模糊图片问题处理
2022/03/13 Javascript
分享一个vue实现的记事本功能案例
2022/04/11 Vue.js
Golang获取List列表元素的四种方式
2022/04/20 Golang