之前有一篇博客非常详细的介绍了sea.js的加载流程,以及源代码实现,链接地址:http://www.cnblogs.com/chaojidan/p/4123980.html

这篇博客我主要讲下sea.js的介绍和使用。

首先,先介绍下sea.js的CMD规范,以及跟其他规范的区别。

CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践。

09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生,逐步形成了三大流派:

  1. Modules/1.x 流派。这个观点觉得 1.x 规范已经够用,只要移植到浏览器端就好。主流代表是服务端的开发人员。
  2. Modules/Async 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范。这个观点下的典型代表是 AMD 规范及其实现 RequireJS
  3. Modules/2.0 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范,但应该尽可能与 Modules/1.x 规范保持一致。这个观点下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者对 CommonJS 的社区的贡献很大,这份 Modules/2.0-draft 规范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 规范,这规范是 CMD 规范的前身。可惜的是 BravoJS 太学院派,FlyScript 后来做了自我阉割,将整个网站(flyscript.org)下线了。

第二流派:AMD 与 RequireJS

AMD 风格下,通过参数传入依赖模块,破坏了就近声明 (需要时,才声明)原则。比如:

define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
    // 等于在最前面声明并初始化了要用到的所有模块
   if (false) {
       // 即便没用到某个模块 b,但 b 还是提前执行了
       b.foo()
   } 
})

第三流派:Modules/2.0  CMD模块

CMD 里,默认推荐的是

define(function(require, exports, module) {     //a,b模块只下载好了,并且只执行了模块中的define方法,而define方法中的function要等到require时,才会执行
  var a = require('a');     //延迟执行了a,b模块
  var b = require('b');        
  // do sth
})

区别:

1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

2. CMD 推崇依赖就近,AMD 推崇依赖前置。看代码:

// CMD

define(function(require, exports, module) {

  var a = require('./a');

  a.doSomething()

  //此处略去 100 行

  var b = require('./b')

  // 依赖可以就近书写

  b.doSomething();

})

// AMD 默认推荐的是

  define(['./a', './b'], function(a, b) {

    // 依赖必须一开始就写好

    a.doSomething();

   // 此处略去 100 行

    b.doSomething();

  })

虽然 AMD 也支持 CMD 的写法,同时还支持将 require 作为依赖项传递,但 RequireJS 的作者默认是最喜欢上面的写法,也是官方文档里默认的模块定义写法。

3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一

比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹

CMD 可以使得构建时的复杂度降低。

目前 Sea.js 拥有 plugin-combo 插件,模块的合并可以放在线上动态做。有些情况下(比较容易出现),动态 combo 的地址会很长:

https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/long-url.js

当 url 地址很长时,超过 2083(好像是这个值),在 IE 以及一些服务器配置下,过长的 url 会出问题。这时经典的解决办法是将 url 拆分成多段:

https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/u.js

https://a.alipaybojects.com/??path/to/f.js,path/to/g.js..................path/to/long-url.js

拆分后,在 CMD 规范下,上面两个 url 可以并发同时请求,谁先返回都没问题。但在 AMD 下,上面的需求,就挂了,很难实现。

你会说 RequireJS 鼓励的是项目上线前,通过构建工具先构建好,不需要线上 combo,也就不会遇到上面的问题。

Sea.js 放得更宽泛,提前合并好,还是线上时才动态 combo,对 CMD 模块来说都可行。很多时候,combo 真心省事,也更自然。前端开发并非处处要像 Java 一样引入严格的构建过程。

CMD 的懒执行策略,也更有利于页面性能。

RequireJS 2.0 后,不少理念也在悄悄地发生着变化,现在好像也支持懒执行了。

然后,介绍下sea.js的方法和使用。

type="text/javascript" src="js/seajs/2.0.0/sea-debug.js?t=123" data-config="sea-js-config.js?t=123"

上面的data-config是指sea.js的配置文件的路径。还有一个属性是data-main,它是项目的起始模块,如果定义了会先执行此模块。data-main是可选项。

首先我们来看看sea-js-config.js

seajs.config({
     // 配置插件
   plugins: ['shim'],
     // 配置别名
   alias: {
       // 配置 jquery 的 shim 配置,这样我们就可以通过 require('jquery') 来获取 jQuery
     'jquery': {
          src: 'libs/jquery/1.9.1/jquery.js',  //注意,这里是从sea.js所在目录的上两节目录开始查找文件
            exports: 'jQuery'
       }
   }
});

plugins选项配置插件,这里使用了shim插件。由于jquery不是一个标准的CMD模块,所以直接加载jquery是错误的。这里使用了shim插件,会自动把jquery转换成一个标准的CMD模块。不用人工改动jquery源码。alias是配置别名,方便加载的。

看个例子:

