seajs 源码解读

seajs 简朴引见

seajs是前端运用模块化开辟的一种很好的处理计划。关于多人合作开辟的、庞杂巨大的前端项目迥殊有效。简朴的引见不多说,人人能够到seajs的官网seajs.org参看引见。本文主要简朴地解读一下seajs的源码和模块化原理。如果有形貌不实的处所,愿望人人指正和交换。
注:本文的剖析是基于seajs的2.2.1版本。

目次组织

解压seajs今后的src目次组织以下:

intro.js             -- 全局闭包头部
sea.js               -- 基础定名空间

util-lang.js         -- 言语加强
util-events.js       -- 浅易事宜机制
util-path.js         -- 途径处置惩罚
util-request.js      -- HTTP 要求
util-deps.js         -- 依靠提取

module.js            -- 中心代码
config.js            -- 设置
outro.js             -- 全局闭包尾部

src目次寄存主要的seajs源代码。各个文件的作用也如上面所示。个中,module.js是此次源码解读的中心,但我也会顺带引见一下其他文件的作用的。
sea.js对代码比较简朴,实在就是声明一下全局的seajs定名空间。
intro.js和outro.js则是我们熟习的匿名函数包裹基础代码的体式格局,只是这里比较迥殊的是,这段匿名函数被拆分红intro.js和outro.js两个文件。如许的做法重如果轻易调试,在调试的环境下,不援用intro.js和outro.js即能够直接在全局里暴露seajs内部的接口,调试起来比较轻易。intro.js和outro.js兼并起来的代码以下:

(function(global, undefined) {
    if (global.seajs) {
      return
    }
    // ....
})(this);

其他文件的用处就不逐一反复叙说了,看列表即可。

页面如何动态加载js文件

在剖析seajs的源码和原理之前,让我们来回想一下,在没有seajs或许requirejs的情况下,最原始的动态剧本加载要领是如何的。要领很简朴:实在就是建立一个script的标签,设置了src为你想要加载的剧本url,把script标签append到Dom里去就想了,so easy!没错,绝大部份模块加载js库的原理都是云云。

var script = document.createElement('script');
script.setAttribute('src', 'example.js');
script.onload = function() {
    console.log("script loaded!");
};
document.body.appendChild(script);

上述代码即能够完成一次简朴的动态剧本加载。但是,seajs真正的中心在于处置惩罚模块依靠的题目。在前端JS开辟范畴,迥殊是庞杂的web运用,模块依靠题目一直是使人头疼的题目。
很简朴的原理,比方A、B、C、D四个模块对应于A.js、B.js、C.js、D.js四个文件。他们之间的依靠关联比方以下:

  • A 依靠 B
  • B 依靠 C和D

题目在于,如何找出模块里的依靠关联,如何确保A在运行前已加载了B等等。这些都是前端模块化和模块依靠须要处理的题目。

模块化完成思绪

seajs的模块化完成原理,说简朴实在不简朴,说庞杂实在也不是很庞杂。主要思绪能够用下面这一段代码来申明:

Module.define = function (id, deps, factory) {
    // 猎取代码中声明的依靠关联
    deps = parseDependencies(factory.toString());
    // 保留
    Module.save();
    // 婚配到url
    var url = Module.resolve(id);
    // 加载剧本
    script.url = url;
    loadScript();
    // 实行factory并保留模块的援用
    ...
};

猎取代码中声明的依靠

起首我们来看看如何猎取代码中声明须要依靠的模块。平常情况下,seajs中同步加载模块的写法是相似如许的:


define('scripts/a', function(require, exports, module) { var factory = function() { var moduleB = require('scripts/b'); ... }; module.exports = factory; });

那末须要猎取依靠的信息,我们能够借助Function的toString要领,一个函数的toString要领是会返回函数本身的代码的(关于JavaScript本身的函数,会返回[native code])。只须要正则表达式来婚配require关键词背面的援用关联即可。所以seajs中函数parseDependencies的写法就像如许(这一部份代码在util-deps.js):

