0%

hello,SeaJS

hello,SeaJS

为了理解KISS理念(Keep It Simple, Stupid),专门学习并应用SeaJS。

好处

  • 令JavaScript开发模块化并可以轻松愉悦进行加载
  • 可以与jQuery这类框架完美集成
  • 可以提高JavaScript代码的可读性和清晰度
  • 解决目前JavaScript编程中普遍存在的依赖关系混乱和代码纠缠等问题,方便代码的编写和维护
  • 本身仅有个位数的API,因此学习起来毫无压力
  • 遵循MIT 协议,无论个人还是公司,都可以免费自由使用

API文档

官方提供的demo:example

官方文档:doc

经常使用的 API 只有 define, require, require.async, exports, module.exports 这五个。其他 API 有个印象就好,在需要时再来查文档,不用刻意去记。

基础概念

  • 一切都是模块(遵循CMD:Common Module Definition规范)

  • 一个模块一个文件

    且一般开头为:

    define(factory);

    所以在SeaJS中,define是用来定义一个模块的。

    而factory,可以是函数、字符串、json。

    factory 为函数时,表示是模块的构造方法。执行该构造方法,可以得到模块向外提供的接口。factory方法在执行时,默认会传入三个参数:requireexportsmodule

    define(function(require, exports, module) {
    
      // 模块代码
    
    });

    重点来了

    我如果不想按照CMD规范来写可以吗?答案也是可以的,SeaJs也允许我们使用Modules/Transport规范来书写代码,具体的表现形式如下:

    define(id?, deps?, factory) 字符串 id 表示模块标识,数组 deps 是模块依赖。

    define('hello', ['jquery'], function(require, exports, module) {
    
      // 模块代码
    
    });

require

  • 第一个参数require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口。
    define(function(require, exports) {

      // 获取模块 a 的接口
      var a = require('./a');

      // 调用模块 a 的方法
      a.doSomething();

    });

