珠峰培训

seajs实用教程(二) 使用

作者:

2015-11-23 15:32:05

120

获取seajs

seajs版本的git地址 https://github.com/seajs/seajs

使用git clone方法获取seajs

git clone https://github.com/seajs/seajs/archive/master.zip

2 官网推荐使用 spm 安装:

$ npm install spm -g
$ spm install seajs

学习资料:

官网

Sea.js:简单、极致的模块化Web开发体验

在开始说明seajs之前先了解下seajs开发的原理。

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

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

seajs常用API概览

seajs.config 用来对 Sea.js 进行配置。

seajs.use 用来在页面中加载一个或多个模块。

define 用来定义模块。 

require 用来获取指定模块的接口。

require.async 用来在模块内部异步加载一个或多个模块。

exports 用来在模块内部对外提供接口。

module.exports 用来在模块内部对外提供接口。

下来我们从一个开发者角度来对这些API进行下深入的了解。开发个系统当然,先需要做下系统的配置,我们就了解下seajs.config

seajs.config是对全局进行配置,配置项有下面几种。

base    在解析顶级标识时,会相对 base 路径来解析。
alias   当模块标识很长时,可以使用 alias 来简化。
charset 表示下载js时script标签的charset属性。
timeout 表示下载文件的最大时长,以毫秒为单位。
debug   表示是否工作在调试模式下。
 
这几个都是很常用的配置,举个例子如下
 
seajs.config({
    base: 'path/to/js/',
    alias: {
      'jquery': 'jquery.js'
    },
    charset: 'utf-8',
    timeout: 20000,
    debug: false
});
 
base用来指定根路径,可以指定为绝对路径或者相对路径。
譬如使用require("a") 则加载文件path/to/js/a.js
 
debug:可用的有三种 0|1|2
0: 普通状态,你就当啥也没发生
1: 调试状态,combo 会失效,其他和 0 的时候一样
2: 无缓存状态,所有请求都会自动在后面添加一个时间戳,其他和 1 的时候一样
 
比较不常用的:
 
map方法,可将某个文件映射到另一个。可用于在线调试,非常方便。
map: [
    ['.css', '.css?v=' + version],
    ['.js', '.js?v=' + version]
]
 
preload方法

了解完了配置文件,就说下怎么用呢,怎么加载,从哪入口呢?

seajs.use()方法

seajs.use主要用于载入入口模块。入口模块相当于C程序的main函数,同时也是整个模块依赖树的根。
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,如下所示。
 
<script src="./sea.js" data-main="./init"></script>
 
这种写法会令html更加简洁。

了解了这么多,下来就到了重点了,模块编写。

define(factory) 
是一个全局函数,用来定义模块。
 
接收一个参数 可以使对象 可以使字符串 也可以使函数
 
 
接收两个以上参数 
define(id?, deps?, factory)
字符串 id 表示模块标识,数组 deps 是模块依赖。
 
如果只有一个参数,则赋值给factory。
如果有两个参数,第二个赋值给factory;第一个如果是array则赋值给deps,否则赋值给id。
如果有三个参数,则分别赋值给id,deps和factory。

主要来看工厂函数factory解析

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

require——模块加载函数,用于记载依赖模块。
 
require加载机制,在后面做统一讨论。
 
exports——接口点,将数据或方法定义在其上则将其暴露给外部调用。
module——模块的元数据。
这三个参数可以根据需要选择是否需要显示指定。
 
exports:
exports 是一个对象,用来向外提供模块接口。
除了给 exports 对象增加成员,还可以使用 return 直接向外提供接口。
 
seajs对外提供
 
module是一个对象,存储了模块的元信息,具体如下:
 
module.id——模块的ID。
module.dependencies——一个数组,存储了此模块依赖的所有模块的ID列表。
module.exports——与exports指向同一个对象。

require.async

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

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

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

seajs的API就先讲到这里,对于使用,大家可以看下课时的应用示例。

编写自己的seajs模块

模块编写:

   SeaJS中使用define函数定义一个模块。

factory可以接收三个参数:require, exports, module。

   require——模块加载函数,用于记载依赖模块。
   exports——接口点,将数据或方法定义在其上则将其暴露给外部调用。
   module——模块的元数据。

exports 仅仅是 module.exports 的一个引用。在factory内部给exports重新赋值并不会改变module.exports的值。因此给exports赋值是无效的,不能用来更改模块接口。

