Seajs源码解析系列(四)

前言:前三章对Seajs的基础应用,核心模块以及路径解析功能都做了介绍,这一章则对Seajs剩下的几项功能做一个综合的介绍。主要包括Seajs事件机制,脚本加载以及模块依赖。
代码解析:

一、Seajs事件机制:

Seajs内部提供了以下几种事件类型:

seajs.on seajs.on(event, callback) 用来添加事件回调。

// 给 fetch 事件添加一个回调
seajs.on('fetch', function(data) {
  ...
});

seajs.off seajs.off(event?, callback?) 用来移除事件回调。

// 从 fetch 事件的回调中移除掉 fn 函数
seajs.off('fetch', fn);

// 移除掉 fetch 事件的所有回调
seajs.off('fetch');

// 移除掉所有事件的所有回调
seajs.off();

seajs.emit seajs.emit(event, data) 用来触发事件。

// 触发 fetch 事件
seajs.emit('fetch', { uri: uri, fetchedList: fetchedList });

以下就是Seajs事件机制的源码,注释十分详细,这里就不多做介绍了。

/**
 * util-events.js - The minimal events support
 */

var events = data.events = {}

// Bind event
seajs.on = function(name, callback) {
  var list = events[name] || (events[name] = [])
  list.push(callback)
  return seajs
}

// Remove event. If `callback` is undefined, remove all callbacks for the
// event. If `event` and `callback` are both undefined, remove all callbacks
// for all events
seajs.off = function(name, callback) {
  // Remove *all* events
  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 {
      delete events[name]
    }
  }

  return seajs
}

// Emit event, firing all bound callbacks. Callbacks receive the same
// arguments as `emit` does, apart from (除了) the event name
//触发事件
//事件的回调函数链保存在seajs.data.events中;
//以事件名称为key,Array对象保存的回调函数链为value
var emit = seajs.emit = function(name, data) {
  var list = events[name]

  if (list) {
    // Copy callback lists to prevent modification
    list = list.slice()

    // Execute event callbacks, use index because it's the faster.
    for(var i = 0, len = list.length; i < len; i++) {
      list[i](data)
    }
  }

  return seajs
}
二、Seajs脚本加载

seajs.request()方法用于从服务端请求对应的模块。

//从服务端请求模块
  function sendRequest() {
    seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset, emitData.crossorigin)
  }

源码如下:当浏览器支持webworker时,直接调用importScript加载函数脚本,否则的话,通过动态创建script标签的方式进行脚本加载。为了防止在IE中的内存泄漏,在脚本加载完onload之后,要及时移除该脚本。

/**
 * util-request.js - The utilities for requesting script and style files
 * ref: tests/research/load-js-css/test.html
 */
//使用webworker加载js脚本
if (isWebWorker) {
  function requestFromWebWorker(url, callback, charset, crossorigin) {
    // Load with importScripts
    var error
    try {
      importScripts(url)
    } catch (e) {
      error = e
    }
    callback(error)
  }
  // For Developers
  seajs.request = requestFromWebWorker
}
//创建script标签的方式加载js脚本
else {
  var doc = document
  var head = doc.head || doc.getElementsByTagName("head")[0] || doc.documentElement
  var baseElement = head.getElementsByTagName("base")[0]

  var currentlyAddingScript

  function request(url, callback, charset, crossorigin) {
    var node = doc.createElement("script")

    if (charset) {
      node.charset = charset
    }

    if (!isUndefined(crossorigin)) {
      node.setAttribute("crossorigin", crossorigin)
    }

    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
  }

  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
      //为了防止在IE中的内存泄漏,在脚本加载完onload之后,会及时移除该脚本
      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

}
三、Seajs模块依赖

