seajs 源码分析一

说明下,写这个不是说明接下去不分析jquery了,只是这两天在准备面试,顺便把seajs给读了,调节下心情。把seajs的源码分析速度写完以后,就会继续jquery。。。

===================================================================================

首先是seajs的大架构:

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传入闭包,方便闭包里的特定对象属性暴露到全局。

接下去这段就是说明把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之类的,源码如下:

//文件夹目录匹配
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
}

 

 

 

 

 

posted @ 2014-03-16 11:39  胖蝎子  阅读(761)  评论(0编辑  收藏  举报