seajs3.0.0原始碼分析記錄

桃子夭夭發表於2015-10-23

自己邊讀變加了一些註釋,理解了一下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
}

(為了方便理解,原始碼中一些輔助程式碼被我剪下掉了)

 最後附上我寫過註釋的原始碼

相關文章