代码之家  ›  专栏  ›  技术社区  ›  Upperstage

JS中的单例模式与抽象

  •  1
  • Upperstage  · 技术社区  · 15 年前

    尽管下面的示例利用了ExtJS,但是可以很容易地推断到另一个框架。我是抽象和数据隐藏(以及OO)的爱好者;是否有其他人隐藏数据和成员/函数,或者你认为这种尝试是多余的吗?

    (注意:我坚信DOM ID几乎不应该硬编码。尽管我使用原型作为典型类的公共方法,但您会注意到下面在原型之外创建的公共函数。)

    http://yuiblog.com/blog/2007/06/12/module-pattern/

    Ext.ns('Foo.Bar');
    
    /**
     * Foo.Bar.MainToolbar (singleton)
     */
    Foo.Bar.MainToolbar = (function()
    {
      // Temporary, private class used to create and return an object - a singleton
      var toolbarClass = Ext.extend( Ext.Container,
      {
        /**
         * constructor (public)
         */
        constructor: function( config )
        {
          config = config || {};
    
          // PRIVATE MEMBER DATA ========================================
    
          // Need IDs for animation anchors
          var accountId = Ext.id( null, 'ls-accountDiv-');
          var faqId = Ext.id( null, 'ls-faqDiv-');
          var logoutId = Ext.id( null, 'ls-logoutDiv-');
          var loginId = Ext.id( null, 'ls-loginDiv-');
          var rulesId = Ext.id( null, 'ls-rulesDiv-');
    
          var itemCls =
            'color: white; cursor: pointer; font-weight: bold; ' +
            'font-family:Helvetica,Arial,Verdana,Lucida Sans Unicode,Sans-serif;';
    
    
          // PUBLIC METHODS *********************************************
    
          /**
           * userLogin (public) -
           */
          this.userLogin = function( userName, password )
          {
            // Update title bar
            Ext.fly(accountId).update( userName );
            Ext.fly(loginId).hide(true);
            Ext.fly(logoutId).show(true);
          };
    
          // PRIVATE METHODS ********************************************
    
          /**
           * handleFaqClick (private) - handler for click on FAQ
           */
          var handleFaqClick = function( event )
          {
            var dialogMsg = '<div style="text-align: leftblah, blah</div>';
    
            Ext.Msg.show({
              title: 'FAQ',
              modal: true,
              msg: dialogMsg,
              animEl: faqId,
              buttons: Ext.Msg.OK,
              icon: Ext.MessageBox.QUESTION,
              minWidth: '700'
            });
          };
    
          /**
           * handleLogoutClick (private) - handler for click on logout
           */
          var handleLogoutClick = function( event )
          {
            Ext.fly(accountId).update('');
            Ext.fly(logoutId).hide(true);
            Ext.fly(loginId).show(true);
          };
    
          /**
           * handleRulesClick (private) - handler for click on RULES
           */
          var handleRulesClick = function( event )
          {
            var dialogMsg = 
              '<div style="text-align: left;"><br/><b>blah, blah</div>';
    
            Ext.Msg.show({
              title: 'Rules',
              modal: true,
              msg: dialogMsg,
              animEl: rulesId,
              buttons: Ext.Msg.OK,
              icon: Ext.MessageBox.INFO,
              minWidth: '700'
            });
          };
    
    
          // CONSTRUCTOR  ===============================================
    
          // Some parameters (possibly) offered by the user are ignored
          config.id = Ext.id( null, 'ls-mainToolbar-');
          config.layout = 'absolute';
          config.layoutConfig = {};
          config.height = 38;
          config.width = 968;
    
          config.items = [
          {
            id: Ext.id( null, 'ls-mainToolbar-'),
            xtype: 'box', x: 25, y: 0, height: 36, 
            autoEl: { tag: 'img', src: './images/top_toolbar.png' }
    
          },{
            id: Ext.id( null, 'ls-logo-'),
            xtype: 'box',
            x: 70, y: 8, height: 22, width: 200,
            autoEl: { style: itemCls, html: 'Foo Bar' }
          },{
            id: accountId,
            xtype: 'box',
            x: 470, y: 8, height: 22, width: 200,
            autoEl: { style: itemCls + ' text-align: right;', html: ' ' }
          },{
            id: logoutId,
            xtype: 'box', x: 730, y: 8, height: 22, width: 36,
            autoEl: {style: itemCls + ' visibility: hidden;', html: 'logout'},
            listeners:
              { render:
                function( cmp ){
                  cmp.getEl().addListener('click', 
                    handleLogoutClick.createDelegate(this))
                }.createDelegate(this)
              }
          },{
            id: loginId,
            xtype: 'box', x: 730, y: 8, height: 22, width: 36,
            autoEl: { style: itemCls, html: 'login' },
            listeners:
              { render:
                function( cmp ){
                  cmp.getEl().addListener('click',
                    Foo.Bar.LoginDialog.show.createDelegate(
                      Foo.Bar.LoginDialog, [Ext.emptyFn]))
                }
              }
          },{
            id: rulesId,
            xtype: 'box', x: 800, y: 8, height: 22, width: 36,
            autoEl: { style: itemCls, html: 'rules'},
            listeners:
              { render:
                function( cmp ){
                  cmp.getEl().addListener( 'click', 
                    handleRulesClick.createDelegate(this) )
                }.createDelegate(this)
              }
          },{
            id: faqId,
            xtype: 'box', x: 860, y: 8, height: 22, width: 26,
            autoEl: { style: itemCls, html: 'faq'},
            listeners:
              { render:
                function( cmp ){
                  cmp.getEl().addListener( 'click', 
                    handleFaqClick.createDelegate(this) )
                }.createDelegate(this)
              }
          }];
    
          toolbarClass.superclass.constructor.apply( this, [config] );
    
          Foo.Bar.LoginDialog.addListener(
            Foo.Bar.LoginDialog.LOGIN_SUCCESSFUL_EVENT(), 
              this.userLogin.createDelegate(this));
        }
      });
    
      return new toolbarClass();
    })();
    
    3 回复  |  直到 15 年前
        1
  •  2
  •   Miguel Ventura    15 年前

    小心地在JavaScript中隐藏数据通常是一种过分的做法,但这也可能是一个非常好的主意,尤其是当您正在创建一个库,并且希望消费者使用库的公共API,而不是乱搞内部(很多有想象力的人都喜欢这样)。

    简单的例子:

    var API = (function() {
        // internal stuff goes in here
        // ...
        // some public methods i'll expose later
        // all have access to the internals since they're inside the closure
        function method1() { ... }
        function method2() { ... }
        var somevar;
        return {
            public_method1: method1,
            public_method2: method2,
            public_var: somevar,
        };
    })();
    
    // use the API:
    API.public_method1();
    
        2
  •  0
  •   Kaze no Koe    15 年前

    我将发布我的一个大型项目的片段,希望它对您有用。我不是专业人士,所以你可能会发现有些事情很愚蠢或做得很糟糕。我使用的框架是原型。

    缺少很多代码,但我希望您能够理解其结构。

    BaseInterface是一个mixin。

    如果你有问题,请告诉我。

    function getRandomNumber() {
        return 4; // Chosen by fair dice roll. Guaranteed to be random. Thanks to xkcd.com for this function.
    }
    
    /*  -------------------------------
        Core
        -------------------------------
    */
    
    var CORE = function () {
        var mix = function () {
            /* Merge the properties of all the objects given as arguments into the first object, making sure only the first object is modified. Of all the properties with the same name belonging to different objects, the one belonging to the rightmost object wins; that is, precedence goes right to left. */
            var args = $A(arguments);
            if (!args[0]) {
                args[0] = {}; // probably got an empty prototype or something.
            }
            for (var l = args.length, i = 1; i < l; ++i) {
                Object.extend(args[0], args[i]);
            }
            return args[0];
        }
    
        return {
            mix: mix
        }
    }();
    
    
    var INTERFACE = (function(){
    
        Notifier = function () {
            CORE.mix(this, {
                max: 5, // max number of notifications shown
                timeout: 8 // a notification disappears after this number of seconds
            }, arguments[0] || {});
            this.elm = ELM.DIV({ // ELM.DIV is too long to explain, it's some trickery I got partly from script.aculo.us - the idea at least.
                attributes:{
                    id:'notifier',
                    style: 'display: none;'
                }
            })
        };
    
        CORE.mix(Notifier.prototype, CORE.ELEMENT.BaseInterface, {
            notify: function (msg) {
                if (this.elm) {
                    var notes = this.elm.getElementsBySelector('.notification');
                    while (notes.length >= this.max) {
                        notes.last().remove();
                    }
                    this.elm.insert({top: '<div class="notification" style="display: none;">' + msg + '</div>'});
                    if (!this.elm.visible()) {
                        this.elm.setStyle('opacity: 0; display: block;');
                        this.elm.morph('opacity: 1;', {
                            duration: 1
                        });
                    }
                    var newNote = this.elm.down('div');
                    newNote.setStyle('opacity: 0; display: block;');
                    newNote.morph('opacity: 1;', {duration: 1});
                    this.removeNotification.bind(this).delay(this.timeout, newNote);
                }
            },
            removeNotification: function (note) {
                note.remove();
                var notes = this.elm.getElementsBySelector('.notification');
                if (notes.length === 0) {
                    this.elm.hide();
                }
            }
        });
    
        return {
            Notifier: new Notifier() //singleton
        };
    
    })();
    
    /*global Ajax, INTERFACE, CONFIG, CORE, Template $ */
    
    var CONTENT = function () {
    
        var wallpapers = [];
    
        wallpapers.toJSON = function () { // needed if I want to send a list of wallpapers back to the server
            var tmp = [];
            this.each(function (wp) {
                if (wp.elm.visible()) {
                    tmp.push(wp.toJSON());
                }
            });
            return '[' + tmp.join(', ') + ']';
        };
    
        var newWallpapers = []; // just a buffer
    
        Wallpaper = function () {
            CORE.mix(this, {
                thumbUrl: '',
                view: '',
                width: 0,
                height: 0,
                source: ''
            }, arguments[0] || {});
            this.aspect = this.width / this.height;
            switch (this.aspect) {
            case 1.6:
                this.aspect = 2;
                break;
            case 16 / 9:
                this.aspect = 2;
                break;
            case 5 / 4:
                this.aspect = 1;
                break;
            case 4 / 3:
                this.aspect = 1;
                break;
            default:
                if (this.width > 2500) {
                    this.aspect = 3;
                } else {
                    this.aspect = 0;
                }
            }
            this.dimension = this.width < 1280 ? 0 : (this.width < 1680 ? 1 : (this.width < 1920 ? 2 : 3 ));
            this.hr_aspect = CONFIG.labels.aspect[this.aspect];
            this.hr_source = CONFIG.labels.source[this.source].capitalize();
            this.html = '<div class="source">' + this.hr_source + '</div><a class="thumb" target="_BLANK" href="'+ this.view + '"><img class="thumb" src="' + this.thumbUrl + '" /></a><div class="info"><div class="resolution">' + this.width + 'x' + this.height + '</div><div class="aspect">' + this.hr_aspect + '</div></div>';
        };
    
        CORE.mix(Wallpaper.prototype, CORE.ELEMENT.BaseInterface, {
            fxParms: null,
            getElement: function () {
                this.elm = document.createElement('div');
                this.elm.className="wallpaper";
                this.elm.innerHTML = this.html;
                return this.elm;
            },
            postInsert: function () {
                if (this.thumbHeight) {
                    var x = this.thumbHeight * 200 / this.thumbWidth;
                    this.elm.down('img.thumb').setStyle('margin: ' + ((200 - x) / 2) + 'px 0 0;');
                }
                delete this.html;
            },
            toJSON: function () {
                return Object.toJSON({
                    thumbUrl: this.thumbUrl,
                    view: this.view,
                    width: this.width,
                    height: this.height,
                    source: this.hr_source,
                    aspect: this.hr_aspect
                });
            }
        });
    
        return {
            wallpapers: wallpapers, // wallpapers being shown
            newWallpapers: newWallpapers, // incoming wallpapers
            Wallpaper: Wallpaper // constructor
        };
    
    }();
    


    • 如果是大写的(例如墙纸:墙纸),则为 总是 构造器
    • 如果这是一个“新的…”声明,它是 独生子女
    • 如果它没有大写(例如newwallps:newwallps),则为 任何一个 简单物体
        3
  •  0
  •   Brian Moeskau    15 年前

    如果您编写应用程序代码供自己内部使用,那么在JS中隐藏成员并没有多大帮助。唯一的目的是阻止某人访问您隐藏的内容,而唯一能带来好处的时间是您专门编写代码供他人使用,并且希望(强烈)强制实施API。请注意,即使是在ExtJS的情况下,如果您查看大多数类,API也常常是通过约定而不是闭包来实现的(私有的东西仅仅被标记为私有的),因此您可以在需要时重写和扩展它们。有些私密的东西确实不应该被弄乱,但这是个例外。这就是为什么它是如此强大的框架——几乎所有东西都是可扩展的。因此,这实际上取决于您希望最终代码的严格程度。