seajs3.0.0源码分析记录

自己边读变加了一些注释,理解了一下seajs3.0.0工作的流程。正则没有一个个去理解,插件模块也没看, 以后有时间了可以补充完整~

事件系统中事件队列的获取&定义方法

var list = events[name] || (events[name] = [])

以前自己写都是

if(!events[name]){
    events[name]=[];
}
var list=events[name];

加载模块文件的方法

webworker环境下加载模块文件

获取seajs的加载路径:

var stack;
  try {
    var up = new Error();
    throw up;
  } catch (e) {
    // IE won't set Error.stack until thrown
    stack = e.stack.split('\n');
  }
//  at Error (native) <- Here's your problem
//  at http://localhost:8000/_site/dist/sea.js:2:4334 <- What we want
//  at http://localhost:8000/_site/dist/sea.js:2:8386
//  at http://localhost:8000/_site/tests/specs/web-worker/worker.js:3:1

根据url加载模块,使用的是webworker全局环境下的importScripts方法

function requestFromWebWorker(url, callback, charset) {
    // Load with importScripts
    var error;
    try {
      importScripts(url);
    } catch (e) {
      error = e;
    }
    callback(error);
  }
  // For Developers
  seajs.request = requestFromWebWorker;

浏览器js线程中异步加载模块文件

  // 通过script标签加载脚本
  function request(url, callback, charset) {
    var node = doc.createElement("script")

    if (charset) {
      var cs = isFunction(charset) ? charset(url) : charset
      if (cs) {
        node.charset = cs
      }
    }

    addOnload(node, callback, url)

    node.async = true
    node.src = url

    // For some cache cases in IE 6-8, the script executes IMMEDIATELY after
    // the end of the insert execution, so use `currentlyAddingScript` to
    // hold current node, for deriving url in `define` call
    currentlyAddingScript = node

    // ref: #185 & http://dev.jquery.com/ticket/2709
    baseElement ?
        head.insertBefore(node, baseElement) :
        head.appendChild(node)

    currentlyAddingScript = null
  }
  //添加加载完成之后的处理,包括报错、清除对象、移除dom等操作
  function addOnload(node, callback, url) {
    var supportOnload = "onload" in node

    if (supportOnload) {
      node.onload = onload
      node.onerror = function() {
        emit("error", { uri: url, node: node })
        onload(true)
      }
    }
    else {
      node.onreadystatechange = function() {
        if (/loaded|complete/.test(node.readyState)) {
          onload()
        }
      }
    }

    function onload(error) {
      // Ensure only run once and handle memory leak in IE
      node.onload = node.onerror = node.onreadystatechange = null

      // Remove the script to reduce memory leak
      // 添加了又去除掉
      if (!data.debug) {
        head.removeChild(node)
      }

      // Dereference the node
      node = null

      callback(error)
    }
  }

  // For Developers
  seajs.request = request

核心模块类 Module

Module类定义

//定义module类
function Module(uri, deps) {
  //模块路径
  this.uri = uri
  //这是一个字符串数组,内容是该模块依赖模块的id列表
  this.dependencies = deps || []
  //这是该模块所依赖的模块的一个map key为id,value为Module对象
  this.deps = {} // Ref the dependence modules
  //模块状态
  this.status = 0
  //用来辅助load的一个数组,load完成后会删掉
  this._entry = []
}

 

模块缓存

var cachedMods = seajs.cache = {}

原型方法列表

// 获取模块中依赖模块的uri数组
Module.prototype.resolve 
// 一个辅助模块load过程的函数
Module.prototype.pass
//加载并执行模块
Module.prototype.load
//模块加载完成后触发
Module.prototype.onload
//模块404时候触发
Module.prototype.error
//执行模块
Module.prototype.exec
//用来获取模块文件,每调用一次给一个缓存对象设置一个key value,value是一个函数,调用即开始加载文件

函数方法列表

//通过id获取文件uri
Module.resolve = function(id, refUri)
//用来定义一个模块
Module.define= function (id, deps, factory)
//将id、依赖、 factory 、模块加载状态设置给缓存中的模块
Module.save= function(uri, meta)
////根据uri,从缓存中获取模块,或者以接收的uri和deps参数创建一个模块返回,并加入缓存
Module.get=function(uri, deps)
//用来加载并运行模块
Module.use = function(ids, callback, uri)

暴露出来的API

 1 // Public API
 2 
 3 seajs.use = function(ids, callback) {
 4   //use 实际上创建了一个id为data.cwd + "_use_" + cid()的模块,这个模块的依赖模块是待启动的模块
 5   Module.use(ids, callback, data.cwd + "_use_" + cid())
 6   return seajs
 7 }
 8 
 9 Module.define.cmd = {}