项目主模块app.js
define(function(require, exports, module) {
    //加载jquery, 并把它$设为全局变量
   window.$ = window.jQuery = $ = require('jquery');
      //定义一个全局的模块加载函数.module为模块名,options为参数
    exports.script_load = function(module, options) {
        //使用require.async异步加载模块。模块需要提供一个固定的对外调用接口,这里定义为run。
        require.async('modules/' + module, function(module) {
       if (typeof(module.run) === 'function') {
                  module.run(options);
       }
         });
    }
    window.script_load = exports.script_load
});
上面我们加载了jquery, 并且定义了一个模块加载函数。现在我们在html页面加入如下代码:
<script type="text/javascript">
     seajs.use('modules/app', function(app) {
           app.script_load('index');
  });
</script>
use方法执行时,会先加载app模块,加载并执行完后,就进入function中,这时就会调用app.script_load方法,此方法就会去加载index模块,加载完成后,执行index中的代码,index中会返回run方法。index执行完毕后,会调用require.async的回调方法:

if (typeof(module.run) === 'function') {
                  module.run(options);
}

因此index模块中返回了run方法,因此就执行index中的run方法。

index.js
define(function(require, exports, module) {
   exports.run = function() {
         $('#alert').click(function() {
        alert('弹出了一个框!');
         });
  }
});

SeaJS中使用“define”函数定义一个模块,define可以接收三个参数,
define可以接收的参数分别是模块ID,依赖模块数组及工厂函数。
我阅读源代码后发现define对于不同参数个数的解析规则如下:
如果只有一个参数,则赋值给factory。
如果有两个参数,第二个赋值给factory;第一个如果是array则赋值给deps,否则赋值给id。
如果有三个参数,则分别赋值给id,deps和factory。
id是一个模块的标识字符串,define只有一个参数时,id会被默认赋值为此js文件的绝对路径。
如example.com下的a.js文件中使用define定义模块,则这个模块的ID会赋值为 http://example.com/a.js ,没有特别的必要建议不要传入id。deps一般也不需要传入,需要用到的模块用require加载即可。
工厂函数function是模块的主体和重点。在只传递一个参数给define时(推荐写法),这个参数就是工厂函数,此时工厂函数的三个参数分别是:
• require——模块加载函数,用于记载依赖模块。
• exports——接口点,将数据或方法定义在其上则将其暴露给外部调用。
• module——模块的元数据。
module是一个对象,存储了模块的元信息,具体如下:
• module.id——模块的ID。
• module.dependencies——一个数组,存储了此模块依赖的所有模块的ID列表。
• 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, exports, module) {
  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';

}

});

绝对地址——给出js文件的绝对路径。如

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

就代表载入 http://example/js/a.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主要用于载入入口模块。入口模块相当于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,例如,

<!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>

传给require的路径标识必须是字符串字面量,不能是表达式,如下面使用require的方法是错误的:
 require('module' + '1');
 require('Module'.toLowerCase());
这都会造成SeaJS无法进行正确的正则匹配以下载相应的js文件。
上文说过SeaJS会在html页面打开时通过静态分析,一次性下载所有需要的js文件,如果想要某个js文件在用到时才下载,可以使用require.async。

require.async('/path/to/module/file', function(m) {
  //code of callback...
});
这样只有在用到这个模块时,对应的js文件才会被下载,也就实现了JavaScript代码的按需加载。

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表示是否工作在调试模式下。
要将现有JS库如jQuery与SeaJS一起使用,只需根据SeaJS的的模块定义规则对现有库进行一个封装。例如,下面是对jQuery的封装方法:
define(function() {

   //{{{jQuery原有代码开始

   //}}}jQuery原有代码结束

  return $.noConflict();
});

特别注意:下面这种写法是错误的!
define(function(require, exports) {

  // 错误用法!!!
  exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});
正确的写法是用 return 或者给 module.exports 赋值:
define(function(require, exports, module) {

  // 正确写法
  module.exports = {
    foo: 'bar',
    doSomething: function() {}
  };

});
提示:exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 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 的赋值需要同步执行,不能放在回调函数里。下面这样是不行的:

define(function(require, exports, module) {

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

});
seajs.config({
  alias: {
  'jquery': 'jquery/1.7.2/jquery-debug.js'
}
});

seajs.use(['./a','jquery'],function(a,$){
  var num = a.a;
  $('#J_A').text(num);
})

use方法将会从我们的config配置信息中查看 ,是否有预先需要被加载的模块。如果有,就先加载,没有就加载a和jquery模块。