在SeaJS中,我们需要把require看作一个关键词,具体书写规范可以看[require](https://github.com/seajs/seajs/issues/259)
  • require 是同步往下执行,require.async 则是异步回调执行。require.async 一般用来加载可延迟异步加载的模块。

    define(function(require, exports, module) {
    
      // 异步加载一个模块,在加载完成时,执行回调
      require.async('./b', function(b) {
        b.doSomething();
      });
    
      // 异步加载多个模块,在加载完成时,执行回调
      require.async(['./c', './d'], function(c, d) {
        c.doSomething();
        d.doSomething();
      });
    
    });
  • require.resolve(id)该函数不会加载模块,只返回解析后的绝对路径。

    define(function(require, exports) {
    
      console.log(require.resolve('./b'));
      // ==> http://example.com/path/to/b.js
    
    });

exports

  • exports 是一个对象,用来向外提供模块接口。

    define(function(require, exports) {
    
      // 对外提供 foo 属性
      exports.foo = 'bar';
    
      // 对外提供 doSomething 方法
      exports.doSomething = function() {};
    
    });
`exports` 仅仅是 `module.exports` 的一个引用。在 `factory` 内部给 `exports` 重新赋值时,并不会改变 `module.exports` 的值。因此给 `exports` 赋值是无效的,不能用来更改模块接口。        

module

  • module 是一个对象,上面存储了与当前模块相关联的一些属性和方法。

  • module.id & module.url

    module.id: 模块的唯一标识。

    module.uri: 根据模块系统的路径解析规则得到的模块绝对路径。

    一般情况下(没有在 define 中手写 id 参数时),module.id 的值就是 module.uri,两者完全相同。

    module.dependencies: 表示当前模块的依赖

    module.exports: 当前模块对外提供的接口。

  • 传给 factory 构造方法的 exports 参数是 module.exports 对象的一个引用。只通过 exports 参数来提供接口,有时无法满足开发者的所有需求。 比如当模块的接口是某个类的实例时,需要通过 module.exports 来实现:

    define(function(require, exports, module) {
    
      // exports 是 module.exports 的一个引用
      console.log(module.exports === exports); // true
    
      // 重新给 module.exports 赋值
      module.exports = new SomeClass();
    
      // exports 不再等于 module.exports
      console.log(module.exports === exports); // false
    
    });

    注意:对 module.exports 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:

    // x.js
    define(function(require, exports, module) {
    
      // 错误用法
      setTimeout(function() {
        module.exports = { a: "hello" };
      }, 0);
    
    });

    在 y.js 里有调用到上面的 x.js:

    // y.js
    define(function(require, exports, module) {
    
      var x = require('./x');
    
      // 无法立刻得到模块 x 的属性 a
      console.log(x.a); // undefined
    
    });

模块的加载与启动

  • 启动

    <script src="path/to/sea.js">script>
    <script>
     seajs.use('./main');
    </script>
或者这样写:

    // 加载模块 main,并在加载完成时,执行指定回调
     seajs.use('./main', function(main) {
       main.init();
     });

`use` 方法还可以一次加载多个模块:

   // 并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
   seajs.use(['./a', './b'], function(a, b) {
     a.init();
     b.init();
   });
  • seajs.useDOM ready 事件没有任何关系。如果某些操作要确保在 DOM ready 后执行,需要使用 jquery 等类库来保证,比如:

    seajs.use(['jquery', './main'], function($, main) {
      $(document).ready(function() {
        main.init();
      });
    });

配置

seajs.config({

  // 别名配置
  alias: {
    'es5-safe': 'gallery/es5-safe/0.9.3/es5-safe',
    'json': 'gallery/json/1.0.2/json',
    'jquery': 'jquery/jquery/1.10.1/jquery',
    'app/biz': 'http://path/to/app/biz.js',
  },

  // 路径配置
  paths: {
    'gallery': 'https://a.alipayobjects.com/gallery',
    'app': 'path/to/app',
  },

  // 变量配置
  vars: {
    'locale': 'zh-cn'
  },

  // 映射配置
  map: [
    ['http://example.com/js/app/', 'http://localhost/js/app/'],
    ['.js', '-debug.js']
  ],

  // 预加载项
  preload: [
    Function.prototype.bind ? '' : 'es5-safe',
    this.JSON ? '' : 'json'
  ],

  // 调试模式
  debug: true,

  // Sea.js 的基础路径
  base: 'http://example.com/path/to/base/',

  // 文件编码
  charset: 'utf-8'
});

上述代码中对别名进行配之后,再次引入jquery,就可以这样写:

define(function(require, exports, module) {

   var $ = require('jquery');
     //=> 加载的是 http://path/to/base/jquery/jquery/1.10.1/jquery.js

   var biz = require('app/biz');
     //=> 加载的是 http://path/to/app/biz.js

});

上述代码中对路径进行配置后,再次引用可以这样写:

define(function(require, exports, module) {

   var underscore = require('gallery/underscore');
     //=> 加载的是 https://a.alipayobjects.com/gallery/underscore.js

   var biz = require('app/biz');
     //=> 加载的是 path/to/app/biz.js

});

vars 变量配置,就是说模块路径在运行时才能确定的情况下去使用。

 define(function(require, exports, module) {

  var lang = require('./i18n/{locale}.js');
     //=> 加载的是 path/to/i18n/zh-cn.js

});

map配置,可对模块路径进行映射修改,可用于路径转换、在线调试等。

define(function(require, exports, module) {

  var a = require('./a');
     //=> 加载的是 path/to/a-debug.js

});

preload可以在普通模块加载前,提前加载并初始化好指定模块。 注意preload 中的配置,需要等到 use 时才加载。比如:

seajs.config({
  preload: 'a'
});

// 在加载 b 之前,会确保模块 a 已经加载并执行好
seajs.use('./b');

preload 配置不能放在模块文件里面:

debug为true时,加载器不会删除动态插入的 script 标签。插件也可以根据 debug 配置,来决策 log 等信息的输出。

base,Sea.js 在解析顶级标识时,会相对 base 路径来解析

seajs.config 可以多次运行,每次运行时,会对配置项进行合并操作

构建工具

SeaJS的官方文档推荐我们使用spm进行构建,构建完了再将 dist 目录下的文件部署到 sea-modules 目录中,如果你要自定义构建的话再去用grunt云云。

作为一个gulp的粉丝,怎么能够容忍grunt在这里撒野,所以我自己用gulp写了SeaJS的构建脚本。

具体代码请见我的一个git项目starComment