资讯专栏INFORMATION COLUMN

seajs 模块源码解读

e10101 / 2597人阅读

摘要:这里的依赖都是通过来异步加载的,加载完毕之后立刻执行函数,在模块文件执行完毕后包括和其他代码,触发的事件。

入口 seajs.use

seajs.use直接调用Module.use(),Module.use的源码如下:

// Use function is equal to load a anonymous module
// ids:模块标识,uri是dirname + "_use_0" + 数字自增变量
Module.use = function (ids, callback, uri) {
 // 从缓存cachedMods中获取已存在的模块对象或者新建module对象,并将缓存写入其中,返回当前模块对象
  var mod = Module.get(uri, isArray(ids) ? ids : [ids])

  // 对使用seajs.use或者require.async的模块挂在一个callback。seajs.use的callback在所有依赖都loaded完毕之后才执行,通过require递归的exec()各个已经loaded的依赖模块。这个callback作为seajs的入口,是所有模块的factory开始执行的线头起点。
  mod.callback = function() {
    var exports = []
    // resolve通过正则表达式解析当前模块的依赖deps,并返回解析后的完成路径,其值为一个数组
    var uris = mod.resolve()

    // seajs.use入口处的依赖模块依次开始执行,此为模块执行的起点
    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()
}

// Get an existed module or create a new one
Module.get = function(uri, deps) {
  return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
}

// Module构造函数
function Module(uri, deps) {
  this.uri = uri
  this.dependencies = deps || []
  this.exports = null
  this.status = 0

  // Who depends on me
  this._waitings = {}  // 依赖当前模块的对象

  // The number of unloaded dependencies
  this._remain = 0 // 依赖的未加载完毕的模块个数
}

load:加载当前模块的依赖,并且在当前模块的所有依赖都加载完毕后,触发onload函数

// Load module.dependencies and fire onload when all done
Module.prototype.load = function() {
  var mod = this

  // If the module is being loaded, just wait it onload call
  // 如果当前模块正在被加载或者已经加载完毕,直接返回
  if (mod.status >= STATUS.LOADING) {
    return
  }
  // 设置当前模块为加载状态
  mod.status = STATUS.LOADING

  // Emit `load` event for plugins such as combo plugin
  // 解析当前加载模块所有依赖文件的完整路径
  var uris = mod.resolve()
  emit("load", uris, mod)
  
  // 初始化模块所依赖的未加载完毕的模块的个数
  var len = mod._remain = uris.length
  var m

  // Initialize modules and register waitings
  // 依次初始化当前mod依赖的deps为module对象,并且初始化_waitings,并存入cacheMods
  for (var i = 0; i < len; i++) {
    m = Module.get(uris[i])

    if (m.status < STATUS.LOADED) {
      // Maybe duplicate: When module has dupliate dependency, it should be it"s count, not 1
      // 如果当前依赖为未加载完成状态,则该模块的waiting + 1
      m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
    } else {
      // 当前依赖已经加载完成,则模块的remain - 1
      mod._remain--
    }
  }
  
  // 当前模块的所有依赖都已经加载完毕时,触发onload
  if (mod._remain === 0) {
    mod.onload()
    return
  }

  // Begin parallel loading
  var requestCache = {}
  // 通过fetch函数,加载当前mod依赖的所有模块
  for (i = 0; i < len; i++) {
    m = cachedMods[uris[i]]

    if (m.status < STATUS.FETCHING) {
      m.fetch(requestCache)
    } else if (m.status === STATUS.SAVED) {
      m.load()
    }
  }

  // Send all requests at last to avoid cache bug in IE6-9. Issues#808
  for (var requestUri in requestCache) {
    if (requestCache.hasOwnProperty(requestUri)) {
      requestCache[requestUri]()
    }
  }
}

// Call this method when module is loaded
// 模块的所有依赖都加载完毕后,执行onload
Module.prototype.onload = function() {
  var mod = this
  mod.status = STATUS.LOADED
  
  // 对于使用require.async定义的模块,有callback函数,在所有依赖模块加载完毕后执行callback函数
  if (mod.callback) {
    mod.callback()
  }

  // Notify waiting modules to fire 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) {
        // 递归调用依赖当前模块的模块,执行onload函数,直到module.use顶端
        m.onload()
      }
    }
  }

  // Reduce memory taken
  delete mod._waitings
  delete mod._remain
}

