Sea.Js的运行原理

1.CMD(Common Module Definition)规范

Sea.js采用了和Node相似的CMD规范,使用require、exports和module来组织模块。但Sea.js比起Node的不同点在于,前者的运行环境是在浏览器中,这就导致A依赖的B模块不能同步地读取过来,所以Sea.js比起Node,除了运行之外,还提供了两个额外的东西:

a.模块的管理

b.模块从服务端的同步

即Sea.js必须分为模块加载期和执行期。加载期需要将执行期所有用到的模块从服务端同步过来,在再执行期按照代码的逻辑顺序解析执行模块。本身执行期与node的运行期没什么区别。

Sea.js需要三个接口:define 用来wapper模块,指明依赖,同步依赖;use 用来启动加载期;require 实际上是加载期到执行期的桥梁。

模块标识:模块id的标准参考Module Identifiers,简单说来就是作为一个模块的唯一标识。

Factory:一个可以产生模块的工厂。node中的工厂就是新的运行时,而在Sea.js中(Tea.js中也同样),factory就是一个函数。这个函数接受三个参数。

依赖(Dependencies):依赖就是一个id的数组,即模块所依赖模块的标识

2.依赖加载原理

有很多语言都有模块化的结构,比如c/c++的#include语句,Ruby的require语句等等。模块的执行,必然需要其依赖的模块准备就绪才能顺利执行。
c/c++是编译语言,在预编译时,替换#include语句,将依赖的文件内容包含进来,在编译后的执行期,所有的模块才会开始执行;
而Ruby是解释型语言,在模块执行前,并不知道它依赖什么模块,待到执行到require语句时,执行将暂停,从外部读取并执行依赖,然后再回来继续执行当前模块。
JavaScript作为一门解释型语言,在复杂的浏览器环境中,Sea.js是如何处理CMD模块间的依赖的呢?

a.Node的依赖加载原理

node于Ruby类似,当我们使用node usegreet.js来运行这个模块时,实际上node会构建一个运行的上下文,在这个上下文中运行这个模块。运行到require(‘./greet‘)这句话时,会通过注入的API,在新的上下文中解析greet.js这个模块,然后通过注入的exports或module这两个关键字获取该模块的接口,将接口暴露出来给usegreet.js使用,即通过greet这个对象来引用这些接口。

node的模块方案的特点如下:
(1)使用require、exports和module作为模块化组织的关键字;
(2)每个模块只加载一次,作为单例存在于内存中,每次require时使用的是它的接口;
(3)require是同步的,通俗地讲,就是node运行A模块,发现需要B模块,会停止运行A模块,把B模块加载好,获取的B的接口,才继续运行A模块。如果B模块已经加载到内存中了,当然require B可以直接使用B的接口,否则会通过fs模块化同步地将B文件内存,开启新的上下文解析B模块,获取B的API。
注意:实际上node如果通过fs异步的读取文件的话,require也可以是异步的,所以曾经node中有require.async这个API。

b.Sea.js加载原理

由于在浏览器端,采用与node同样的依赖加载方式是不可行的,因为依赖只有在执行期才能知道,但是此时在浏览器端,我们无法像node一样直接同步地读取一个依赖文件并执行!我们只能采用异步的方式。于是Sea.js的做法是,分成两个时期——加载期和执行期;

加载期:即在执行一个模块之前,将其直接或间接依赖的模块从服务器端同步到浏览器端;
执行期:在确认该模块直接或间接依赖的模块都加载完毕之后,执行该模块。

加载期:不难想见,模块间的依赖就像一棵树。启动模块作为根节点,依赖模块作为叶子节点。下面是pixelegos(一个开源项目)的依赖树:

技术分享

如上图,在页面中通过seajs.use(‘/js/pixelegos‘)调用,目的是执行pixelegos这个模块。Sea.js并不知道pixelegos还依赖于其他什么模块,只是到服务端加载pixelegos.js,将其加载到浏览器端之后,通过分析发现它还依赖于其他的模块,于是Sea.js又去加载其他的模块。随着更多的模块同步到浏览器端后,一棵依赖树才慢慢地通过递归显现出来。

