这里是seajs loader的核心部分,有些IE兼容的部分还不是很明白,大虾路过的话,求教~
主要是理解各个模块如何依赖有序加载,以及CMD规范。
代码有点长,需要耐心看:
1 /** 2 * The core of loader 3 */ 4 ;(function(seajs, util, config) { 5 // 模块缓存 6 var cachedModules = {} 7 // 接口修改缓存 8 var cachedModifiers = {} 9 // 编译队列 10 var compileStack = [] 11 // 模块状态 12 var STATUS = { 13 'FETCHING': 1, // The module file is fetching now. 模块正在下载中 14 'FETCHED': 2, // The module file has been fetched. 模块已下载 15 'SAVED': 3, // The module info has been saved. 模块信息已保存 16 'READY': 4, // All dependencies and self are ready to compile. 模块的依赖项都已下载,等待编译 17 'COMPILING': 5, // The module is in compiling now. 模块正在编译中 18 'COMPILED': 6 // The module is compiled and module.exports is available. 模块已编译 19 } 20 21 22 function Module(uri, status) { 23 this.uri = uri 24 this.status = status || 0 25 26 // this.id is set when saving 27 // this.dependencies is set when saving 28 // this.factory is set when saving 29 // this.exports is set when compiling 30 // this.parent is set when compiling 31 // this.require is set when compiling 32 } 33 34 35 Module.prototype._use = function(ids, callback) { 36 //转换为数组,统一操作 37 util.isString(ids) && (ids = [ids]) 38 // 使用模块系统内部的路径解析机制来解析并返回模块路径 39 var uris = resolve(ids, this.uri) 40 41 this._load(uris, function() { 42 // Loads preload files introduced in modules before compiling. 43 // 在编译之前,再次调用preload预加载模块 44 // 因为在代码执行期间,随时可以调用seajs.config配置预加载模块 45 preload(function() { 46 // 编译每个模块,并将各个模块的exports作为参数传递给回调函数 47 var args = util.map(uris, function(uri) { 48 return uri ? cachedModules[uri]._compile() : null 49 }) 50 51 if (callback) { 52 // null使回调函数中this指针为window 53 callback.apply(null, args) 54 } 55 }) 56 }) 57 } 58 59 // 主模块加载依赖模块(称之为子模块),并执行回调函数 60 Module.prototype._load = function(uris, callback) { 61 // 过滤uris数组 62 // 情况一:缓存中不存在该模块,返回其uri 63 // 情况二:缓存中存在该模块,但是其status < STATUS.READY(即还没准备好编译) 64 var unLoadedUris = util.filter(uris, function(uri) { 65 return uri && (!cachedModules[uri] || 66 cachedModules[uri].status < STATUS.READY) 67 }) 68 69 var length = unLoadedUris.length 70 // 如果length为0,表示依赖项为0或者都已下载完成,那么执行回调编译操作 71 if (length === 0) { 72 callback() 73 return 74 } 75 76 var remain = length 77 78 for (var i = 0; i < length; i++) { 79 // 闭包,为onFetched函数提供上下文环境 80 (function(uri) { 81 // 创建模块对象 82 var module = cachedModules[uri] || 83 (cachedModules[uri] = new Module(uri, STATUS.FETCHING)) 84 //如果模块已下载,那么执行onFetched,否则执行fetch操作(请求模块) 85 module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched) 86 87 function onFetched() { 88 // cachedModules[uri] is changed in un-correspondence case 89 module = cachedModules[uri] 90 // 如果模块状态为SAVED,表示模块的依赖项已经确定,那么下载依赖模块 91 if (module.status >= STATUS.SAVED) { 92 // 从模块信息中获取依赖模块列表,并作循环依赖的处理 93 var deps = getPureDependencies(module) 94 // 如果存在依赖项,继续下载 95 if (deps.length) { 96 Module.prototype._load(deps, function() { 97 cb(module) 98 }) 99 } 100 // 否则直接执行cb 101 else { 102 cb(module) 103 } 104 } 105 // Maybe failed to fetch successfully, such as 404 or non-module. 106 // In these cases, just call cb function directly. 107 // 如果下载模块不成功,比如404或者模块不规范(代码出错),导致此时模块状态可能为fetching,或者fetched 108 // 此时直接执行回调函数,在编译模块时,该模块就只会返回null 109 else { 110 cb() 111 } 112 } 113 114 })(unLoadedUris[i]) 115 } 116 117 function cb(module) { 118 // 更改模块状态为READY,当remain为0时表示模块依赖都已经下完,那么执行callback 119 (module || {}).status < STATUS.READY && (module.status = STATUS.READY) 120 --remain === 0 && callback() 121 } 122 } 123 124 125 Module.prototype._compile = function() { 126 var module = this 127 // 如果该模块已经编译过,则直接返回module.exports 128 if (module.status === STATUS.COMPILED) { 129 return module.exports 130 } 131 132 // Just return null when: 133 // 1. the module file is 404. 134 // 2. the module file is not written with valid module format. 135 // 3. other error cases. 136 // 这里是处理一些异常情况,此时直接返回null 137 if (module.status < STATUS.SAVED && !hasModifiers(module)) { 138 return null 139 } 140 // 更改模块状态为COMPILING,表示模块正在编译 141 module.status = STATUS.COMPILING 142 143 // 模块内部使用,是一个方法,用来获取其他模块提供(称之为子模块)的接口,同步操作 144 function require(id) { 145 // 根据id解析模块的路径 146 var uri = resolve(id, module.uri) 147 // 从模块缓存中获取模块(注意,其实这里子模块作为主模块的依赖项是已经被下载下来的) 148 var child = cachedModules[uri] 149 150 // Just return null when uri is invalid. 151 // 如果child为空,只能表示参数填写出错导致uri不正确,那么直接返回null 152 if (!child) { 153 return null 154 } 155 156 // Avoids circular calls. 157 // 如果子模块的状态为STATUS.COMPILING,直接返回child.exports,避免因为循环依赖反复编译模块 158 if (child.status === STATUS.COMPILING) { 159 return child.exports 160 } 161 // 指向初始化时调用当前模块的模块。根据该属性,可以得到模块初始化时的Call Stack. 162 child.parent = module 163 // 返回编译过的child的module.exports 164 return child._compile() 165 } 166 // 模块内部使用,用来异步加载模块,并在加载完成后执行指定回调。 167 require.async = function(ids, callback) { 168 module._use(ids, callback) 169 } 170 // 使用模块系统内部的路径解析机制来解析并返回模块路径。该函数不会加载模块,只返回解析后的绝对路径。 171 require.resolve = function(id) { 172 return resolve(id, module.uri) 173 } 174 // 通过该属性,可以查看到模块系统加载过的所有模块。 175 // 在某些情况下,如果需要重新加载某个模块,可以得到该模块的 uri, 然后通过 delete require.cache[uri] 来将其信息删除掉。这样下次使用时,就会重新获取。 176 require.cache = cachedModules 177 178 // require是一个方法,用来获取其他模块提供的接口。 179 module.require = require 180 // exports是一个对象,用来向外提供模块接口。 181 module.exports = {} 182 var factory = module.factory 183 184 // factory 为函数时,表示模块的构造方法。执行该方法,可以得到模块向外提供的接口。 185 if (util.isFunction(factory)) { 186 compileStack.push(module) 187 runInModuleContext(factory, module) 188 compileStack.pop() 189 } 190 // factory 为对象、字符串等非函数类型时,表示模块的接口就是该对象、字符串等值。 191 // 如:define({ "foo": "bar" }); 192 // 如:define('I am a template. My name is {{name}}.'); 193 else if (factory !== undefined) { 194 module.exports = factory 195 } 196 197 // 更改模块状态为COMPILED,表示模块已编译 198 module.status = STATUS.COMPILED 199 // 执行模块接口修改,通过seajs.modify() 200 execModifiers(module) 201 return module.exports 202 } 203 204 205 Module._define = function(id, deps, factory) { 206 var argsLength = arguments.length 207 // 根据传入的参数个数,进行参数匹配 208 209 // define(factory) 210 // 一个参数的情况: 211 // id : undefined 212 // deps : undefined(后面会根据正则取出依赖模块列表) 213 // factory : function 214 if (argsLength === 1) { 215 factory = id 216 id = undefined 217 } 218 // define(id || deps, factory) 219 // 两个参数的情况: 220 221 else if (argsLength === 2) { 222 // 默认情况下 :define(id, factory) 223 // id : '...' 224 // deps : undefined 225 // factory : function 226 factory = deps 227 deps = undefined 228 229 // define(deps, factory) 230 // 如果第一个参数为数组 :define(deps, factory) 231 // id : undefined 232 // deps : [...] 233 // factory : function 234 if (util.isArray(id)) { 235 deps = id 236 id = undefined 237 } 238 } 239 240 // Parses dependencies. 241 // 如果deps不是数组(即deps未指定值),那么通过正则表达式解析依赖 242 if (!util.isArray(deps) && util.isFunction(factory)) { 243 deps = util.parseDependencies(factory.toString()) 244 } 245 246 // 元信息,之后会将信息传递给对应的module对象中 247 var meta = { id: id, dependencies: deps, factory: factory } 248 var derivedUri 249 250 // Try to derive uri in IE6-9 for anonymous modules. 251 // 对于IE6-9,尝试通过interactive script获取模块的uri 252 if (document.attachEvent) { 253 // Try to get the current script. 254 // 获取当前的script 255 var script = util.getCurrentScript() 256 if (script) { 257 // 将当前script的url进行unpareseMap操作,与模块缓存中key保持一致 258 derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script)) 259 } 260 261 if (!derivedUri) { 262 util.log('Failed to derive URI from interactive script for:', 263 factory.toString(), 'warn') 264 265 // NOTE: If the id-deriving methods above is failed, then falls back 266 // to use onload event to get the uri. 267 } 268 } 269 270 // Gets uri directly for specific module. 271 // 如果给定id,那么根据id解析路径 272 // 显然如果没指定id: 273 // 对于非IE浏览器而言,则返回undefined(derivedUri为空) 274 // 对于IE浏览器则返回CurrentScript的src 275 // 如果指定id: 276 // 则均返回有seajs解析(resolve)过的路径url 277 var resolvedUri = id ? resolve(id) : derivedUri 278 // uri存在的情况,进行模块信息存储 279 if (resolvedUri) { 280 // For IE: 281 // If the first module in a package is not the cachedModules[derivedUri] 282 // self, it should assign to the correct module when found. 283 if (resolvedUri === derivedUri) { 284 var refModule = cachedModules[derivedUri] 285 if (refModule && refModule.realUri && 286 refModule.status === STATUS.SAVED) { 287 cachedModules[derivedUri] = null 288 } 289 } 290 // 存储模块信息 291 var module = save(resolvedUri, meta) 292 293 // For IE: 294 // Assigns the first module in package to cachedModules[derivedUrl] 295 if (derivedUri) { 296 // cachedModules[derivedUri] may be undefined in combo case. 297 if ((cachedModules[derivedUri] || {}).status === STATUS.FETCHING) { 298 cachedModules[derivedUri] = module 299 module.realUri = derivedUri 300 } 301 } 302 else { 303 // 将第一个模块存储到firstModuleInPackage 304 firstModuleInPackage || (firstModuleInPackage = module) 305 } 306 } 307 // uri不存在的情况,在onload回调中进行模块信息存储,那里有个闭包 308 else { 309 // Saves information for "memoizing" work in the onload event. 310 // 因为此时的uri不知道,所以将元信息暂时存储在anonymousModuleMeta中,在onload回调中进行模块save操作 311 anonymousModuleMeta = meta 312 } 313 314 } 315 316 // 获取正在编译的模块 317 Module._getCompilingModule = function() { 318 return compileStack[compileStack.length - 1] 319 } 320 321 // 从seajs.cache中快速查看和获取已加载的模块接口,返回值是module.exports数组 322 // selector 支持字符串和正则表达式 323 Module._find = function(selector) { 324 var matches = [] 325 326 util.forEach(util.keys(cachedModules), function(uri) { 327 if (util.isString(selector) && uri.indexOf(selector) > -1 || 328 util.isRegExp(selector) && selector.test(uri)) { 329 var module = cachedModules[uri] 330 module.exports && matches.push(module.exports) 331 } 332 }) 333 334 return matches 335 } 336 337 // 修改模块接口 338 Module._modify = function(id, modifier) { 339 var uri = resolve(id) 340 var module = cachedModules[uri] 341 // 如果模块存在,并且处于COMPILED状态,那么执行修改接口操作 342 if (module && module.status === STATUS.COMPILED) { 343 runInModuleContext(modifier, module) 344 } 345 // 否则放入修改接口缓存中 346 else { 347 cachedModifiers[uri] || (cachedModifiers[uri] = []) 348 cachedModifiers[uri].push(modifier) 349 } 350 351 return seajs 352 } 353 354 355 // For plugin developers 356 Module.STATUS = STATUS 357 Module._resolve = util.id2Uri 358 Module._fetch = util.fetch 359 Module.cache = cachedModules 360 361 362 // Helpers 363 // ------- 364 // 正在下载的模块列表 365 var fetchingList = {} 366 // 已下载的模块列表 367 var fetchedList = {} 368 // 回调函数列表 369 var callbackList = {} 370 // 匿名模块元信息 371 var anonymousModuleMeta = null 372 var firstModuleInPackage = null 373 // 循环依赖栈 374 var circularCheckStack = [] 375 376 // 批量解析模块的路径 377 function resolve(ids, refUri) { 378 if (util.isString(ids)) { 379 return Module._resolve(ids, refUri) 380 } 381 382 return util.map(ids, function(id) { 383 return resolve(id, refUri) 384 }) 385 } 386 387 function fetch(uri, callback) { 388 // fetch时,首先将uri按map规则转换 389 var requestUri = util.parseMap(uri) 390 // 在fethedList(已下载的模块列表)中查找,有的话,直接返回,并执行回调函数 391 // TODO : 为什么这一步,fetchedList可能会存在该模? 392 if (fetchedList[requestUri]) { 393 // See test/issues/debug-using-map 394 cachedModules[uri] = cachedModules[requestUri] 395 callback() 396 return 397 } 398 // 在fetchingList(正在在下载的模块列表)中查找,有的话,只需添加回调函数到列表中去,然后直接返回 399 if (fetchingList[requestUri]) { 400 callbackList[requestUri].push(callback) 401 return 402 } 403 // 如果走到这一步,表示该模块是第一次被请求, 404 // 那么在fetchingList插入该模块的信息,表示该模块已经处于下载列表中,并初始化该模块对应的回调函数列表 405 fetchingList[requestUri] = true 406 callbackList[requestUri] = [callback] 407 408 // Fetches it 409 // 获取该模块,即发起请求 410 Module._fetch( 411 requestUri, 412 413 function() { 414 // 在fetchedList插入该模块的信息,表示该模块已经下载完成 415 fetchedList[requestUri] = true 416 417 // Updates module status 418 var module = cachedModules[uri] 419 // 此时status可能为STATUS.SAVED,之前在_define中已经说过 420 if (module.status === STATUS.FETCHING) { 421 module.status = STATUS.FETCHED 422 } 423 424 // Saves anonymous module meta data 425 // 因为是匿名模块(此时通过闭包获取到uri,在这里存储模块信息) 426 // 并将anonymousModuleMeta置为空 427 if (anonymousModuleMeta) { 428 save(uri, anonymousModuleMeta) 429 anonymousModuleMeta = null 430 } 431 432 // Assigns the first module in package to cachedModules[uri] 433 // See: test/issues/un-correspondence 434 if (firstModuleInPackage && module.status === STATUS.FETCHED) { 435 cachedModules[uri] = firstModuleInPackage 436 firstModuleInPackage.realUri = uri 437 } 438 firstModuleInPackage = null 439 440 // Clears 441 // 在fetchingList清除模块信息,因为已经该模块fetched并save 442 if (fetchingList[requestUri]) { 443 delete fetchingList[requestUri] 444 } 445 446 // Calls callbackList 447 // 依次调用回调函数,并清除回调函数列表 448 if (callbackList[requestUri]) { 449 util.forEach(callbackList[requestUri], function(fn) { 450 fn() 451 }) 452 delete callbackList[requestUri] 453 } 454 455 }, 456 457 config.charset 458 ) 459 } 460 461 function save(uri, meta) { 462 var module = cachedModules[uri] || (cachedModules[uri] = new Module(uri)) 463 464 // Don't override already saved module 465 // 此时status可能有两个状态: 466 // STATUS.FETCHING,在define里面调用(指定了id),存储模块信息 467 // STATUS.FETCHED,在onload的回调函数里调用,存储模块信息 468 if (module.status < STATUS.SAVED) { 469 // Lets anonymous module id equal to its uri 470 // 匿名模块(即没有指定id),用它的uri作为id 471 module.id = meta.id || uri 472 // 将依赖项(数组)解析成的绝对路径,存储到模块信息中 473 module.dependencies = resolve( 474 util.filter(meta.dependencies || [], function(dep) { 475 return !!dep 476 }), uri) 477 // 存储factory(要执行的模块代码,也可能是对象或者字符串等) 478 module.factory = meta.factory 479 480 // Updates module status 481 // 更新模块状态为SAVED,(注意此时它只是拥有了依赖项,还未全部下载下来(即还未READY)) 482 module.status = STATUS.SAVED 483 } 484 485 return module 486 } 487 488 // 根据模块上下文执行模块代码 489 function runInModuleContext(fn, module) { 490 // 传入与模块相关的两个参数以及模块自身 491 // exports用来暴露接口 492 // require用来获取依赖模块(同步)(编译) 493 var ret = fn(module.require, module.exports, module) 494 // 支持返回值暴露接口形式,如: 495 // return { 496 // fn1 : xx 497 // ,fn2 : xx 498 // ... 499 // } 500 if (ret !== undefined) { 501 module.exports = ret 502 } 503 } 504 // 判断模块是否存在接口修改 505 function hasModifiers(module) { 506 return !!cachedModifiers[module.realUri || module.uri] 507 } 508 // 修改模块接口 509 function execModifiers(module) { 510 var uri = module.realUri || module.uri 511 var modifiers = cachedModifiers[uri] 512 // 内部变量 cachedModifiers 就是用来存储用户通过 seajs.modify 方法定义的修改点 513 // 查看该uri是否又被modify更改过 514 if (modifiers) { 515 // 对修改点统一执行factory,返回修改后的module.exports 516 util.forEach(modifiers, function(modifier) { 517 runInModuleContext(modifier, module) 518 }) 519 // 删除 modify 方法定义的修改点 ,避免再次执行 520 delete cachedModifiers[uri] 521 } 522 } 523 524 //获取纯粹的依赖关系,得到不存在循环依赖关系的依赖数组 525 function getPureDependencies(module) { 526 var uri = module.uri 527 // 对每个依赖项进行过滤,对于有可能形成循环依赖的进行剔除,并打印出警告日志 528 return util.filter(module.dependencies, function(dep) { 529 // 首先将被检查模块的uri放到循环依赖检查栈中,之后的检查会用到 530 circularCheckStack = [uri] 531 //接下来检查模块uri是否和其依赖的模块存在循环依赖 532 var isCircular = isCircularWaiting(cachedModules[dep]) 533 if (isCircular) { 534 // 如果循环,则将uri放到循环依赖检查栈中 535 circularCheckStack.push(uri) 536 // 打印出循环警告日志 537 printCircularLog(circularCheckStack) 538 } 539 540 return !isCircular 541 }) 542 } 543 544 function isCircularWaiting(module) { 545 // 如果依赖模块不存在,那么返回false,因为此时也无法获得依赖模块的依赖项,所以这里无法做判断 546 // 或者如果模块的状态值等于saved,也返回false,因为模块状态为saved的时候代表该模块的信息已经有了, 547 // 所以尽管形成了循环依赖,但是require主模块时,同样可以正常编译,返回主模块接口(好像nodejs会返回undefined) 548 if (!module || module.status !== STATUS.SAVED) { 549 return false 550 } 551 // 如果不是以上的情况,那么将依赖模块的uri放到循环依赖检查栈中,之后的检查会用到 552 circularCheckStack.push(module.uri) 553 // 再次取依赖模块的依赖模块 554 var deps = module.dependencies 555 556 if (deps.length) { 557 // 通过循环依赖检查栈,检查是否存在循环依赖(这里是第一层依赖模块检查,与主模块循环依赖的情况) 558 if (isOverlap(deps, circularCheckStack)) { 559 return true 560 } 561 // 如果不存在上述情形,那么进一步查看,依赖模块的依赖模块,查看他们是否存在对循环依赖检查栈中的uri的模块存在循环依赖 562 // 这样的话,就递归了,循环依赖检查栈就像形成的一条链,当前模块依次对主模块,主模块的主模块...直到最顶上的主模块,依次进行判断是否存在依赖 563 for (var i = 0; i < deps.length; i++) { 564 if (isCircularWaiting(cachedModules[deps[i]])) { 565 return true 566 } 567 } 568 } 569 // 如果不存在循环依赖,那么pop出之前已经push进的模块uri,并返回false 570 circularCheckStack.pop() 571 return false 572 } 573 // 打印出循环警告日志 574 function printCircularLog(stack, type) { 575 util.log('Found circular dependencies:', stack.join(' --> '), type) 576 } 577 //判断两个数组是否有重复的值 578 function isOverlap(arrA, arrB) { 579 var arrC = arrA.concat(arrB) 580 return arrC.length > util.unique(arrC).length 581 } 582 // 从配置文件读取是否有需要提前加载的模块 583 // 如果有预先加载模块,首先设置预加载模块为空(保证下次不必重复加载),并加载预加载模块并执行回调,如果没有则顺序执行 584 function preload(callback) { 585 var preloadMods = config.preload.slice() 586 config.preload = [] 587 preloadMods.length ? globalModule._use(preloadMods, callback) : callback() 588 } 589 590 591 // Public API 592 // 对外暴露的API 593 // ---------- 594 // 全局模块,可以认为是页面模块,页面中的js,css文件都是通过它来载入的 595 // 模块初始状态就是COMPILED,uri就是页面的uri 596 var globalModule = new Module(util.pageUri, STATUS.COMPILED) 597 598 // 页面js,css文件加载器 599 seajs.use = function(ids, callback) { 600 // Loads preload modules before all other modules. 601 // 预加载模块 602 preload(function() { 603 globalModule._use(ids, callback) 604 }) 605 606 // Chain 607 return seajs 608 } 609 610 611 // For normal users 612 // 供普通用户调用 613 seajs.define = Module._define 614 seajs.cache = Module.cache 615 seajs.find = Module._find 616 seajs.modify = Module._modify 617 618 619 // For plugin developers 620 // 供开发者使用 621 seajs.pluginSDK = { 622 Module: Module, 623 util: util, 624 config: config 625 } 626 627 })(seajs, seajs._util, seajs._config)