var SLASH_RE = /\\\\/g
var REQUIRE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^\/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g
function parseDependencies(code) {
  var ret = []
  code.replace(SLASH_RE, "")
        // 婚配require关键词,找出依靠关联
      .replace(REQUIRE_RE, function(m, m1, m2) {
        if (m2) {
          ret.push(m2)
        }
      })
  return ret
}

经由历程id来婚配剧本的url地点

然后找出代码中声明的依靠id,经由历程id来婚配准确的剧本url地点。这一部份的代码在util-path.js

function id2Uri(id, refUri) {
  if (!id) return ""

  id = parseAlias(id)
  id = parsePaths(id)
  id = parseVars(id)
  id = normalize(id)

  var uri = addBase(id, refUri)
  uri = parseMap(uri)

  return uri
}

这里有个迥殊的处所,相似require('a/b/c')如许的写法,seajs是如何晓得剧本地点的绝对途径的呢?原理很简朴,就是经由历程seajs本身往dom里增加的id为’seajsnode’的script节点或许是当前html中末了一个script节点,经由历程这些节点的src属性猎取剧本的绝对途径。

模块加载历程

让我们把目光移回到中心的module.js中。seajs为模块的加载历程定义了6种状况。

var STATUS = Module.STATUS = {
  // 1 - The `module.uri` is being fetched
  FETCHING: 1,
  // 2 - The meta data has been saved to cachedMods
  SAVED: 2,
  // 3 - The `module.dependencies` are being loaded
  LOADING: 3,
  // 4 - The module are ready to execute
  LOADED: 4,
  // 5 - The module is being executed
  EXECUTING: 5,
  // 6 - The `module.exports` is available
  EXECUTED: 6
}

也就是:
* FETCHING 最先加载当前模块
* SAVED 当前模块加载完成并保留模块数据
* LOADING 最先加载依靠的模块
* LOADED 依靠模块已加载完成
* EXECUTING 当前模块实行中
* EXECUTED 当前模块实行完成

实在这一加载实行历程并不是线性的,当前模块在加载所依靠的模块的是,所依靠的模块一样也须要举行这一历程,直到一切的依靠都加载实行终了,当前模块才最先实行。

在module.js中seajs中的一些要领申清楚明了上述全部流程。

  • Module.use 组织一个没有factory的模块,最先全部加载流程,状况初始化为FETCHING到SAVED;
  • Module.prototype.load 经由历程load要领,最先加载子模块,状况由SAVED到LOADING;
  • Module.prototype.onload 当子模块都加载完成后都邑挪用onload要领,状况由LOADING到LOADED;
  • Module.prototype.exec 加载历程都完毕了,最先实行模块,状况由EXECUTING到EXECUTED;

这里每一个要领的细致历程就不逐一剖析,有兴致的同砚能够去看源码。
实际上,seajs会对加载过的模块保留一份援用在cachedMods中,在require的时刻会先挪用缓存中的模块。

seajs.require = function(id) {
  var mod = Module.get(Module.resolve(id))
  if (mod.status < STATUS.EXECUTING) {
    mod.onload()
    mod.exec()
  }
  return mod.exports
}
Module.get = function(uri, deps) {
  return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
}

总结

前端模块化一直是前端开辟中比较主要的一点。前端开辟相对其他言语来讲比较特别,迥殊是对应大型Web项目的前端代码,如何简约文雅地分别模块,如何治理这些模块的依靠题目,这些都须要花肯定的时候去熟悉和讨论。因而,Common.js(致力于设想、计划并标准化 JavaScript API)的降生开启了“ JavaScript 模块化的时期”。前端范畴的模块化计划,像requireJS、SeaJS等都是Common.js的实践者,对我们计划前端的代码很有协助。但是,题目实在另有许多,seajs依旧未能完整满足前端模块化开辟,在机能题目、打包布置等要领另有着不足,不过手艺的将来总在提高,置信今后会有更好的处理要领。

参考

http://island205.github.io/HelloSea.js/
http://seajs.org/docs/#docs
http://chuansongme.com/account/wtp-notes

    原文作者:civerzhu
    原文地址: https://segmentfault.com/a/1190000000471722
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