阅读sea.js源码小结

协程星云
• 阅读 2225

sea.js想解决的问题

  1. 恼人的命名冲突

  2. 烦琐的文件依赖

对应带来的好处 Sea.js 带来的两大好处:

  1. 通过 exports 暴露接口。这意味着不需要命名空间了,更不需要全局变量。这是一种彻底的命名冲突解决方案。

  2. 通过 require 引入依赖。这可以让依赖内置,开发者只需关心当前模块的依赖,其他事情 Sea.js 都会自动处理好。对模块开发者来说,这是一种很好的 关注度分离,能让程序员更多地享受编码的乐趣。

API速查

1. seajs.config
2. seajs.use
3. define
4. require
5. require.async
6. exports
7. module.exports

sea.js的执行过程

启动

script标签引入sea.js文件,seajs.config(data)启动配置函数,config函数会会合并所有config配置,seajs.use = function(ids, callback),启用主脚本

运行过程

主脚本启动之后,首先利用request模块请求主脚本(生成script标签插入head标签中),然后根据正则解析模块define的依赖,并对依赖递归解析其依赖。
在运行过程中,通过监听发布者模式,系统内置了8个事件,可用于开发插件。

resolve       -- 将 id 解析成为 uri 时触发
load          -- 开始加载文件时触发
fetch         -- 具体获取某个 uri 时触发
request       -- 发送请求时触发
define         -- 执行 define 方法时触发
exec         -- 执行 module.factory 时触发
config         -- 调用 seajs.config 时触发
error          -- 加载脚本文件出现 404 或其他错误时触发

全局挂载

所有相关数据最后全部挂载在window.seajs下,包括方法及模块数据。

小知识点

exports与module.exports

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

//源码如下
// Exec factory
var factory = mod.factory;

var exports = isFunction(factory) ?
  factory.call(mod.exports = {}, require, mod.exports, mod) :
  factory

关于动态依赖

有时会希望可以使用 require 来进行条件加载:

if (todayIsWeekend)
  require("play");
else
  require("work");

但请牢记,从静态分析的角度来看,这个模块同时依赖 play 和 work 两个模块,加载器会把这两个模块文件都下载下来。 这种情况下,推荐使用 require.async 来进行条件加载。

//sea.js源码如下
require.async = function(ids, callback) { //可传入回调函数
  Module.use(ids, callback, uri + "_async_" + cid())  //——async_英语标识这个脚本是异步加载的,cid用于清除缓存
  return require //返回require方便链式调用
}

在开发时,Sea.js 是如何知道一个模块的具体依赖呢?

a.js

define(function(require, exports) {
  var b = require('./b');
  var c = require('./c');
});

Sea.js 在运行 define 时,接受 factory 参数,可以通过 factory.toString() 拿到源码,再通过正则匹配 require 的方式来得到依赖信息。依赖信息是一个数组,比如上面 a.js 的依赖数组是:['./b', './c']

//源码如下

// Parse dependencies according to the module factory code
if (!isArray(deps) && isFunction(factory)) {  
  deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString()) //parseDependencies是利用正则解析依赖的一个函数
}

时间出发函数Emit

// Emit event, firing all bound callbacks. Callbacks receive the same
// arguments as `emit` does, apart from the event name
var emit = seajs.emit = function(name, data) {
  var list = events[name]

  if (list) {
    // Copy callback lists to prevent modification
    list = list.slice()

    // Execute event callbacks, use index because it's the faster.
    for(var i = 0, len = list.length; i < len; i++) {
      list[i](data)
    }
  }

  return seajs
}

主要看这个部分list = list.slice(),注释是防止拷贝该时间的回调函数,防止修改,困惑了一下。

原因是Javascript中赋值时,对于引用数据类型,都是传地址。
所以这里,如果想防止触发事件的过程中回调函数被更改,必须对这个list数组进行拷贝,而并非只是将list指向events[name]的地址。

根据debug值配置是否删除动态插入的脚本