fetch: fetch主要是通过创建标签并且append到head来实现依赖的加载。这里的依赖都是通过async来异步加载的,加载完毕之后立刻执行define函数,在模块文件执行完毕后(包括define和其他js代码),触发script的onload事件。

// Fetch a module
Module.prototype.fetch = function(requestCache) {
  var mod = this
  var uri = mod.uri

  mod.status = STATUS.FETCHING

  // Emit `fetch` event for plugins such as combo plugin
  var emitData = {
    uri: uri
  }
  emit("fetch", emitData)
  var requestUri = emitData.requestUri || uri

  // Empty uri or a non-CMD module
  if (!requestUri || fetchedList[requestUri]) {
    mod.load()
    return
  }

  // fetchingList和callbackList为两个全局对象,分别存放正在加载的模块列表?????
  if (fetchingList[requestUri]) {
    callbackList[requestUri].push(mod)
    return
  }

  fetchingList[requestUri] = true
  callbackList[requestUri] = [mod]

  // Emit `request` event for plugins such as text plugin
  emit("request", emitData = {
    uri: uri,
    requestUri: requestUri,
    onRequest: onRequest,
    charset: data.charset
  })

  if (!emitData.requested) {
    requestCache ? requestCache[emitData.requestUri] = sendRequest : sendRequest()
  }

  function sendRequest() {
    seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
  }
  
  // script标签的onload事件发生后,触发onRequest方法
  function onRequest() {
    delete fetchingList[requestUri]
    fetchedList[requestUri] = true

    // Save meta data of anonymous module
    if (anonymousMeta) {
      Module.save(uri, anonymousMeta)
      anonymousMeta = null
    }

    // Call callbacks
    var m, mods = callbackList[requestUri]
    delete callbackList[requestUri]
    while ((m = mods.shift()))
      m.load()
  }
}

request: 将script添加到head,并且定义onload事件

seajs.request = function (url, callback, charset) {
    var isCSS = IS_CSS_RE.test(url)
    var node = doc.createElement( isCSS ? "link" : "script")

    if (charset) {
      var cs = isFunction(charset) ? charset(url) : charset
      if (cs) {
        node.charset = cs
      }
    }

    addOnload(node, callback, isCSS, url)

    if (isCSS) {
      node.rel = "stylesheet"
      node.href = url
    } else {
      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, isCSS, url) {
    var supportOnload = "onload" in node

    // for Old WebKit and Old Firefox
    if (isCSS && (isOldWebKit || !supportOnload)) {
      setTimeout(function() {
        pollCss(node, callback)
      }, 1) // Begin after node insertion
      return
    }

    if (supportOnload) {
      node.onload = onload
      node.onerror = function() {
        emit("error", {
          uri: url,
          node: node
        })
        onload()
      }
    } else {
      node.onreadystatechange = function() {
        if (/loaded|complete/.test(node.readyState)) {
          onload()
        }
      }
    }

    function onload() {
      // Ensure only run once and handle memory leak in IE
      node.onload = node.onerror = node.onreadystatechange = null

      // Remove the script to reduce memory leak
      if (!isCSS && !data.debug) {
        head.removeChild(node)
      }

      // Dereference the node
      node = null

      callback()
    }
  }

Module.define

// Define a module
Module.define = function(id, deps, factory) {
  var argsLen = arguments.length

  // define(factory)
  if (argsLen === 1) {
    factory = id
    id = undefined
  } else if (argsLen === 2) {
    factory = deps

    // define(deps, factory)
    if (isArray(id)) {
      deps = id
      id = undefined1 `1` // define(id, factory)
      ``
    } else {
      deps = undefined
    }
  }

  // Parse dependencies according to the module factory code
  if (!isArray(deps) && isFunction(factory)) {
    deps = parseDependencies(factory.toString())
  }

  var meta = {
    id: id,
    uri: Module.resolve(id),
    deps: deps,
    factory: factory
  }

  // Try to derive uri in IE6-9 for anonymous modules
  if (!meta.uri && doc.attachEvent) {
    var script = getCurrentScript()

    if (script) {
      meta.uri = script.src
    }

    // NOTE: If the id-deriving methods above is failed, then falls back
    // to use onload event to get the uri
  }

  // Emit `define` event, used in nocache plugin, seajs node version etc
  emit("define", meta)
  
  // Save information for "saving" work in the script onload event
  meta.uri ? Module.save(meta.uri, meta) : anonymousMeta = meta
}

resolve

// Resolve module.dependencies
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
}