seajs的CMD模式的优势以及使用的更多相关文章

  1. 神马是代码简单的cmd模式,这就是!

    小狼正在研究 “怎么查找连在一起的同色方块?”算法问题 ,突然感觉我是不是需要一种开发模式,不然感觉自己的代码好乱的. 可能是研究算法吧,导致小狼的思路特别清晰,加上也用了差不多1年的nodejs.s ...

  2. 关于cmd模式下切换目录

    cmd下切换目录: 经常犯下的错误一: 在默认路径下输入 cd D: 想切换到D盘但是会出现上面的现象. 正确的的做法是直接输入要转移到的盘符: D:  就可以了. 在这种情况下再输入cd D:

  3. 在cmd模式下对mysql的操作语句

    A.window下的语句 1.mysqld -install     //安装mysql服务 2.mysqld -remove //卸载mysql服务 3.net start mysql //启动服务 ...

  4. MySQL学习笔记(cmd模式下的操作)

    1.登入MySQL 1.1 登入MySQL 1.1.1命令如下: C:\Users\zjw>mysql -hlocalhost -uroot -p Enter password: ****** ...

  5. seajs构建web申请书

    随着开发项目的不断扩大,查找代码依赖关系复杂化,维护比较沉闷.记seajs有这种效果方面.果断尝鲜.解决两个问题:1)命名冲突 2)文件相关性 因为所在BG使用TAF服务,基于C++开发一套WSP w ...

  6. 模块化的JavaScript开发的优势在哪里

    如今模块化的 JavaScript 的开发越来越火热,无论是模块加载器还是优秀的 JavaScript 模块,都是层出不穷.既然这么火,肯定是有存在的理由,肯定是解决了某些实际问题.很多没接触过模块化 ...

  7. seajs学习一天后的总结归纳

    公司项目最近需要将js文件迁移到seajs来进行模块化管理,由于我以前主要接触模块化开发是接触的AMD规范的requireJS,没有接触过CMD规范,而且在实际项目中还没有用过类似技术.于是,我非常兴 ...

  8. seajs源码分析

    seajs主要做了2件事 1.定义什么是模块,如何声明模块:id.deps.factory.exports ----define=function(id,deps,factory){return ex ...

  9. seajs初尝 加载jquery返回null解决学习日志含示例下载

    原文地址:http://www.tuicool.com/articles/bmuaEb 如需demo示例,请点击下方链接下载: http://yunpan.cn/cVEybKs8nV7CF  提取码 ...

随机推荐

  1. ruby 生成有条件限制的随机数

    #conding:utf-8 #生成只有数字的随机码可控制长度def random_int(len) newpass = "" 1.upto(len){ |i| newpass & ...

  2. 黄聪:日租VPS中FileZilla_Server配置方法

    1.关闭VPS中IIS的FTP服务 2.FileZilla_Server 监听端口 21 3.FTP客户端端口为11311(看服务商给出的)

  3. 【学】ECMA6的新特性1

    ECMA6的新特性1 let特性: 1.不允许重复声明 2.没有预解析 3.块级作用域 一对{}包括的区域称为代码块 块级作用域指一个变量或者函数只在该区域才起作用. 例1: console.log( ...

  4. 如何从eclipse中下载并导入Github上的项目

    eclipse导入项目,方法就是点击File ->Import,选择Existing Projects into Workspace 但前提是,你导入的这个项目原本就是用eclipse的构建的, ...

  5. Linux的一些常用快捷键和基本命令

    *******1.在Linux中,只有/能够当盘符,/首先要分配给系统盘所在分区*******2.swap交换分区,相当于Windows下的虚拟内存,用来模拟内存,当内存不够用时,就会使用交换分区.其 ...

  6. Volley网络框架的使用

    Volley的特点:   使用网络通信更快.更简单 Get/Post网络请求网络图像的高效率异步请求 可以对网络请求的优先级进行排序处理 可以进行网络请求的缓存 可以取消多级别请求 可以和Activi ...

  7. 在Eclipse中使用JUnit4进行单元测试(中级篇)

    我们继续对初级篇中的例子进行分析.初级篇中我们使用Eclipse自动生成了一个测试框架,在这篇文章中,我们来仔细分析一下这个测试框架中的每一个细节,知其然更要知其所以然,才能更加熟练地应用JUnit4 ...

  8. Codeforces Round #381 (Div. 2)D. Alyona and a tree(树+二分+dfs)

    D. Alyona and a tree Problem Description: Alyona has a tree with n vertices. The root of the tree is ...

  9. osgi笔记

    Bundle-Classpath可以实现内嵌jar. 一个Bundle的Activator不需要进行Export 一个Package中的类被两个ClassLoader加载,包中的Private cla ...

  10. c++中的引用与指针的区别

    http://blog.csdn.net/lyd_253261362/article/details/4323691 c++中的引用与指针的区别 ★ 相同点: 1. 都是地址的概念: 指针指向一块内存 ...