zoukankan      html  css  js  c++  java
  • 深入seajs源码系列三

    入口方法

           每个程序都有个入口方法,类似于c的main函数,seajs也不例外。系列一的demo在首页使用了seajs.use(),这便是入口方法。入口方法可以接受2个参数,第一个参数为模块名称,第二个为回调函数。入口方法定义了一个新的模块,这个新定义的模块依赖入参提供的模块。然后设置新模块的回调函数,用以在loaded状态之后调用。该回调函数主要是执行所有依赖模块的工厂函数,最后在执行入口方法提供的回调。

    // Public API
    // 入口地址
    seajs.use = function(ids, callback) {
      Module.preload(function() {
        Module.use(ids, callback, data.cwd + "_use_" + cid())
      })
      return seajs
    }
    
    // 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()
      }
    }
    
    // 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()
    }

    Module.preload用于预加载seajs提供的插件plugins,非主要功能,可以忽略。而Module.use则是核心方法,该方法正如之前所说,创建新的module并设置回调函数,最后加载新模块的所有依赖模块。

    加载依赖之load方法

             load方法可谓是seajs的精华所在。该方法主要加载依赖模块并依次执行依赖模块的回调函数,最终执行的回调函数则是通过seajs.use(“./name”)创建的新模块的回调,也就是mod.callback。

             load方法递归加载依赖模块,如果依赖模块还依赖其他模块,则再加载这个模块。这是通过Module类中的_waitings和_remain来实现的。

    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
      for (var i = 0; i < len; i++) {
        m = Module.get(uris[i])
    
        // 修改  依赖文件 的 _waiting属性
        if (m.status < STATUS.LOADED) {
          // Maybe duplicate: When module has dupliate dependency, it should be it's count, not 1
          m._waitings[mod.uri] = (m._waitings[mod.uri] || 0) + 1
        }
        else {
          mod._remain--
        }
      }
    
        //  加载完依赖,执行模块
      if (mod._remain === 0) {
        mod.onload()
        return
      }
    
      // Begin parallel loading
      var requestCache = {}
    
      for (i = 0; i < len; i++) {
        m = cachedMods[uris[i]]
    
        // 该依赖并未加载,则先fetch,将seajs.request函数绑定在对应的requestCache上,此时并未加载模块
        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]()
        }
      }
    }
    
    // 依赖模块加载完毕执行回调函数
    // 并检查依赖该模块的其他模块是否可以执行
    Module.prototype.onload = function() {
      var mod = this
      mod.status = STATUS.LOADED
    
      if (mod.callback) {
        mod.callback()
      }
        console.log(mod)
      // 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
    }

    首先初始化模块的_waitings和_remain属性,如果_remain为0,则意味着没有依赖或者依赖已加载,可以执行onload函数;如果不为0,则fetch未加载的模块。在这里有个实现的小技巧,就是同时加载所有依赖:requestCache对象保存加载函数:(在fetch函数中定义)

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

    其中,sendRequest函数定义如下:

    function sendRequest() {
        seajs.request(emitData.requestUri, emitData.onRequest, emitData.charset)
      }

    并行加载所有依赖,当依赖加载完毕,执行onRequest回调,向上冒泡,加载依赖的依赖,直至没有依赖模块。

    当最上层的依赖已没有依赖模块时,执行onload函数,在函数体内设置状态为loaded,执行mod.callback,并检查并设置该模块的_waitings属性,判断下层模块是否还有依赖,若没有则执行下层模块的mod.callback,这一依次回溯,最终将会执行通过seajs.use创建的匿名模块的mod.callback。

    例证

           通过一个简单的例子,论证上述过程:

    tst.html
    
    <script>
            seajs.use('./b');
    </script>
    -------------------------------------
    a.js
    
    define(function(require,exports,module){
        exports.add = function(a,b){
            return a+b;
        }
    })
    ------------------------------------
    b.js
    
    define(function(require,exports,module){
        var a = require("./a");
        console.log(a.add(3,5));
    })

    通过调试工具,可以看出执行onload的次序:

    图像 1

    最后可看出,匿名模块的状态码为4,也就是该模块并未执行.确实,也没有给匿名模块定义工厂函数,无法执行.

    模块执行之exec

            模块执行是在seajs.use中定义的mod.callback中调用的,依次调用所有依赖的exec方法,执行程序逻辑。exec方法中有commonJS的一些重要关键字或者函数,如require,exports等,让我们一看究竟:

    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
    
      function require(id) {
        return Module.get(require.resolve(id)).exec()
      }
    
      require.resolve = function(id) {
        return Module.resolve(id, uri)
      }
    
      require.async = function(ids, callback) {
        Module.use(ids, callback, uri + "_async_" + cid())
        return require
      }
    
      // Exec factory
      var factory = mod.factory
    
      // 工厂函数有返回值,则返回;
      // 无返回值,则返回mod.exports
      var exports = isFunction(factory) ?
          factory(require, mod.exports = {}, mod) :
          factory
    
      if (exports === undefined) {
        exports = mod.exports
      }
    
      // Reduce memory leak
      delete mod.factory
    
      mod.exports = exports
      mod.status = STATUS.EXECUTED
    
      // Emit `exec` event
      emit("exec", mod)
    
      return exports
    }

    require函数获取模块并执行模块的工厂函数,获取返回值。require函数的resolve方法则是获取对应模块名的绝对url,require函数的async方法异步加载依赖并执行回调。对于工厂方法的返回值,如果工厂方法为对象,则这就是exports的值;or工厂方法有返回值,则为exports的值;or module.exports的值为exports的值。当可以获取到exports值时,设置状态为executed。

    值得注意的一点:当想要通过给exports赋值来导出一个对象时

    define(function(require,exports,module){
        exports ={
            add: function(a,b){
                 return a+b;
            }
        }
    })

    是不成功的.我们通过执行上述方法来判断最终导出exports的值.首先,函数没有返回值,其次,mod.exports为undefined,最终导出的exports为undefined。为什么会出现这种情况呢?是因为js中引用赋值所造成的。js的赋值策略是“按共享传递”,虽说初始时exports === module.exports,但是当给exports赋一个对象时,此时exports指向该对象,module.exports却仍未初始化,为undefined,因此会出错。

    正确的写法为

    define(function(require,exports,module){
        module.exports ={
            add: function(a,b){
                 return a+b;
            }
        }
    })

    总结

            可以说,seajs的核心模块的实现已讲解完毕,通过阅读玉伯的源码,见识了不少编码技巧,领略了回调模式的巧妙,以及于细微处的考量。代码的每一处都考虑到了内存泄露和this指针引用偏移的危险,做了积极的预防,这种精神值得学习。

           对于seajs,前前后后花了不下一个星期来阅读源码,从刚开始的一知半解到如今的拜服,我真切的领会到了设计思想的重要性。之前我不怎么重视实现的技巧性,认为能够实现,不出bug,健壮性良好即可,但是现在我意识到错了,尤其是在load依赖模块那部分实现中,技巧性十足。

          这段时间最大的收货就是:“阅读大牛的代码,品味,咀嚼,消化”。

  • 相关阅读:
    在放置不同图片尺寸时,应该选择合适的放置
    在腾讯开发应用中心上架apk所遇到的问题
    仿慕课网下拉加载动画
    android 视频的缩略图 缓存机制和 异步加载缩略图
    在做Android开发的,如何去掉滚动view在尽头时的阴影效果
    java中常见的模式之自定义观察者和java库中观察者
    在JAVA和android中常用的单列模式
    android 代码控制控件的长宽,小技巧
    IFrame 框架的用法简介
    PHP中RabbitMQ之amqp扩展实现(四)
  • 原文地址:https://www.cnblogs.com/accordion/p/4303641.html
Copyright © 2011-2022 走看看