执行期:在执行期,执行也是从根节点开始,本质上是按照代码的顺序结构,对整棵树进行了遍历。有的模块可能已经EXECUTED,而有的还需要执行获取其exports。由于在执行期时,所有依赖的模块都加载好了,所以与node执行过程有点类似。

pixelegos通过同步的require函数获取tool、canvas和menu,后三者同样通过require来执行各自的依赖模块,于是通过这样一个递归的过程,pixelegos就执行完毕了。

打包模块的加载过程:

打包的方式有三种,self,relative和all。
self,只是自己做了transport
relative,将多有相对路径的模块transport,concat
all,包括相对路径模块和库模块(即在seajs-modules文件夹中的),transport,concat

加载方式(以压缩的pixelegos.js为例)
(1)在use时,定义一个匿名的use_模块,依赖于/dist/pixelegos模块,匿名的use_模块load依赖,开始加载pixelegos.js模块;
(2)pixelegos.js加载执行,所有打包在里面的模块被define;
(3)pixelegos.js的onload回调执行,调用/dist/pixelegos模块的load,加载其依赖模块,但依赖的模块都加载好了;
(4)通知匿名的use_加载完成,开始执行期。

3.Sea.js的实现

module.js是Sea.js的核心,Sea.js中为模块定义了六种状态:

FETCHING:开始从服务端加载模块
SAVED:模块加载完成
LOADING:加载依赖模块中
LOADED:依赖模块加载完成
EXECUTING:模块执行中
EXECUTED:模块执行完成

Sea.use调用Module.use构造一个没有factory的模块,该模块即为这个运行期的根节点

// Use function is equal to load a anonymous module
Module.use = function (ids, callback, uri) {
var mod = Module.get(uri, isArray(ids) ? ids: [ids])

mod.callback = function () {var exports = []var uris = mod.resolve()for (var i = 0, len = uris.length; i < len; i++) {exports[i] = cachedMods[uris[i]].exec()}if (callback) {callback.apply(global, exports)}delete mod.callback
}mod.load()

}

模块构造完成,则调用mod.load()来同步其子模块;直接跳过fetching这一步;mod.callback也是Sea.js不纯粹的一点,在模块加载完成后,会调用这个callback。
在load方法中,获取子模块,加载子模块,在子模块加载完成后,会触发mod.onload():