// Remove the script to reduce memory leak
      if (!data.debug) {
        head.removeChild(node)
      }

这里思考了蛮久,为什么可以删除动态插入的脚本?这样脚本还会生效吗?

首先,必须了解计算机内存分为

  1. 静态数据区 (用来存放程序中初始化的全局变量的一块内存区域)

  2. 代码区 (通常用来存放执行代码的一块内存区域)

  3. 栈区 (栈在进程运行时产生,一个进程有一个进程栈。栈用来存储程序临时存放的局部变量,即函数内定义的变量 不包括static 类型的。函数被调用时,他的形参也会被压栈。

  4. 堆区 (用于存放进程运行中被动态分配的内存段,它的大小并且不固定,可动态扩展。当进程调用malloc等分配内存时,新分配的内存被动态的添加到堆上(堆被扩大),当利用free等函数释放内存时,被释放的‘ 内存从堆中剔除)

这些在Javascript中都被屏蔽了,大部分时候我们都不需要考虑,但是如果要深入了解的话,则是必须要知道的知识。

首先HTML文档中的JS脚本在计算机中作为指令被读入内存,之后开始执行,CPU开始一条一条指令读取,比如,读取到var cool = "wilson"时,就会在内存中分配一个6字符大小的内存,一个function也一样会在内存中占据一定大小。所以,当指令全部运行完之后,指令本身其实已经没有用了,但是仍然给占据了一部分内存。
当你点击按钮触发一个回调函数时,并非去读取指令,而是读取内存中这个回调函数的地址。所以删除这些动态加载的JS文件是没有问题的。

ID 和路径匹配原则

所谓 ID 和路径匹配原则 是指,使用 seajs.use 或 require 进行引用的文件,如果是具名模块(即定义了 ID 的模块),会把 ID 和 seajs.use 的路径名进行匹配,如果一致,则正确执行模块返回结果。反之,则返回 null。

对 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

});

WilsonLiu's blog首发地址:http://blog.wilsonliu.cn

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Karen110 Karen110
4年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
.NET的命名空间
关于Namespace(命名空间)的使用在前面的程序中我们看到,我常用<%@ImportNamespace"System.Data"%,这是在引用M$为我们提供的Namespace,这和ASP不同的,我们在ASP.net必须先引用与我们操作有关的Namespace后才能使用相应的功能。其实说白了,一个Namespace;就是一个组件。这个是关
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
## 码出高效——小组代码规范
码出高效——小组代码规范编程规约一.命名风格1.代码中的命名不能以下划线、美元符号开头或结尾。反例:<fontcolorDC143Csize3\_name/$name/name&/name\_</font2.【强制】代码中的命名严禁使用拼音与英文混合的方式,更不
Stella981 Stella981
3年前
JavaScript 使用闭包保护变量 防止污染
使用JavaScript编写插件或团队协作时,可使用闭包来解决此类以下两个问题:1、定义过多全局变量,可能会造成全局变量命名冲突;2、在插件内定义变量,需要保护该变量不被轻易修改;优点:可以把局部变量驻留在内存中,可以避免使用全局变量;在调用过后不会被垃圾机制回收;缺点:避免滥用闭包,占用更多内存的缺点,用完要及时让垃圾回收器回收(fn
Wesley13 Wesley13
3年前
ES6模块化及优点,简单案例让你秒懂
模块化:模块是一个文件   好处:       1.减少命名冲突       2.避免引入时的层层依赖       3.可以提升执行效率   第一种方法       1.如何导出(暴露)           expor
Wesley13 Wesley13
3年前
C++ 中命名空间的 5 个常见用法
相信小伙伴们对C已经非常熟悉,但是对命名空间经常使用到的地方还不是很明白,这篇文章就针对命名空间这一块做了一个叙述。命名空间在1995年被引入到c标准中,通常是这样定义的:命名空间定义了新的作用域。它们提供了一种避免名称冲突的方法。c中的命名空间通常用于避免命名冲突。尽管命名空间在最近的c代码中广泛使用,但大多数较旧代码都
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这