zoukankan      html  css  js  c++  java
  • seajs源码分析(一)整体结构以及module.js

    1,seajs的主要内容在module.js内部,最开始包含这么几个东西

    var cachedMods = seajs.cache = {}
    var anonymousMeta
    
    var fetchingList = {}
    var fetchedList = {}
    var callbackList = {}

    还有几个状态:

    var STATUS = Module.STATUS = {
      // 1 - `module.uri` 获取中
      FETCHING: 1,
      // 2 - 模块已经存储在了cachedMods对象中
      SAVED: 2,
      // 3 - 模块依赖加载中
      LOADING: 3,
      // 4 - 模块已经加载完成,准备执行
      LOADED: 4,
      // 5 - 模块执行中
      EXECUTING: 5,
      // 6 - 模块已执行完成
      EXECUTED: 6
    }

    2,页面中主要部分是一个Module类

    function Module(uri, deps) {
      this.uri = uri
      this.dependencies = deps || []
      this.exports = null
      this.status = 0
    
      // 依赖于本模块的模块
      this._waitings = {}
    
      // 本模块未加载完的依赖数
      this._remain = 0
    }

    3,大体结构如上,具体实现的话得先从seajs的入口seajs.use说起:

    //可以看到,ids实际上是在preload的模块全部开始加载后才开始加载的
    seajs.use = function(ids, callback) {
      Module.preload(function() {
        Module.use(ids, callback, data.cwd + "_use_" + cid())
      })
      return seajs
    }

    seajs是个什么对象?

    var seajs = global.seajs = {
      // The current version of Sea.js being used
      version: "@VERSION"
    }

    global的传入是这样的:

    (function(global, undefined) {
    
    // 在seajs多次加载时避免冲突
    if (global.seajs) {
      return
    }
    
    })(this);

    跟jquery很像,只是传入的是this,应该是为了兼容node等其他环境,在浏览器内,global就是window

    4,module.preload

    Module.preload = function(callback) {
      var preloadMods = data.preload
      var len = preloadMods.length
    
      if (len) {
        Module.use(preloadMods, function() {
          // 移除已经加载好的预加载模块
          preloadMods.splice(0, len)
    
          // 允许预加载模块再次定义预加载模块(没用过……)
          Module.preload(callback)
        }, data.cwd + "_preload_" + cid())
      }
      else {
        callback()
      }
    }

    实际上它的过程是一直不断的读取需要预加载的内容,最后调用callback。

    preload的内容,是从data.preload读取来的,在config.js文件内部:

    data.preload = (function() {
      var plugins = []
    
      // 这段代码获取url?后面的字段,并把"seajs-xxx"转换成"seajs-xxx=1"
      // 注意: 在uri或cookie内使用"seajs-xxx=1"标志来预加载"seajx-xxx"文件
    var str = loc.search.replace(/(seajs-\w+)(&|$)/g, "$1=1$2") // 在字符串后面连接上cookie这一大段字符串 str += " " + doc.cookie // 将"seajs-xxx=1"的"seajs-xxx"存入plugins数组中,并返回
    str.replace(/(seajs-\w+)=1/g, function(m, name) { plugins.push(name) }) return plugins })()

    这段代码是preload的默认字段,另外里面有几个变量:

    var data = seajs.data = {}
    var doc = document
    var loc = location
    var cwd = dirname(loc.href)
    var scripts = doc.getElementsByTagName("script")

    preload还有一部分是我们自己配置的,在config.js文件内,还有一段这样的代码:

    seajs.config = function(configData) {
    
      //configData就是我们的所有配置,遍历之
      for (var key in configData) {
        var curr = configData[key]//存储当前key的配置信息
        var prev = data[key]
    
        // 如果之前有调用过seajs.config函数进行配置,那么把这些配置信息内的对象合并起来
        if (prev && isObject(prev)) {
          for (var k in curr) {
            prev[k] = curr[k]
          }
        }
        else {
          //如果是数组的话,就concat起来
          if (isArray(prev)) {
            curr = prev.concat(curr)
          }
          //确认base是个绝对路径
          //如果base不是以"/"结尾,加上"/",addBase的作用是将相对路径转换成绝对路径
          else if (key === "base") {
            (curr.slice(-1) === "/") || (curr += "/")
            curr = addBase(curr)
          }
    
          data[key] = curr
        }
      }
      //emit是触发事件的函数
      emit("config", configData)
      return seajs
    }

     至此,data这个对象是什么,config存哪了,就都清楚了,另外,其实在config.js内还有这样一个默认值:

    //这段正则用来匹配path/seajs/xxx/xx/xx/...的path/部分然后存到data.base中
    var BASE_RE = /^(.+?\/)(\?\?)?(seajs\/)+/
    
    data.base = (loaderDir.match(BASE_RE) || ["", loaderDir])[1]
    data.dir = loaderDir
    data.cwd = cwd
    data.charset = "utf-8"

     我们看到,module.preload调用了module.use,那module.use是个什么东西呢?

    5,Module.use

    Module.use = function (ids, callback, uri) {
      var mod = Module.get(uri, isArray(ids) ? ids : [ids])
    
      //这个绑定在mod上的callback会在onload后触发,作用是获取各个依赖模块的输出,并且调用传入的callback,这个callback起真正作用的实际上是从seajs.use中传入的那个函数(如果有的话),因为Module.preload调用Module.use时传入的callback效果实际上在不断的调用自身,直到没有需要预加载的模块。实际效果是seajs.use(['a', 'b', 'c'], function(a, b, c){})
      mod.callback = function() {
        var exports = []
        var uris = mod.resolve()
    
        for (var i = 0, len = uris.length; i < len; i++) {
          exports[i] = cachedMods[uris[i]].exec()
        }
    
        if (callback) {
          callback.apply(global, exports)
        }
    
        delete mod.callback
      }
    
      mod.load()
    }

     这里使用了一大堆未知方法,Module.get, mod.load, mod.exec, mod.resolve

    6,Module.get

    //如果存在则获取该Module对象,否则以模块url为id,存入cachedMods这个对象中,实际上所有新建的Module对象都存在这个对象中
    Module.get = function(uri, deps) {
      return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
    }

    7,Module.prototype.resolve(mod.resolve)

    //该方法获取所有依赖的完成id,然后返回,调用了Module.resolve方法
    Module.prototype.resolve = function() {
      var mod = this
      var ids = mod.dependencies
      var uris = []
    
      for (var i = 0, len = ids.length; i < len; i++) {
        uris[i] = Module.resolve(ids[i], mod.uri)
      }
      return uris
    }

    可以看到,该方法依赖于Module.resolve方法

    8,Module.resolve

    Module.resolve = function(id, refUri) {
      // 为插件触发resolve事件比如text插件
      var emitData = { id: id, refUri: refUri }
      emit("resolve", emitData)
    
      //id2Uri函数将id(比如相对路径)转换成完整的路径,作为存储id
      return emitData.uri || id2Uri(emitData.id, refUri)
    }

    9,Module.prototype.load(mod.load)

    这个方法是重头戏,而且有点长

    Module.prototype.load = function() {
      var mod = this
    
      // 如果已经加载或正在被加载,不要重复加载
      if (mod.status >= STATUS.LOADING) {
        return
      }
    
      mod.status = STATUS.LOADING
    
      // 为插件触发load事件,比如combo插件,获取依赖,存到uris变量中
      var uris = mod.resolve()
      emit("load", uris)
    
      //获取依赖数
      var len = mod._remain = uris.length
      var m
    
      // 依赖模块初始化,并给每个依赖的模块_waiting赋初值
      for (var i = 0; i < len; i++) {
        m = Module.get(uris[i])
    
        //没加载完的模块,添加_waiting
        if (m.status < STATUS.LOADED) {
          // 有可能重复依赖?
          m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
        }
        else {
          mod._remain--
        }
      }
    
      //依赖加载完,则该模块加载完成,触发onload
      if (mod._remain === 0) {
        mod.onload()
        return
      }
    
      // 开始加载
      var requestCache = {}
    
      for (i = 0; i < len; i++) {
        //事实上在resolve后在cachedMods对象中都存储了模块id
        m = cachedMods[uris[i]]
        
        //如果自身没加载,则加载该模块,调用了m.fetch,事实上fetch并没有真正执行内容获取,而仅仅是绑定发送请求的方法,requestCache的作用,是为下面的for循环做准备,具体看fetch。
        if (m.status < STATUS.FETCHING) {
          m.fetch(requestCache)
        }
        else if (m.status === STATUS.SAVED) {
          m.load()
        }
      }
    
      // 这里才是真正执行该方法,这是为了解决 IE6-9的缓存bug , 具体请看github上的Issues#808
      for (var requestUri in requestCache) {
        if (requestCache.hasOwnProperty(requestUri)) {
          requestCache[requestUri]()
        }
      }
    }

    该方法内部调用了mod.fetch,mod.onload方法,除此之外,还有一个mod.exec方法在前面被Module.use依赖

    10,Module.prototype.fetch(mod.fetch)

    Module.prototype.fetch = function(requestCache) {
      var mod = this
      var uri = mod.uri
    
      mod.status = STATUS.FETCHING
    
      // 为combo等插件触发fetch事件
      var emitData = { uri: uri }
      emit("fetch", emitData)
      var requestUri = emitData.requestUri || uri
    
      // 如果uri不符合规范或者已经加载好,直接加载依赖
      if (!requestUri || fetchedList[requestUri]) {
        mod.load()
        return
      }
    
      //如果使用了combo插件,且在加载队列中有该页面的uri,只需要将该模块推入callback中,等待触发onload就是了
      if (fetchingList[requestUri]) {
        callbackList[requestUri].push(mod)
        return
      }
      //否则,推入fetchingList,并初始化该uri的callbackList
      fetchingList[requestUri] = true
      callbackList[requestUri] = [mod]
    
      // 为text等插件触发request事件
      emit("request", emitData = {
        uri: uri,
        requestUri: requestUri,
        onRequest: onRequest,
        charset: data.charset
      })
      
      //如果还没有请求,且传入了requestCache对象,则将uri和发送函数对应推入该对象中,如果没有传入,直接启动发送
      if (!emitData.requested) {
        requestCache ?
            requestCache[emitData.requestUri] = sendRequest :
            sendRequest()
      }
    //此处调用了request方法,作用是将uri作为一个link或者script标签          //的链接,onrequest函数是onload的回调函数,启动发送后将script标签删除
      function sendRequest() {
        request(emitData.requestUri, emitData.onRequest, emitData.charset)
      }
    
      function onRequest() {
       //完成后将该模块在fetching列表中删除,推入fetched列表
        delete fetchingList[requestUri]
        fetchedList[requestUri] = true
    
        // 此时模块运行,实际会有一个调用Module.define的过程,如果该模块是匿名模块,define会给anonymousMeta赋值
        //如果是匿名模块,则调用Module.save
        if (anonymousMeta) {
          Module.save(uri, anonymousMeta)
          anonymousMeta = null
        }
    
        // 该requestUri的所有模块接收完成,调用其load方法
        var m, mods = callbackList[requestUri]
        delete callbackList[requestUri]
        while ((m = mods.shift())) m.load()
      }
    }

    该方法依赖于Module.save方法,实际上隐式依赖于Module.define方法

    11,Module.define

    Module.define = function (id, deps, factory) {
      var argsLen = arguments.length
    
      //这一长串ifelse逻辑是为了解析传入的参数
      // define(factory)
      if (argsLen === 1) {
        factory = id
        id = undefined
      }
      else if (argsLen === 2) {
        factory = deps
    
        // define(deps, factory)
        if (isArray(id)) {
          deps = id
          id = undefined
        }
        // define(id, factory)
        else {
          deps = undefined
        }
      }
    
      // 如果没有申明依赖,并且传入了函数,则调用parseDependencies来解析该函数内容
      if (!isArray(deps) && isFunction(factory)) {
        deps = parseDependencies(factory.toString())
      }
    
      //此处尝试给模块命名id,如果没有成功,则生成匿名模块,等待在后期触发onrequest的时候在将其使用Module.save命名
      var meta = {
        id: id,
        uri: Module.resolve(id),
        deps: deps,
        factory: factory
      }
    
      // 这个是尝试在ie6-9下面给uri复制,getCurrentScript为获取当前的script节点
      if (!meta.uri && doc.attachEvent) {
        var script = getCurrentScript()
    
        if (script) {
          meta.uri = script.src
        }
    
      }
    
      // 为nocache插件触发define事件
      emit("define", meta)
    
      //如果存在uri,则存储,否则,定义为匿名模块
      meta.uri ? Module.save(meta.uri, meta) :
          anonymousMeta = meta
    }

     这里,实际上如果在定义模块的时候没有使用数组存入依赖,很有可能会成为一个匿名模块,但是依赖它的模块会在require里面申明它的一个uri,并在load的时候调用Module.get使用该uri为该匿名模块创建了一个Module对象,所以在启动onrequest之前,若有匿名模块,则肯定是拥有该onrequest函数的模块,然后会在函数内部给该匿名模块命名。此处使用了Module.save。

    12,Module.save

    Module.save = function(uri, meta) {
      var mod = Module.get(uri)
    
      // 确认模块没被存储,如果是匿名模块,则meta.id是没有的
      if (mod.status < STATUS.SAVED) {
        mod.id = meta.id || uri
        mod.dependencies = meta.deps || []
        mod.factory = meta.factory
        mod.status = STATUS.SAVED
      }
    }

    13,Module.prototype.onload(mod.onload)

    //只有在所有依赖加载完后,才会调用onload
    Module.prototype.onload = function() {
      var mod = this
      mod.status = STATUS.LOADED
    
     //这不就是需要调用module.use内绑定的callback么
      if (mod.callback) {
        mod.callback()
      }
    
      //检测依赖该模块的模块,_remain相应减少,如果为0,则调用onload
      var waitings = mod._waitings
      var uri, m
    
      for (uri in waitings) {
        if (waitings.hasOwnProperty(uri)) {
          m = cachedMods[uri]
          m._remain -= waitings[uri]
          if (m._remain === 0) {
            m.onload()
          }
        }
      }
    
      // 释放内存
      delete mod._waitings
      delete mod._remain
    }

    14,Module.prototype.exec(mod.exec)

    这个放到最后,是因为一个模块在所有工作做完后,才会调用该方法

    Module.prototype.exec = function () {
      var mod = this
    
      // 避免重复计算
      if (mod.status >= STATUS.EXECUTING) {
        return mod.exports
      }
    
      mod.status = STATUS.EXECUTING
    
      // 这里创建了最常用的require函数
      var uri = mod.uri
    
      //返回依赖模块的计算值exports
      function require(id) {
        return Module.get(require.resolve(id)).exec()
      }
    
      require.resolve = function(id) {
        return Module.resolve(id, uri)
      }
      //异步加载,触发callback
      require.async = function(ids, callback) {
        Module.use(ids, callback, uri + "_async_" + cid())
        return require
      }
    
      var factory = mod.factory
    
      var exports = isFunction(factory) ?
          factory(require, mod.exports = {}, mod) :
          factory
      //有可能在factory是通过给exports复制来传递结果的
      if (exports === undefined) {
        exports = mod.exports
      }
    
      // 出错,触发error事件(没有结果或者该模块是CSS文件)
      if (exports === null && !IS_CSS_RE.test(uri)) {
        emit("error", mod)
      }
    
      // 释放内存
      delete mod.factory
    
      mod.exports = exports
      mod.status = STATUS.EXECUTED
    
      // 触发excec事件
      emit("exec", mod)
      //最终计算出了该模块的返回值
      return exports
    }
  • 相关阅读:
    mysql报错:java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone.
    MD5登陆密码的生成
    15. 3Sum、16. 3Sum Closest和18. 4Sum
    11. Container With Most Water
    8. String to Integer (atoi)
    6. ZigZag Conversion
    5. Longest Palindromic Substring
    几种非线性激活函数介绍
    AI初探1
    AI初探
  • 原文地址:https://www.cnblogs.com/strayling/p/3488692.html
Copyright © 2011-2022 走看看