对 module.exports 的赋值需要同步执行,不能放在回调函数里。

require 是一个方法,接受 模块标识 作为唯一参数

使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。

require书写约定

  1. 正确拼写 模块 factory 构造方法的第一个参数 必须 命名为 require 。
  2. 不要修改 不要重命名 require 函数,或在任何作用域中给 require 重新赋值。
  3. 使用直接量 require 的参数值 必须 是字符串直接量。

可以把require看成语法关键字就行。

关于模块的几种写法:

第一种是教科书式的写法,也是最常用的一种写法。

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和module的方式:

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

第三种方式:类似于JSON写法,其实就是没有方法的一种写法。

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

现在你也可以来编写自己的cmd模块了,下节我们继续了解怎么改造现有文件成为cmd模块。

如何改造现有文件为 CMD 模块

改造主流模块

这里指的是 jQuery、Moment、Backbone、underscore 等业界主流模块,这些模块一般都有对 AMD 和 CommonJS 的支持代码,例如 jQuery 源文件的最后几行:

if ( typeof module === "object" && module && typeof module.exports === "object" ) {
    module.exports = jQuery;
} else {
    window.jQuery = window.$ = jQuery;
 
    if ( typeof define === "function" && define.amd ) {
        define( "jquery", [], function () { return jQuery; } );
    }
}

还有 Backbone 里:

var Backbone;
if (typeof exports !== 'undefined') {
  Backbone = exports;
} else {
  Backbone = root.Backbone = {};
}

对于有这些兼容代码的,只需要去掉 define.amd 的支持,或是直接包装上 define 就可以了。

define(function(require, exports, module) {
  // code here ...
});

如果没有模块化的兼容代码,有时候需要手动引入依赖,以及暴露对应的接口。

define(function(require, exports, module) {
  var $ = require('$');
  // code here ...
  module.exports = Placeholders;
});

可以参考 cmdjs/gallery 里的 Gruntfile 对这些主流模块的代码替换方式。

现有的 JS 文件

对于一些现有的普通 JS 文件,相对简单的多,参考 CMD 的书写规范,把那些暴露到全局命名空间的接口用 CMD 的方式进行改造就可以了。

比如现有文件 util.js 。

window.util = window.util || {};
util.addClass = function() {
  window.css();
};
util.queryString = function() {};

改为:

define(function(require, exports, module) {
  // 引入依赖
  var css = require('css');
 
  util.addClass = function() {
    css();
  };
  util.queryString = function() {};
 
  // 暴露对应接口
  module.exports = util;
});

这里不啰嗦,就是基本的 CMD 书写规范。实际的改造过程中,情况可以比上面的例子要复杂一些,具体情况具体解决就行。

改造 jQuery 插件

这也是一个经常遇到的问题,我们推荐以下的包装方式:

// jquery-plugin-abc
define(function(require, exports, module) {
  var $ = require('$');
  // 插件的代码
  $.fn.abc = function() {};
});

这样的改造方式实际上是强化了原有的 $ 对象(而不是重新包装出一个新的 $),在实际调用时,可以用下面的方式:

seajs.use(['$', 'jquery-plugin-abc'], function($) {
  // $ 有了 jquery-plugin-abc 所提供的插件功能
  $.abc();
});

更多 jQuery 插件的包装,可以参考 cmdjs/jquery 里的做法。

实在是无法写出比官网还好的教程了,摘自https://github.com/seajs/seajs/issues/971

整体的目录结构:

    ├── index.html
    ├── js
    │   ├── a.js
    │   ├── b.js
    │   └── c.js
    └── lib
        └── seajs
            └── seajs1.3.0.js


其中
    
    index.html:
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>seajs demo</title>
    </head>
    <body>
        <script src="lib/seajs/seajs1.3.0.js"></script>

        <script>
            seajs.config({
                base:"./js/"
            })
            seajs.use(["a"],function(){
                console.log("a.js and b.js saved");
            })
        </script>
    </body>
    </html>
    
    a.js:
    
    define(function(require,exports,module){
        var b = require("b");

        console.log("a.js exec");

        console.log(module);
    })
    
    b.js:
    
    define(function(require,exports,module){
        var b = require("a");
        
        console.log("b.js exec");

        console.log(module);

        var c = require.async("c");

    })
    
    c.js:
    
    define(function(require,exports,module){
        console.log("c.js exec");
        console.log(module);    
    })
    
一个简单的demo就完成了。