• 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实例方式,html5runtime用new实例方式实现。

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

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

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

  • 相关阅读:
    读取列表下标
    字典dict详解
    使用mysql的长连接
    oauth授权协议的原理
    安装性能测试工具:sysbench和使用apache的ab
    发送邮件出现问题
    获取用户的真实ip
    清理代码的阅读笔记
    开发中三个经典的原则
    要干大事就不能把面子看得太重
  • 原文地址:https://www.cnblogs.com/pzhu1/p/9115929.html
Copyright © 2020-2023  润新知