// Load module.dependencies and fire onload when all done
Module.prototype.load = function () {
var mod = this

// If the module is being loaded, just wait it onload call
if (mod.status >= STATUS.LOADING) {return
}mod.status = STATUS.LOADING// Emit `load` event for plugins such as combo plugin
var uris = mod.resolve()
emit("load", uris)var len = mod._remain = uris.length
var m// Initialize modules and register waitings
for (var i = 0; i < len; i++) {m = Module.get(uris[i])if (m.status < STATUS.LOADED) {// Maybe duplicatem._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1}else {mod._remain--}
}if (mod._remain === 0) {mod.onload()return
}// Begin parallel loading
var requestCache = {}for (i = 0; i < len; i++) {m = cachedMods[uris[i]]if (m.status < STATUS.FETCHING) {m.fetch(requestCache)}else if (m.status === STATUS.SAVED) {m.load()}
}// Send all requests at last to avoid cache bug in IE6-9. Issues#808
for (var requestUri in requestCache) {if (requestCache.hasOwnProperty(requestUri)) {requestCache[requestUri]()}
}

}

模块的状态是最关键的,模块状态的流转决定了加载的行为;
是否触发onload是由模块的_remian属性来确定,在load和子模块的onload函数中都对_remain进行了计算,如果为0,则表示模块加载完成,调用onload:

// Call this method when module is loaded
Module.prototype.onload = function () {
var mod = this
mod.status = STATUS.LOADED

if (mod.callback) {mod.callback()
}// Notify waiting modules to fire onload
var waitings = mod._waitings
var uri, mfor (uri in waitings) {if (waitings.hasOwnProperty(uri)) {m = cachedMods[uri]m._remain -= waitings[uri]if (m._remain === 0) {m.onload()}}
}// Reduce memory taken
delete mod._waitings
delete mod._remain

}

模块的_remain和_waitings是两个非常关键的属性,子模块通过_waitings获得父模块,通过_remain来判断模块是否加载完成。
当这个没有factory的根模块触发onload之后,会调用其方法callback,callback是这样的:

mod.callback = function () {
var exports = []
var uris = mod.resolve()

for (var i = 0, len = uris.length; i < len; i++) {exports[i] = cachedMods[uris[i]].exec()
}if (callback) {callback.apply(global, exports)
}delete mod.callback

}

这预示着加载期结束,开始执行期;
而执行期相对比较无脑,首先是直接调用根模块依赖模块的exec方法获取其exports,用它们来调用use传经来的callback。而子模块在执行时,都是按照标准的模块解析方式执行的:

// Execute a module
Module.prototype.exec = function () {
var mod = this

// When module is executed, DO NOT execute it again. When module
// is being executed, just return `module.exports` too, for avoiding
// circularly calling
if (mod.status >= STATUS.EXECUTING) {return mod.exports
}mod.status = STATUS.EXECUTING// Create require
var uri = mod.urifunction require(id) {return Module.get(require.resolve(id)).exec()
}require.resolve = function (id) {return Module.resolve(id, uri)
}require.async = function (ids, callback) {Module.use(ids, callback, uri + "_async_" + cid())return require
}// Exec factory
var factory = mod.factoryvar exports = isFunction(factory) ? factory(require, mod.exports = {},
mod) : factoryif (exports === undefined) {exports = mod.exports
}// Emit `error` event
if (exports === null && ! IS_CSS_RE.test(uri)) {emit("error", mod)
}// Reduce memory leak
delete mod.factorymod.exports = exports
mod.status = STATUS.EXECUTED// Emit `exec` event
emit("exec", mod)return exports

}

注意:var exports = isFunction(factory) ? factory(require, mod.exports = {}, mod) : factory 真的,整个Sea.js就是为了这行代码能够完美运行

4.资源定位

资源定位与模块标识相关,而在Sea.js中有三种模块标识:

普通路径:普通路径与网页中超链接一样,相对于当前页面解析。

相对标识:在define的factory中的相对路径(.. .)是相对标识,相对标识相对当前的URI来解析

顶级标识:不以.或者‘/‘开头的模块标识是顶级标识。

获取真实路径:在Sea.js中,使用data.cwd来代表当前页面的目录;使用data.base来代表sea.js的加载地址。

5.factory依赖分析

在Sea.js的API中,define(factory),并没有指明模块的依赖项,那Sea.js是如何获得的呢。

/**
* util-deps.js - The parser for dependencies
* ref: tests/research/parse-dependencies/test.html
*/

var REQUIRE_RE = /”(?:\”|[^”])“|‘(?:\‘|[^‘])‘|\/*[\S\s]?*\/|\/(?:\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.|.\s*require|(?:^|[^$])\brequire\s*(\s*([“‘])(.+?)\1\s*)/g
var SLASH_RE = /\\/g

function parseDependencies(code) {
var ret = []

code.replace(SLASH_RE, “”)
.replace(REQUIRE_RE, function(m, m1, m2) {
if (m2) {
ret.push(m2)
}
})

return ret
}

Sea.js就是使用REQUIRE_RE在factory的源码中匹配出该模块的依赖项。从REQUIRE_RE这么长的正则来看,这里坑很多;在CommonJS的wrapper方案中可以使用JS语法分析器来获取依赖会更准确。

Sea.Js的运行原理

Sea.Js的运行原理相关推荐

  1. 五、JS单线程运行原理与多线程

    一.进程与线程 进程:正在执行的程序为一个进程.程序的一次运行,它会占用一片独立的内存空间.称为进程线程:线程是进程内一个独立的执行单元.1.一个进程中至少一个运行的线程,称为主线程.进程启动后自动创 ...

  2. 模块化开发之sea.js实现原理总结

    seajs官网说:seajs是一个模块加载器,所以学习它并不难. 在我的理解就是:本来我们是需要手动创建 script标签 引入 js文件的,但用seajs后,它就自动帮我们完成这些工作. 这里只说实 ...

  3. Vue.js 运行原理解析、vue源码剖析

    Vue.js 运行机制全局概览 全局概览 这一节笔者将为大家介绍一下 Vue.js 内部的整个流程,希望能让大家对全局有一个整体的印象,然后我们再来逐个模块进行讲解.从来没有了解过 Vue.js 实现 ...

  4. python webdriver点击指令_测开系列Selenium Webdriver Python(20)--Webdriver运行原理

    Webdriver运行原理 转帖请注明出处!谢谢 在开发Webdriver的自动化脚本过程中,Webdriver后台在创建WebDriver实例的过程中,先确认浏览器的原生组件中是否存在可匹配的版本. ...

  5. 打包工具的配置教程见的多了,但它们的运行原理你知道吗?

    前端模块化成为了主流的今天,离不开各种打包工具的贡献.社区里面对于webpack,rollup以及后起之秀parcel的介绍层出不穷,对于它们各自的使用配置分析也是汗牛充栋.为了避免成为一位" ...

  6. Vue.js 内部运行机制之总结 常见问题解答

    Vue.js 内部运行机制之总结 & 常见问题解答 总结 在本小册的第一节中,笔者对 Vue.js 内部运行机制做了一个全局的概览,当时通过下面这张图把 Vue.js 拆分成一个一个小模块来介 ...

  7. Serverless 实战 —— Serverless 的运行原理与组件架构

    Serverless 的运行原理与组件架构 本文重点探讨下开发者使用 Serverless 时经常遇到的一些问题,以及如何解决 过去一年,我们和大量 Serverless 用户进行了线上和线下的交流, ...

  8. APP自动化测试系列之Appium介绍及运行原理

    VOL 161 23 2020-09 今天距2021年99天 这是ITester软件测试小栈第161次推文 点击上方蓝字"ITester软件测试小栈"关注我,每周一.三.五早上 0 ...

  9. JS 模块化 - 04 CMD 规范与 Sea JS

    1 CMD 规范介绍 CMD: Common Module Definition, 通用模块定义.与 AMD 规范类似,也是用于浏览器端,异步加载模块,一个文件就是一个模块,当模块使用时才会加载执行. ...

最新文章

  1. selenium2与python自动化5-iframe和163邮箱登录
  2. 北斗导航 | GPS原理与接收机设计——琉璃剑之剑气回肠
  3. Java代码示例: 使用reflections工具类获取某接口下所有的实现类
  4. map mybatis 的字段返回0_Mybatis 你踩过坑吗?
  5. paip sms to blog.txt
  6. java内部格式_详解java内部类的访问格式和规则
  7. python封装一个函数并调用_python - 函数的封装与调用
  8. spring 集成mybatis——多数据源切换(附带定时器的配置)
  9. c#解析XML到DATASET及dataset转为xml文件函数
  10. java教程:十分钟理解Java中的弱引用
  11. wordpress如何去掉顶部工具条
  12. pm2 启动 nodejs 项目
  13. 赛马问题--最全面的解析
  14. 机械革命bios升级_¥1500买6年前神舟老战神,3内存+4硬盘升级潜力强,鲁大师15万!...
  15. java获取字符串的最后一个字符_如何获取字符串的最后一个字符
  16. R语言分类算法之距离判别(Distance Discrimination)
  17. java 获取上周开始时间和结束时间,上上周开始和上上周结束时间
  18. mysql5.7架设征途服务器_征途手工架设服务端+配套双客户端+架设流程
  19. 读《麦田里的守望者》(塞林格)
  20. Android项目:基于Android安卓医院挂号预约系统软件app(计算机毕业设计)

热门文章

  1. 微粉助手 v1.3.3
  2. pdfjs-dist pdf转html报错指南
  3. Redis学习笔记:集群
  4. 【Netty】对象重用的秘密:Recycler源码分析
  5. 蓝牙A2DP-android
  6. 河南lvziquan推荐赏桃花的地方
  7. 图形驱动程序和显卡驱动什么区别_什么是集成显卡 集成显卡与独立显区别介绍【详解】...
  8. C++入门篇,类与对象(中篇)
  9. 聊聊前端技术的发展史
  10. win8无法连接微软服务器,修复win8.1提示“无法连接到windows服务”的方案