JavaScript享元模式

      享元(flyweight----蝇量级)模式是一种用于性能优化的模式。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就很有用了。在JavaScript中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事。

      享元模式要求将对象的属性划分为内部状态与外部状态(在这里状态是指属性)。享元模式的目标是尽量减少共享对象的数量。如何划分内部状态和外部状态,可以参考一下经验:
            1.内部状态存储于对象内部
            2.内部状态可以被一些对象共享
            3.内部状态独立于具体的场景,通常不会改变
            4.外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享

      使用享元模式的关键就是区别内部状态和外部状态。

      使用了享元模式后,会因为要维护一些多出来的对象,造成额外的开销。但它又有它的好处。享元模式带来的好处很大程度上取决于如何使用及何时使用,一般来说,一下情况发生时便可以使用享元模式:
            1.一个程序中使用了大量的相似对象
            2.由于使用了大量对象,造成很大的内存开销
            3.对象的大多数状态都可以变为外部状态
            4.剥离出对象的外部状态后,可以用相对较少的共享对象取代大量对象

      实例:微云上传文件
      微云支持好几种上传方式,比如浏览器插件、Flash和表单上传等,为了简化例子,假设只有插件和Flash这两种。不论是插件上传,还是Flash上传,原理都是一样的,当用户选择了文件之后,插件和Flash都会通知调用window下的一个全局JavaScript函数startUpload,用户选择的文件列表被组合成一个数组files塞进该函数的参数列表里:

      var id = 0;

      window.startUpload = function(uploadTypd, files){ //uploadType 区分是控件还是flash
            for(var i=0, file; file = files[ i++]; ){
                  var uploadObj = new Upload( uploadType, file.fileName, file.fileSize );
                       uploadObj.init( id++ ); //给upload对象设置一个唯一的id
            }
       }


       var Upload = function( uploadType, fileName, fileSize ){
            this.uploadType = uploadType;
            this.fileName = fileName;
            this.fileSize = fileSize;
            this.dom = null;
        }

       Upload.prototype.init = function(id){
            var that = this;
            this.id = id;
            this.dom = document.createElement("div");
            this.dom.innerHTML = '<span>文件名称:' + this.fileName + ', 文件大小:' + this.fileSize + '</span>' +
                                             '<button class="delFile">删除</button>';
            this.dom.querySelector('.delFile').onclick = function(){
                  that.delFile();
            }

            document.body.appendChild( this.dom );
         };

        Upload.prototype.delFile = function(){
             if( this.fileSize < 3000 ){
                  return this.dom.parentNode.removeChild( this.dom );
             }
             if( window.confirm( '确定要删除该文件吗?' + this.fileName ) ){
                  return this.dom.parentNode.removeChild( this.dom );
             }
        };

        接下来分别创建3个插件上传对象和3个Flash上传对象:

        startUpload( 'plugin', [
             {
                 fileName: '1.txt',
                 fileSize: 1000
             }, {
                 fileName: '2.html',
                 fileSize: 3000
             }, {
                 fileName: '3.txt',
                 fileSize: 5000
             }
        ]);

        startUpload( 'flash', [
             {
                 fileName: '4.txt',
                 fileSize: 1000
             }, {
                 fileName: '5.html',
                 fileSize: 3000
             }, {
                 fileName: '6.txt',
                 fileSize: 5000
             }
         ]);

      我们需要确认插件类型uploadType是内部状态,其它则是外部状态,这样的话上面的代码可以重构,如下:

     var Upload = function( uploadType ){
           this.uploadType = uploadType;
     };

     Upload.prototype.delFile = function( id ){
           uploadManager.setExternalState( id, this ); //把当前ID对应的对象的外部状态都组装到共享对象中

           if( this.fileSize < 3000 ){
               return this.dom.parentNode.removeChild( this.dom );
           }
           if( window.confirm( '确定要删除该文件吗?' + this.fileName ) ){
               return this.dom.parentNode.removeChild( this.dom );
           }
     };

     var UploadFactory = (function(){
          var createdFlyWeightObjs = {};

          return {
              create: function(uploadType){
                   if( createdFlyWeightObjs[uploadType] ){
                        return createdFlyWeightObjs[uploadType];
                   }

                   return createdFlyWeightObjs[uploadType] = new Upload( uploadType );
              }
          };
      })();

       var uploadManager = (function(){
            var uploadDatabase = {};

            return {
                 add: function( id, uploadTypd, fileName, fileSize){
                      var flyWeightObj = UploadFactory.create( uploadType );

                      var dom = document.createElement( 'div' );
                      dom.innerHTML = '<span>文件名称:' + this.fileName + ', 文件大小:' + this.fileSize + '</span>' +
                                                '<button class="delFile">删除</button>';
                      dom.querySelector('.delFile').onclick = function(){
                             flyWeightObj.delFile(id);
                      }

                      document.body.appendChild( this.dom );

                      uploadDatabase[ id ] = {
                            fileName: fileName,
                            fileSize: fileSize,
                            dom: dom
                      };

                      return flyWeightObj;
                  },
                  setExternalState: function( id, flyWeightObj ){
                        var uploadData = uploadDatabase[ id ];
                        for( var i in uploadData ){
                              flyWeightObj[ i ] = uploadData[ i ];
                        }
                  }
            };
       })();


         var id = 0;

         window.startUpload = function( uploadTypd, files ){
                for(var i=0, file; file = files[ i++]; ){
                     var uploadObj = new Upload( ++id, uploadType, file.fileName, file.fileSize );
                }
          }

  

       startUpload( 'plugin', [
             {
                 fileName: '1.txt',
                 fileSize: 1000
             }, {
                 fileName: '2.html',
                 fileSize: 3000
             }, {
                 fileName: '3.txt',
                 fileSize: 5000
             }
        ]);

        startUpload( 'flash', [
             {
                 fileName: '4.txt',
                 fileSize: 1000
             }, {
                 fileName: '5.html',
                 fileSize: 3000
             }, {
                 fileName: '6.txt',
                 fileSize: 5000
             }
         ]);

      享元模式重构前的代码里一共创建了6个upload对象,重构后,对象的数量减少为2,而且就算现在同时上传2000个文件,维持的对象也只是两个。

      享元模式的关键就是把内部状态和外部状态分离开来。有多少种内部状态的组合,系统中变最多存在多少个共享对象,而外部状态存储在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象。如果没有内部状态,那么共享对象就是一个单例。如果没有外部状态,像字符串的对象池,就不完全是享元模式。

原文地址:https://www.cnblogs.com/xbj-2016/p/5828701.html