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
    }
  • 相关阅读:
    Documentation | AnsibleWorks
    Salt Stack 官方文档翻译
    OSNIT_百度百科
    内心觉得自己会是一个还比较厉害的产品经理,对于新产品的整合上
    知方可补不足~UPDLOCK更新锁的使用
    MongoDB学习笔记~环境搭建
    压力测试~一套完整的压力测试项目文档
    压力测试~测试工具的使用
    不说技术~关于创业一点想法
    HTML5 Canvas 填充与描边(Fill And Stroke)
  • 原文地址:https://www.cnblogs.com/strayling/p/3488692.html
Copyright © 2011-2022 走看看