Seajs的模块依赖通过方法parseDependencies实现,大体代码逻辑是通过使用正则匹配require的方式得到依赖信息。
涉及到的正则表达式如下所示:

  • /[a-z_$]/i:匹配所有字母,不区分大小写;
  • /^[\w$]+/:匹配所有字母,数字,不区分大小写;
  • /^require\s*\(\s*(['"]).+?\1\s*\)/:测试是否含有require(“xx”);
  • /^require\s*\(\s*['"]/:匹配require关键字;
  • /^[\w$]+(?:\s*\.\s*[\w$]+)*/:匹配第一个单词;
  • /^\.\d+(?:E[+-]?\d*)?\s*/i:获取.之后的整数或者是.之后的以科学计数法表达的数字;
  • /^0x[\da-f]*/i:匹配以0x开头[数字 a-f]的字符串;
  • /^\d+\.?\d*(?:E[+-]?\d*)?\s*/i:匹配小数或者以科学计数法表示的小数

源码如下所示:

/**
 * util-deps.js - The parser for dependencies
 * ref: tests/research/parse-dependencies/test.html
 * ref: https://github.com/seajs/searequire
 */
//通过正则匹配require的方式得到依赖信息
function parseDependencies(s) {
  if(s.indexOf('require') == -1) {
    return []
  }
  var index = 0, peek, length = s.length, isReg = 1, modName = 0, parentheseState = 0, parentheseStack = [], res = []
  while(index < length) {
    readch()
    if(isBlank()) {
    }
    else if(isQuote()) {
      dealQuote()
      isReg = 1
    }
    else if(peek == '/') {
      readch()
      if(peek == '/') {
        index = s.indexOf('\n', index)
        if(index == -1) {
          index = s.length
        }
      }
      else if(peek == '*') {
        index = s.indexOf('*/', index)
        if(index == -1) {
          index = length
        }
        else {
          index += 2
        }
      }
      else if(isReg) {
        dealReg()
        isReg = 0
      }
      else {
        index--
        isReg = 1
      }
    }
    else if(isWord()) {
      dealWord()
    }
    else if(isNumber()) {
      dealNumber()
    }
    else if(peek == '(') {
      parentheseStack.push(parentheseState)
      isReg = 1
    }
    else if(peek == ')') {
      isReg = parentheseStack.pop()
    }
    else {
      isReg = peek != ']'
      modName = 0
    }
  }
  return res
  function readch() {
    //返回指定位置的字符
    peek = s.charAt(index++)
  }
  function isBlank() {
    return /\s/.test(peek)
  }
  function isQuote() {
    return peek == '"' || peek == "'"
  }
  //取出""或者''之间的字符,即依赖模块的名称
  function dealQuote() {
    var start = index
    var c = peek
    var end = s.indexOf(c, start)
    if(end == -1) {
      index = length
    }
    else if(s.charAt(end - 1) != '\\') {
      index = end + 1
    }
    else {
      while(index < length) {
        readch()
        if(peek == '\\') {
          index++
        }
        else if(peek == c) {
          break
        }
      }
    }
    if(modName) {
      res.push(s.slice(start, index - 1))
      modName = 0
    }
  }
  function dealReg() {
    index--
    while(index < length) {
      readch()
      if(peek == '\\') {
        index++
      }
      else if(peek == '/') {
        break
      }
      else if(peek == '[') {
        while(index < length) {
          readch()
          if(peek == '\\') {
            index++
          }
          else if(peek == ']') {
            break
          }
        }
      }
    }
  }
  function isWord() {
    return /[a-z_$]/i.test(peek)
  }
  function dealWord() {
    var s2 = s.slice(index - 1)
      //匹配所有的a-z,A-Z,0-9  @by sxy
    var r = /^[\w$]+/.exec(s2)[0]
    parentheseState = {
      'if': 1,
      'for': 1,
      'while': 1,
      'with': 1
    }[r]
    isReg = {
      'break': 1,
      'case': 1,
      'continue': 1,
      'debugger': 1,
      'delete': 1,
      'do': 1,
      'else': 1,
      'false': 1,
      'if': 1,
      'in': 1,
      'instanceof': 1,
      'return': 1,
      'typeof': 1,
      'void': 1
    }[r]
      //测试是否含有require("xx");
    modName = /^require\s*\(\s*(['"]).+?\1\s*\)/.test(s2)
    if(modName) {
        //匹配require关键字
      r = /^require\s*\(\s*['"]/.exec(s2)[0]
      index += r.length - 2
    }
    else {
      //匹配第一个单词

      index += /^[\w$]+(?:\s*\.\s*[\w$]+)*/.exec(s2)[0].length - 1
    }
  }
  function isNumber() {
    return /\d/.test(peek)
      || peek == '.' && /\d/.test(s.charAt(index))
  }

  function dealNumber() {
    var s2 = s.slice(index - 1)
    var r
    if(peek == '.') {
      //TODO: 获取.之后的整数或者是.之后的以科学计数法表达的数字
      //  /^\.\d+(?:E[+-]?\d*)?\s*/i.exec(".671+22+s2ss21")[0]   ==>.671
      r = /^\.\d+(?:E[+-]?\d*)?\s*/i.exec(s2)[0]
    }
    //匹配以0x开头[数字 a-f]的字符串
    else if(/^0x[\da-f]*/i.test(s2)) {

      r = /^0x[\da-f]*\s*/i.exec(s2)[0]
    }
    else {
      //匹配小数或者以科学计数法表示的小数
      r = /^\d+\.?\d*(?:E[+-]?\d*)?\s*/i.exec(s2)[0]
    }
    index += r.length - 1
    isReg = 0
  }
}
四、后记

这一章由于涉及到的内容比较多,相应的介绍的也比较潦草,其实Seajs的核心代码都已经在前面几章介绍完毕了,本章着重的描写的是其中的方法实现。至此,Seajs源码分析系列到此也要告一段落了,由于接触Seajs时间并不长,对其了解并没有太深入,以上很多见解都只是初窥门径罢了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值