前言:前三章对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时间并不长,对其了解并没有太深入,以上很多见解都只是初窥门径罢了。