10 global.define = Module.define
11 
12 
13 // For Developers
14 
15 seajs.Module = Module
16 data.fetchedList = fetchedList
17 data.cid = cid
18 
19 seajs.require = function(id) {
20   var mod = Module.get(Module.resolve(id))
21   if (mod.status < STATUS.EXECUTING) {
22     mod.onload()
23     mod.exec()
24   }
25   return mod.exports
26 }
27 
28 /**
29  * config.js - The configuration for the loader
30  */
31 
32 // The root path to use for id2uri parsing
33 data.base = loaderDir
34 
35 // The loader directory
36 data.dir = loaderDir
37 
38 // The loader's full path
39 data.loader = loaderPath
40 
41 // The current working directory
42 data.cwd = cwd
43 
44 // The charset for requesting files
45 data.charset = "utf-8"
46 
47 // data.alias - An object containing shorthands of module id
48 // data.paths - An object containing path shorthands in module id
49 // data.vars - The {xxx} variables in module id
50 // data.map - An array containing rules to map module uri
51 // data.debug - Debug mode. The default value is false
52 
53 seajs.config = function(configData) {
54 
55   for (var key in configData) {
56     var curr = configData[key]
57     var prev = data[key]
58 
59     // Merge object config such as alias, vars
60     if (prev && isObject(prev)) {
61       for (var k in curr) {
62         prev[k] = curr[k]
63       }
64     }
65     else {
66       // Concat array config such as map
67       if (isArray(prev)) {
68         curr = prev.concat(curr)
69       }
70       // Make sure that `data.base` is an absolute path
71       else if (key === "base") {
72         // Make sure end with "/"
73         if (curr.slice(-1) !== "/") {
74           curr += "/"
75         }
76         curr = addBase(curr)
77       }
78 
79       // Set config
80       data[key] = curr
81     }
82   }
83 
84   emit("config", configData)
85   return seajs
86 }
View Code

常用API和特性的理解

seajs.use原理

seajs.use ("index.js")=Module.use(["index.js"],undefined,"http://xxxx.com/xxx/xxx/_use_i")
① 通过Module.get创建模块mod,
② 通过Module.prototype.load加载mod模块,为mode定义history、remain、callback属性,设置_entry
 
Module.prototype.load中
①通过Module.prototype.resolve函数获取模块的依赖模块uri数组。
Module.prototype.resolve会遍历mod对象的dependencies中的模块id,挨个通过Module.resolve函数获取其uri,最后返回数组
② 遍历上一步获取的uri,通过Module.get获取模块,给mod的deps属性设置值,key为依赖模块id,value为依赖模块对象
④ 通过Module.prototype.fetch 获取所有依赖模块的模块文件
⑤ 对子模块重复以上过程
⑥mod模块加载完成后,执行onload回调,在onload中执行Module.use的回调callback,在callback中执行Module.prototype.exec来运行该模块

require、exports、module的注入&懒执行

Module.use的回调callback中会运行模块的Module.prototype.exec ,返回module的exports对象。
这个过程中,若factory是function,则调用factory,参数传入require,mod.exports,mod。
require:
  function require(id) {
    var m = mod.deps[id] || Module.get(require.resolve(id))
    if (m.status == STATUS.ERROR) {
      throw new Error('module was broken: ' + m.uri);
    }
    return m.exec()
  }
注入的过程:
// Exec factory
  var factory = mod.factory
  //执行factory的代码,这个时候才执行,懒执行
  var exports = isFunction(factory) ?
    factory(require, mod.exports = {}, mod) :
    factory
可以看到,require的作用是从缓存中获取模块,然后exec。也就是说,只有require函数被执行的时候,模块代码才会被执行。这就是seajs的as lazy as posible ,懒执行。

global.define

define函数的作用是从参数中获取 id 、dependences 、  factory,然后从模块缓存中获取模块对象,或者创建新模块加入缓存,将id、dependences、factory设置给模块。
贴上关键代码:
Module.define = function (id, deps, factory) {
  var argsLen = arguments.length
  // define(factory)
  if (argsLen === 1) {
    factory = id
    id = undefined
  }
  else if (argsLen === 2) {
    factory = deps
    // define(deps, factory)
    if (isArray(id)) {
      deps = id
      id = undefined
    }
    // define(id, factory)
    else {
      deps = undefined
    }
  }
  // 从function字符串中获取依赖模块id
  if (!isArray(deps) && isFunction(factory)) {
    deps = typeof parseDependencies === "undefined" ? [] : parseDependencies(factory.toString())
  }
  var meta = {
    id: id,
    uri: Module.resolve(id),
    deps: deps,
    factory: factory
  }
  meta.uri ? Module.save(meta.uri, meta) :
    // Save information for "saving" work in the script onload event
    anonymousMeta = meta
}

(为了方便理解,源码中一些辅助代码被我剪切掉了)

 最后附上我写过注释的源码
posted @ 2015-10-23 20:25  桃子夭夭  阅读(1419)  评论(1编辑  收藏  举报