webuploader上传插件源代码重点难点分析

webuploader源代码设计博大精深,具有工匠精神,本文分析webuploader源代码总体流程和一些重点难点,webuploader采用模块机制,比较复杂,模块编程和异步加载现在几乎已经成为历史,本文忽略wiget组件机制和flash部分的源码分析。

先看webuploader的总体程序结构:

(function( root, factory ) { // 直接执行匿名函数,传参,root就是window,factory就是模块构造函数
  var modules = {},
  _require = function( deps, callback ) { //获取依赖模块,调用callback,传递依赖模块,callback调setModule( id, factory, arguments )
    var args, len, i;
    // 如果deps不是数组,则直接返回指定module
    if ( typeof deps === 'string' ) {
      return getModule( deps );
    } else {
      args = [];
      for( len = deps.length, i = 0; i < len; i++ ) {
        args.push( getModule( deps[ i ] ) );
      }
      return callback.apply( null, args );
    }
  }
  _define = function( id, deps, factory ) { // 调require
    _require( deps || [], function() {
      setModule( id, factory, arguments );
    });
  }
  setModule = function( id, factory, args ) { // 执行模块构造函数factory,返回对象保存到modules[]
    var module = {
      exports: factory
    },
    returned;

    if ( typeof factory === 'function' ) {
      args.length || (args = [ _require, module.exports, module ]);

      returned = factory.apply( null, args );
      returned !== undefined && (module.exports = returned);
    }

    modules[ id ] = module.exports;
  }
  getModule = function( id ) {}
  exportsTo = function( obj ) { // obj是引用所有模块集合,此函数是修改引用对象之后再返回引用对象,修改什么呢?就是把模块名中的xxx/路径转换为.xxx namespace
    var key, host, parts, part, last, ucFirst;

    // make the first character upper case.
    ucFirst = function( str ) { //把首字母变为大写
      return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
    };

    for ( key in modules ) { // 循环处理modules[]中每一个模块,key就是模块名,以runtime/runtime为例
      host = obj; // 每次循环开始时host重新引用obj

      if ( !modules.hasOwnProperty( key ) ) {
        continue;
      }

      parts = key.split('/'); //如果模块名有/xxx这样的路径,就取路径名到数组 parts=['runtime','runtime']

      last = ucFirst( parts.pop() ); // 从parts取出(去掉)最后一部分,第一个字母变大写,数组变为['Runtime'],下面只循环一次,只有一个路径

      while( (part = ucFirst( parts.shift() )) ) { // 如果模块名有路径,循环处理每一个部分,第一个字母大写
        host[ part ] = host[ part ] || {}; // 修改属性是修改引用的obj的属性,第一次循环obj.Runtime={}
        host = host[ part ]; // host重新赋值,不再引用之前的对象,而是引用host[part]对象也就是obj的一个属性。第一次循环host重新引用obj.Runtime
      }

      host[ last ] = modules[ key ]; //就是设置obj.Runtime.Runtime=modules['runtime/runtime'],这样就把moudles[]中的路径名变成了obj[]中的namespace,比如在modules[]中有一个模块runtime/runtime,那么在obj[]中就建立一个Runtime.Runtime模块,也就是建立obj.Runtime.Runtime模块,第一个Runtime是namespace,没有其它意义,第二个Runtime是属性名/模块名,其值是模块对象或函数。

    }

    return obj; //返回修改之后的模块集合,如果以为函数是修改host但却返回obj那就理解错了。
  }
  makeExport = function( dollar ) {
    root.__dollar = dollar;
    return exportsTo( factory( root, _define, _require ) ); //在这里执行factory函数产生各个模块
  }
  root.WebUploader = makeExport(); //把模块集合存储到全局对象window.WebUploader做为应用程序可以访问的api

})( window, function( window, define, require ) { //匿名函数即为factory函数,构造各个模块保存到modules[],最终返回webuploader全局对象含所有模块。

  。。。
  return require('webuploader');
});

webuploader调用方式:

  uploader = WebUploader.create(opts) // 调用WebUploader.create这个api创建new Uploader实例

    Base.create = Uploader.create = function( opts ) {
      return new Uploader( opts );
    };

Uploader构造函数:

  function Uploader( opts ) {
    this.options = $.extend( true, {}, Uploader.options, opts );
    this._init( this.options );
  }

之后写应用代码调用uploader实例的方法,比如:

uploader.addButton({
  id: '#filePicker2',
  label: '继续添加'
});

再比如:

stats = uploader.getStats(); //获取上传统计数据

点击按钮开始上传这个事件绑定是在应用程序upload.js中设置的:

