• seajs源码分析


    seajs主要做了2件事

    1.定义什么是模块,模块的接口:id、deps、exports  

    2.解决了如何加载模块:将模块的id与模块路径用规则关联上,能够透明的加载模块和顺序执行

      核心的代码是util-path.js和module.js

    对外接口

    seajs.use    执行的入口方法

    define       模块定义的初始化方法

    核心类:Module

    静态方法

    Module.use(使用模块load后exec)、Module.preload(获取data.preload参数后执行Module.use)、Module.get(拿到模块,若无则创建)

    模块use的执行过程

    load     加载此模块的js

    fetch     获取此模块的依赖模块,并关联依赖模块load后回调

    load     在fetch后,request全部依赖模块,onRequest时回调load判断是否remain==0,调用onload

    onload    执行callback函数,Module.use中callback会执行deps.exec

    exec     执行模块factory(即define(fn)的fn),且将require关键字映射exec上,达到链式递归执行所有依赖模块

    复制代码
    /*
        创建seajs对象,标识版本号。创建data数据对象,data挂在seajs为外部访问
    */
    var seajs = global.seajs = {
      // The current version of Sea.js being used
      version: "@VERSION"
    }
    
    var data = seajs.data = {}
    
    /*
        seajs自己的事件触发,on绑定事件,off移除,emit触发
    */
    /**
     * 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 are passed the same
    // arguments as `emit` is, apart from the event name
    var emit = seajs.emit = function(name, data) {
      var list = events[name], fn
    
      if (list) {
        // Copy callback lists to prevent modification
        list = list.slice()
    
        // Execute event callbacks
        while ((fn = list.shift())) {
          fn(data)
        }
      }
    
      return seajs
    }
    
    /*
        类型判断和计数函数
    */
    /**
     * util-lang.js - The minimal language enhancement
     */
    
    function isType(type) {
      return function(obj) {
        return Object.prototype.toString.call(obj) === "[object " + type + "]"
      }
    }
    
    var isObject = isType("Object")
    var isString = isType("String")
    var isArray = Array.isArray || isType("Array")
    var isFunction = isType("Function")
    
    var _cid = 0
    function cid() {
      return _cid++
    }
    
    /*
        解析路径的处理方法,包括:
        dirname        获取路径的目录
        realpath    过滤目录中的./ ../
        normalize    补全文件名.js后缀
        parseAlias    解析配置别名
        parsePaths    解析配置中的path
        parseVars    解析配置中{}
        parseMap    加载路径根据配置匹配规则映射
        addBase        通过id,路由规则生成完整绝对路径
        
        id2Uri        根据id获取uri,完整的绝对路径
        
        loaderDir    seajs文件路径的获取:根据id=seajsnode查找seajs的dom,获取src后匹配目录
    */
    /**
     * util-path.js - The utilities for operating path such as id, uri
     */
    
    var DIRNAME_RE = /[^?#]*//
    
    var DOT_RE = //.//g
    var DOUBLE_DOT_RE = //[^/]+/..//
    
    // Extract the directory portion of a path
    // dirname("a/b/c.js?t=123#xx/zz") ==> "a/b/"
    // ref: http://jsperf.com/regex-vs-split/2
    function dirname(path) {
      return path.match(DIRNAME_RE)[0]
    }
    
    // Canonicalize a path
    // realpath("http://test.com/a//./b/../c") ==> "http://test.com/a/c"
    function realpath(path) {
      // /a/b/./c/./d ==> /a/b/c/d
      path = path.replace(DOT_RE, "/")
    
      // a/b/c/../../d  ==>  a/b/../d  ==>  a/d
      while (path.match(DOUBLE_DOT_RE)) {
        path = path.replace(DOUBLE_DOT_RE, "/")
      }
    
      return path
    }
    
    // Normalize an id
    // normalize("path/to/a") ==> "path/to/a.js"
    // NOTICE: substring is faster than negative slice and RegExp
    function normalize(path) {
      var last = path.length - 1
    
      // If the uri ends with `#`, just return it without '#'
      if (path.charAt(last) === "#") {
        return path.substring(0, last)
      }
    
      return  (path.substring(last - 2) === ".js" ||
          path.indexOf("?") > 0 ||
          path.substring(last - 3) === ".css") ? path : path + ".js"
    }
    
    
    var PATHS_RE = /^([^/:]+)(/.+)$/
    var VARS_RE = /{([^{]+)}/g
    
    function parseAlias(id) {
      var alias = data.alias
      return alias && isString(alias[id]) ? alias[id] : id
    }
    
    function parsePaths(id) {
      var paths = data.paths
      var m
    
      if (paths && (m = id.match(PATHS_RE)) && isString(paths[m[1]])) {
        id = paths[m[1]] + m[2]
      }
    
      return id
    }
    
    function parseVars(id) {
      var vars = data.vars
    
      if (vars && id.indexOf("{") > -1) {
        id = id.replace(VARS_RE, function(m, key) {
          return isString(vars[key]) ? vars[key] : m
        })
      }
    
      return id
    }
    
    function parseMap(uri) {
      var map = data.map
      var ret = uri
    
      if (map) {
        for (var i = 0, len = map.length; i < len; i++) {
          var rule = map[i]
    
          ret = isFunction(rule) ?
              (rule(uri) || uri) :
              uri.replace(rule[0], rule[1])
    
          // Only apply the first matched rule
          if (ret !== uri) break
        }
      }
    
      return ret
    }
    
    
    var ABSOLUTE_RE = /^//.|://
    var ROOT_DIR_RE = /^.*?//.*?//
    
    function addBase(id, refUri) {
      var ret
      var first = id.charAt(0)
    
      // Absolute
      if (ABSOLUTE_RE.test(id)) {
        ret = id
      }
      // Relative
      else if (first === ".") {
        ret = realpath((refUri ? dirname(refUri) : data.cwd) + id)
      }
      // Root
      else if (first === "/") {
        var m = data.cwd.match(ROOT_DIR_RE)
        ret = m ? m[0] + id.substring(1) : id
      }
      // Top-level
      else {
        ret = data.base + id
      }
    
      return ret
    }
    
    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
    }
    
    
    var doc = document
    var loc = location
    var cwd = dirname(loc.href)
    var scripts = doc.getElementsByTagName("script")
    
    // Recommend to add `seajsnode` id for the `sea.js` script element
    var loaderScript = doc.getElementById("seajsnode") ||
        scripts[scripts.length - 1]
    
    // When `sea.js` is inline, set loaderDir to current working directory
    var loaderDir = dirname(getScriptAbsoluteSrc(loaderScript) || cwd)
    
    function getScriptAbsoluteSrc(node) {
      return node.hasAttribute ? // non-IE6/7
          node.src :
        // see http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
          node.getAttribute("src", 4)
    }
    
    /*
        request     动态加载css或者script到head
                    创建dom原生link、script标签
        addOnload    定义浏览器兼容的异步请求回调
        getCurrentScript    获取当前资源加载状态为进行中interactive的资源
    */
    /**
     * util-request.js - The utilities for requesting script and style files
     * ref: tests/research/load-js-css/test.html
     */
    
    var head = doc.getElementsByTagName("head")[0] || doc.documentElement
    var baseElement = head.getElementsByTagName("base")[0]
    
    var IS_CSS_RE = /.css(?:?|$)/i
    var READY_STATE_RE = /^(?:loaded|complete|undefined)$/
    
    var currentlyAddingScript
    var interactiveScript
    
    // `onload` event is supported in WebKit < 535.23 and Firefox < 9.0
    // ref:
    //  - https://bugs.webkit.org/show_activity.cgi?id=38995
    //  - https://bugzilla.mozilla.org/show_bug.cgi?id=185236
    //  - https://developer.mozilla.org/en/HTML/Element/link#Stylesheet_load_events
    var isOldWebKit = (navigator.userAgent
        .replace(/.*AppleWebKit/(d+)..*/, "$1")) * 1 < 536
    
    
    function request(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)
    
      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) {
      var missingOnload = isCSS && (isOldWebKit || !("onload" in node))
    
      // for Old WebKit and Old Firefox
      if (missingOnload) {
        setTimeout(function() {
          pollCss(node, callback)
        }, 1) // Begin after node insertion
        return
      }
    
      node.onload = node.onerror = node.onreadystatechange = function() {
        if (READY_STATE_RE.test(node.readyState)) {
    
          // 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()
        }
      }
    }
    
    function pollCss(node, callback) {
      var sheet = node.sheet
      var isLoaded
    
      // for WebKit < 536
      if (isOldWebKit) {
        if (sheet) {
          isLoaded = true
        }
      }
      // for Firefox < 9.0
      else if (sheet) {
        try {
          if (sheet.cssRules) {
            isLoaded = true
          }
        } catch (ex) {
          // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
          // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
          // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
          if (ex.name === "NS_ERROR_DOM_SECURITY_ERR") {
            isLoaded = true
          }
        }
      }
    
      setTimeout(function() {
        if (isLoaded) {
          // Place callback here to give time for style rendering
          callback()
        }
        else {
          pollCss(node, callback)
        }
      }, 20)
    }
    
    function getCurrentScript() {
      if (currentlyAddingScript) {
        return currentlyAddingScript
      }
    
      // For IE6-9 browsers, the script onload event may not fire right
      // after the the script is evaluated. Kris Zyp found that it
      // could query the script nodes and the one that is in "interactive"
      // mode indicates the current script
      // ref: http://goo.gl/JHfFW
      if (interactiveScript && interactiveScript.readyState === "interactive") {
        return interactiveScript
      }
    
      var scripts = head.getElementsByTagName("script")
    
      for (var i = scripts.length - 1; i >= 0; i--) {
        var script = scripts[i]
        if (script.readyState === "interactive") {
          interactiveScript = script
          return interactiveScript
        }
      }
    }
    
    /**
     * module.js - The core of module loader
     */
    //系统全局对象,用来保存全局数据和状态
    var cachedMods = seajs.cache = {}
    var anonymousMeta
    
    var fetchingList = {}
    var fetchedList = {}
    var callbackList = {}
    
    var STATUS = Module.STATUS = {
      // 1 - The `module.uri` is being fetched
      FETCHING: 1,
      // 2 - The meta data has been saved to cachedMods
      SAVED: 2,
      // 3 - The `module.dependencies` are being loaded
      LOADING: 3,
      // 4 - The module are ready to execute
      LOADED: 4,
      // 5 - The module is being executed
      EXECUTING: 5,
      // 6 - The `module.exports` is available
      EXECUTED: 6
    }
    
    //模块类   id、deps、exports 构造函数
    function Module(uri, deps) {
      this.uri = uri
      this.dependencies = deps || []
      this.exports = null
      this.status = 0//模块状态
      // The number of unloaded dependencies
      this._remain = 0
      // Who depend on me
      this._waitings = {}
    }
    
    //Module静态方法,Module.use使用模块,Module.preload调用data.preload后回调Module.use
    // Load preload modules before all other modules
    Module.preload = function(callback) {
      var preloadMods = data.preload
      var len = preloadMods.length
    
      if (len) {
        Module.use(preloadMods, function() {
          // Remove the loaded preload modules
          preloadMods.splice(0, len)
    
          // Allow preload modules to add new preload modules
          Module.preload(callback)
        }, data.cwd + "_preload_" + cid())
      }
      else {
        callback()
      }
    }
    
    //入口模块,ids--依赖模块,uri模块id:获取uri模块
    // Use function is equal to load a anonymous module
    Module.use = function (ids, callback, uri) {
      var mod = Module.get(uri, isArray(ids) ? ids : [ids])
      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()
    }
    
    //获取uri模块,若cachedMods中无则创建个新模块
    Module.get = function(uri, deps) {
      return cachedMods[uri] || (cachedMods[uri] = new Module(uri, deps))
    }
    
    // seajs.use 入口方法
    // 执行过程:A依赖B,B依赖C,seajs.use(A):
    /*
    fetch            获取A模块的依赖模块(B),注册B.load的回调A.load(每次依赖的文件onRquest,要跟新自己的状态)
    load            发送A模块的所有依赖模块request,直到remain==0,触发onload
    onload            当A模块所有依赖都已加载,触发A.callback
    exec            seajs.use 将执行模块的callback设置为exec,调用A.exec,且A模块中的require映射了exec方法,链式的执行下去。。
    */
    seajs.use = function(ids, callback) {
      Module.preload(function() {
        Module.use(ids, callback, data.cwd + "_use_" + cid())
      })
      return seajs
    }
    
    //将依赖的模块通过fetch方法注册,最终执行load请求js,onRequest回调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()//获取依赖uris
      emit("load", uris)
    
      var len = mod._remain = uris.length
      var m
      // Initialize modules and register waitings 拿到依赖模块注册依赖
      for (var i = 0; i < len; i++) {
        m = Module.get(uris[i])//获取依赖module
        if (m.status < STATUS.LOADED) {//模块未加载
          // Maybe duplicate
          m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1//记录下有几个模块依赖自己
        }
        else {
          mod._remain--//加载后--
        }
      }
    
       //若所有依赖都加载后,执行onload
      if (mod._remain === 0) {
        mod.onload()
        return
      }
    
      // Begin parallel loading
      var requestCache = {}
    
      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]()
        }
      }
    }
    
    //将模块的所有依赖的uri都注册到此模块的requestCache列表中
    // 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
      }
    
      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() {
        request(emitData.requestUri, emitData.onRequest, emitData.charset)
      }
    
      function onRequest() {
        delete fetchingList[requestUri]
        fetchedList[requestUri] = true
    
        // Save meta data of anonymous module
        if (anonymousMeta) {
          save(uri, anonymousMeta)
          anonymousMeta = null
        }
    
        // Call callbacks
        var m, mods = callbackList[requestUri]
        delete callbackList[requestUri]
        while ((m = mods.shift())) m.load()
      }
    }
    
    //若模块的依赖加载全部加载触发onload回调mod.callback,同时通知所有依赖他的模块
    // Call this method when module is loaded
    Module.prototype.onload = function() {
      var mod = this
      mod.status = STATUS.LOADED
    
      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) {
            m.onload()
          }
        }
      }
    
      // Reduce memory taken
      delete mod._waitings
      delete mod._remain
    }
    
    //解析模块依赖的uris 获取绝对uris
    // 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] = resolve(ids[i], mod.uri)
      }
      return uris
    }
    
    //触发resolve事件,返回id2Uri
    // Helpers
    function resolve(id, refUri) {
      // Emit `resolve` event for plugins such as text plugin
      var emitData = { id: id, refUri: refUri }
      emit("resolve", emitData)
      return emitData.uri || id2Uri(emitData.id, refUri)
    }
    
    //加载完所有依赖的模块后,执行自身模块,同时通过关键字require将依赖的模块顺序执行
    // Execute a module
    Module.prototype.exec = function () {
      var mod = this
      // When module is executed, DO NOT execute it again. When module
      // is being executed, just return `module.exports` too, for avoiding
      // circularly calling
      if (mod.status >= STATUS.EXECUTING) {
        return mod.exports//模块已执行
      }
      mod.status = STATUS.EXECUTING
      // Create require
      var uri = mod.uri
    
      //执行require的id
      function require(id) {
        return cachedMods[require.resolve(id)].exec()
      }
        
      //获得id模块的绝对路径  
      require.resolve = function(id) {
        return resolve(id, uri)
      }
    
      //异步请求uri
      require.async = function(ids, callback) {
        Module.use(ids, callback, uri + "_async_" + cid())
        return require
      }
    
      // Exec factory
      var factory = mod.factory
    
      var exports = isFunction(factory) ?
          factory(require, mod.exports = {}, mod) :
          factory
    
      if (exports === undefined) {
        exports = mod.exports
      }
    
      // Emit `error` event
      if (exports === null && !IS_CSS_RE.test(uri)) {
        emit("error", mod)
      }
    
      // Reduce memory leak
      delete mod.factory
    
      mod.exports = exports
      mod.status = STATUS.EXECUTED
    
      // Emit `exec` event
      emit("exec", mod)
    
      return exports
    }
    
    //对外的接口 define(function(){...})用于定义模块,初始化模块的基础属性(并不执行模块factory方法)
    Module.define.cmd = {}
    global.define = 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 = undefined
        }
        // 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: 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)
    
      meta.uri ? save(meta.uri, meta) :
          // Save information for "saving" work in the script onload event
          anonymousMeta = meta
    }
    
    function save(uri, meta) {
      var mod = Module.get(uri)
    
      // Do NOT override already saved modules
      if (mod.status < STATUS.SAVED) {
        mod.id = meta.id || uri
        mod.dependencies = meta.deps || []
        mod.factory = meta.factory
        mod.status = STATUS.SAVED
      }
    }
    
    // For Developers 调试接口
    seajs.Module = Module
    data.fetchedList = fetchedList
    data.cid = cid
    //获取模块id
    seajs.resolve = id2Uri
    //获取模块exports接口
    seajs.require = function(id) {
      return (cachedMods[resolve(id)] || {}).exports
    }
    复制代码

     

  • 相关阅读:
    可运行的Java RMI示例和踩坑总结
    JS异步与同步
    Github作为Maven仓库
    Jmeter笔记
    nodeJS生成xlsx以及设置样式
    double运算的坑
    mysql零散操作
    go包的理解
    nodeJS 服务端文件上传
    webpack+thymeleaf实现数据直出
  • 原文地址:https://www.cnblogs.com/liujinyu/p/seajs.html
Copyright © 2020-2023  润新知