seajs 源碼分析一

說明下,寫這個不是說明接下去不分析jquery了,只是這兩天在準備面試,順便把seajs給讀了,調節下心情。把seajs的源碼分析速度寫完之後,就會繼續jquery。。。javascript

===================================================================================css

首先是seajs的大架構:java

1 /**
2  * Sea.js 2.2.1 | seajs.org/LICENSE.md
3  */
4 (function(global, undefined) {
5            //some code
6 })(this)

採用了閉包的結構,這樣就不會干擾全局變量。在頁面中的正常引用時,因未指定this具體指向,因此會自動設爲window對象。這樣就把global=window,undefined傳入閉包,方便閉包裏的特定對象屬性暴露到全局。jquery

接下去這段就是說明把seajs變量暴露到全局,並附帶版本號:面試

//若是全局中已經有seajs就直接返回
if (global.seajs) {
  return
}

var seajs = global.seajs = {
  // The current version of Sea.js being used
  version: "2.2.1"
}

var data = seajs.data = {}

 

接下去的內容主要是用於判斷類型的:閉包

function isType(type) {
  return function(obj) {
    return {}.toString.call(obj) == "[object " + type + "]"
  }
}

var isObject = isType("Object")
var isString = isType("String")
var isArray = Array.isArray || isType("Array")
var isFunction = isType("Function")

var _cid = 0
function cid() {
  return _cid++
}

能夠看到,seajs的類型判斷基本是用isType的,它會根據傳入值返回一個函數,這個函數可以判斷接下來須要判斷的類型和傳入的是否相同。其內部調用的是Object.prototype.toString原生函數。架構

cid用於返回特定惟一值。函數

 

接下來是seajs裏的事件機制,事件機制主要是方便插件的開發,在seajs運行到特定步驟時,都會激發一些列事件,插件經過將函數插入這些事件中,作到在特定點運行特定動做,這個原理在《javascript高級程序設計》裏有提到過,源碼以下:源碼分析

var events = data.events = {}

// 綁定事件,將事件放入特定的事件名稱下
seajs.on = function(name, callback) {
  var list = events[name] || (events[name] = [])
  list.push(callback)
  return seajs
}

// 根據name,callback來移除事件
// 若是name和callback都未定義的話,移除全部事件
seajs.off = function(name, callback) {
  // 移除全部事件
  if (!(name || callback)) {
    events = data.events = {}
    return seajs
  }

  var list = events[name]
  if (list) {
    if (callback) {
      // 移除特定事件名稱下的特定函數
      for (var i = list.length - 1; i >= 0; i--) {
        if (list[i] === callback) {
          list.splice(i, 1)
        }
      }
    }
    else {
      // 未指定callback的話,就直接移除全部該事件名稱下的事件
      delete events[name]
    }
  }

  return seajs
}

// 運行事件,將全部該事件名稱下的事件所有跑一遍
var emit = seajs.emit = function(name, data) {
  var list = events[name], fn

  if (list) {
    // 複製事件,防止對原先事件的修改
    list = list.slice()

    // 運行每一項事件
    while ((fn = list.shift())) {
      fn(data)
    }
  }

  return seajs
}

 接下來是seajs中關於路徑處理的,將路徑轉化,加上alias,map之類的,源碼以下:this

//文件夾目錄匹配
var DIRNAME_RE = /[^?#]*\//
// 匹配/./
var DOT_RE = /\/\.\//g
// 匹配/目錄名/../
var DOUBLE_DOT_RE = /\/[^/]+\/\.\.\//
// 匹配雙斜槓
var DOUBLE_SLASH_RE = /([^:/])\/\//g

// 獲得文件夾目錄
// dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/"
function dirname(path) {
  return path.match(DIRNAME_RE)[0]
}

// 路徑轉化
// realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c"
function realpath(path) {
  // /a/b/./c/./d ==> /a/b/c/d
  path = path.replace(DOT_RE, "/")

  // a/b/c/../../d  ==>  a/b/../d  ==>  a/d
  while (path.match(DOUBLE_DOT_RE)) {
    path = path.replace(DOUBLE_DOT_RE, "/")
  }

  // a//b/c  ==>  a/b/c
  path = path.replace(DOUBLE_SLASH_RE, "$1/")

  return path
}

// 轉化id成路徑
// normalize("path/to/a") ==> "path/to/a.js"
// substring比slice和RegExp快
function normalize(path) {
  var last = path.length - 1
  var lastC = path.charAt(last)

  // 若uri以#結尾,則直接返回去掉#後的結果
  if (lastC === "#") {
    return path.substring(0, last)
  }
  // 在最後加.js
  return (path.substring(last - 2) === ".js" ||
      path.indexOf("?") > 0 ||
      path.substring(last - 3) === ".css" ||
      lastC === "/") ? path : path + ".js"
}

// paths vars匹配
var PATHS_RE = /^([^/:]+)(\/.+)$/
var VARS_RE = /{([^{]+)}/g

// 取得相應id下的alias
function parseAlias(id) {
  var alias = data.alias
  return alias && isString(alias[id]) ? alias[id] : id
}

// 根據設置的data.paths轉化id
function parsePaths(id) {
  var paths = data.paths
  var m

  if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) {
    id = paths[m[1]] + m[2]
  }

  return id
}

// 根據設置的vars 轉化id
function parseVars(id) {
  var vars = data.vars

  if (vars && id.indexOf("{") > -1) {
    id = id.replace(VARS_RE, function(m, key) {
      return isString(vars[key]) ? vars[key] : m
    })
  }

  return id
}

// 根據在map中設置的規則轉化uri
function parseMap(uri) {
  var map = data.map
  var ret = uri

  if (map) {
    for (var i = 0, len = map.length; i < len; i++) {
      var rule = map[i]

      ret = isFunction(rule) ?
          (rule(uri) || uri) :
          uri.replace(rule[0], rule[1])

      // 只運行第一條匹配的規則
      if (ret !== uri) break
    }
  }

  return ret
}

// 絕對路徑 根目錄匹配
var ABSOLUTE_RE = /^\/\/.|:\//
var ROOT_DIR_RE = /^.*?\/\/.*?\//

function addBase(id, refUri) {
  var ret
  var first = id.charAt(0)

  // 絕對路徑
  if (ABSOLUTE_RE.test(id)) {
    ret = id
  }
  // 關於相對路徑處理,判斷是否有傳入refUri,不然用data.cwd
  else if (first === ".") {
    ret = realpath((refUri ? dirname(refUri) : data.cwd) + id)
  }
  // 根路徑
  else if (first === "/") {
    var m = data.cwd.match(ROOT_DIR_RE)
    ret = m ? m[0] + id.substring(1) : id
  }
  // 其它
  else {
    ret = data.base + id
  }

  // 若以//開頭,則添加默認的protocol
  if (ret.indexOf("//") === 0) {
    ret = location.protocol + ret
  }

  return ret
}

// id到uri的轉化函數
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
}
相關文章
相關標籤/搜索