$upload.on('click', function ()
    { // $upload就是上传按钮元素对象。
        
      if (state === 'ready')
      {    
        uploader.upload(); // upload()方法就是执行request('start-upload',args),就是执行start-upload命令函数
              
        Uploader.prototype[upload] = function ()
          {        
            return this.request('start-upload', arguments);           // start-upload命令函数
                      
            startUpload: function (file)
              { // 点击"开始上传"按钮执行startupload,开始上传第一步是getfile,并不是真正开始上传
                            
                if (file)
                {            }
                else
                { // 执行这儿,获取文件,改变文件在queue里面的状态从inited->queued
                                
                  $.each(me.request('get-files', [Status.INITED]), function ()
                  { //request返回获取的文件,再循环处理每一个文件
                                    
                    this.setStatus(Status.QUEUED); // this代表循环项(获取的文件对象)
                                        
                    setStatus: function (status, text)
                    {                      
                      this.trigger('statuschange', status, prevStatus); //处理文件/queue/stats
                                          
                    }              
                  });              
                  Base.nextTick(me.__tick); // 延迟执行__tick,uploader实例初始化时定义了__tick,就是执行_tick绑定uploader实例
                                
                  this.__tick = Base.bindFn(this._tick, this); //bindfn返回function(){_tick.apply(this,args)},__tick就是执行_tick绑定作用域
                                
                  me.owner.trigger('startUpload'); //开始上传时要进行一些相关的状态数据处理,应用代码也可以绑定这些事件进行一些状态处理
                            
                  _startSend: function (block)
                    {            
                      promise = me.request('before-send', block, function ()
                      { // 先执行before-send命令,再执行回调,before-send命令不存在,因此就是延迟一秒执行callback(传入的匿名函数)
                                      
                        if (file.getStatus() === Status.PROGRESS)
                        {                
                          me._doSend(block); // dosend是真正的上传代码,之前都是预处理,预处理还涉及到统计和进度数据处理,非常复杂。
                                        
                        }            
                      });          
                      _tick: function ()
                        { // 异步调度执行_startsend
                                      
                          me._startSend(val);           // 做上传操作。
                                    
                          _doSend: function (block)
                            { // 从startupload开始经过多次延迟异步调度才执行到这儿真正开始上传,封装层次非常多非常细致复杂
                                           // 开始发送。
                                          
                              tr.appendBlob(opts.fileVal, block.blob, file.name);            
                              tr.append(data);            
                              tr.setRequestHeader(headers);            
                              tr.send(); // 底层用xhr = new XMLHttpRequest()上传文件

命令的定义:

$.each({
  upload: 'start-upload',
  stop: 'stop-upload',
  。。。
}, function( fn, command ) {
  Uploader.prototype[ fn ] = function() {
    return this.request( command, arguments );
  };
});

调用命令函数的方式,比如:

promise = me.request('before-send', block, function ()
{ //先执行before-send命令函数,再执行匿名函数(回调),并且返回promise,根据命令函数执行结果resolve返回的promise对象
      
  some code。。。  
});  
promise.fail(function () {} //注册失败回调, 如果失败则执行回调

执行命令函数的request代码是webuploader源代码的精华之一,非常复杂高深,下面就分析一下:

request: function (apiName, args, callback)
{ //apiName比如是"start-upload"
      
  for (; i < len; i++)
  { // 相当于假定每个组件都有apiName函数,要把每个组件的apiName函数都执行一遍
          
    widget = widgets[i];      
    rlt = widget.invoke(apiName, args); // invoke就是调用执行函数,这里就是执行apiName对应的函数并传递widget组件为作用域
        
  }     // 如果有callback,则用异步方式。
      
  if (callback || dfds.length)
  {  // callback就是调用request时传入的匿名函数    
          
    return promise[key](function ()
    { // 这个匿名函数就是为了延迟执行下一个then(callback),相当于setTimeout()的作用
              
      var deferred = Base.Deferred(),
                args = arguments;        
      if (args.length === 1)
      {          
        args = args[0];        
      }        
      setTimeout(function ()
      {          
        deferred.resolve(args);        
      }, 1);        
      return deferred.promise();      
    })[callback ? key : 'done'](callback || Base.noop);          //这个return语句超级复杂,它其实就是promise.then(fn).then(callback),由于promise是when返回的,因此是已经resolved的,会立即执行第一个then(),fn代码resolve返回的promise才执行下一个then(callback)里面的callback,也就是延迟执行callback。
           //这个return语句的意思就是,在执行完apiName函数之后,再延迟执行callback,并返回promise对象,这样在调用request之后还可以写比如promise.fail(fn),继续下一个异步处理。
        
  }
  else
  {      
    return rlts[0]; //最简单的情况就是返回执行apiName函数的结果数据,比如get-file执行完就是返回获取到的文件对象WUfile。
        
  }  
},

webuploader的功能模块有一部分用widget组件方式,还有一部分用new实例方式,html5runtimenew实例方式实现。

至于trigger/on那是webuploader自己设计的一套逻辑事件机制,用于异步调度执行相关过程,因为在操作过程中,会有几十个上百个异步过程需要调度执行,需要同步,比如过程2要保证在过程1之后执行。

tick也是延迟/异步调度方法。

webuploader博大精深,本文只是一个粗浅的分析,抱着学习人家对象编程技术的态度,文中错误不妥之处欢迎指正交流,呵呵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值