Seajs源码解析系列(四)

来源:互联网 发布:梦泰软件教学 编辑:程序博客网 时间:2024/06/05 15:15

前言:前三章对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 eventseajs.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 eventsseajs.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对象保存的回调函数链为valuevar 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
原创粉丝点击