// Resolve id to uri
Module.resolve = function(id, refUri) {
  // Emit `resolve` event for plugins such as text plugin
  var emitData = { id: id, refUri: refUri }
  emit("resolve", emitData)

  return emitData.uri || seajs.resolve(emitData.id, refUri)
}


function id2Uri(id, refUri) {
  if (!id) return ""

  id = parseAlias(id)
  id = parsePaths(id)
  id = parseVars(id)
  id = normalize(id)

  var uri = addBase(id, refUri)
  uri = parseMap(uri)

  return uri
}

seajs.resolve = id2Uri;
参考文献

https://seajs.github.io/seajs...
https://github.com/seajs/exam...
https://github.com/seajs/seajs

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99020.html

相关文章

  • seajs 源码解读

    摘要:本文主要简单地解读一下的源码和模块化原理。其中,是这次源码解读的核心,但我也会顺带介绍一下其他文件的作用的。对代码比较简单,其实就是声明一下全局的命名空间。然而,真正的核心在于处理模块依赖的问题。 seajs 简单介绍 seajs是前端应用模块化开发的一种很好的解决方案。对于多人协作开发的、复杂庞大的前端项目尤其有用。简单的介绍不多说,大家可以到seajs的官网seajs.org参看...

    LiangJ 评论0 收藏0
  • Seajs源码解读

    摘要:如果这个模块的时候没有设置,就表示是个匿名模块,那怎么才能与之前发起请求的那个相匹配呢这里就有了一个全局变量,先将元数据放入这个对象。模块加载完毕的回调保存元数据到匿名模块,为请求的不管是不是匿名模块,最后都是通过方法,将元数据存入到中。 近几年前端工程化越来越完善,打包工具也已经是前端标配了,像seajs这种老古董早已停止维护,而且使用的人估计也几个了。但这并不能阻止好奇的我,为了了...

    bigdevil_s 评论0 收藏0
  • JavaScript模块化发展

    摘要:所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后前置依赖,这个回调函数才会运行。如果将前面的代码改写成形式,就是下面这样定义了一个文件,该文件依赖模块,当模块加载完毕之后执行回调函数,这里并没有暴露任何变量。 模块化是我们日常开发都要用到的基本技能,使用简单且方便,但是很少人能说出来但是的原因及发展过程。现在通过对比不同时期的js的发展,将JavaScript模...

    mengbo 评论0 收藏0
  • seajs源码解析

    摘要:最后将执行的结果暴露给对象。脚本事件在脚本执行的时候不会立马触发解决办法是通过脚本的来判断总结以上就是对的一个大致的分析,如有错误,欢迎指出。 Seajs是一款模块化开发框架,遵循CMD规范。虽然到现在为止很多模块打包工具比它更加的完善,但还是有必要拜读一下的,毕竟为前端模块化的发展做了很大的贡献,分析一下涨涨姿势。文章主要从以下几个方面来分析。有不对的地方,欢迎大家指出。 1、什么是...

    YPHP 评论0 收藏0
  • cordova研习笔记(二) —— cordova 6.X 源码解读(上)

    摘要:本文源码为版本。的代码结构也是一个很经典的定义结构构造函数实例修改函数原型共享实例方法,它提供事件通道上事件的订阅撤消订阅调用。 前言 cordova(PhoneGap) 是一个优秀的经典的中间件框架,网上对其源代码解读的文章确实不多,本系列文章试着解读一下,以便对cordova 框架的原理理解得更深入。本文源码为cordova android版本6.1.2。 源码结构 我们使用IDE...

    Java_oldboy 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<