zoukankan      html  css  js  c++  java
  • js设计模式学习一(单例模式)

    写在最前

    为什么会有设计模式这样的东西存在,那是因为程序设计的不完美,需要用设计模式来弥补设计上的缺陷,那立马估计会有童鞋问,既然设计的不完美,那就换个完美点的语言,首先,没有绝对完美的语言存在,其次,借鉴下前辈说的话,灵活而简单的语言更能激发人们的创造力,所以生命力旺盛,这也能够解释,近些年来前端发展的如此迅速的原因吧。
    ps一段,自从开始正儿八经深入学习前端已经有一年多左右了,当时定的一个看书目标就是最初的是dom入门,之后是高三书和犀牛书,截止到现在这三本基本都算看完了,犀牛书后续的一些章节还没有完全看完,在上个工作的过程中还算是比较忙的(可能是外包项目比较多),中间有准备了面试,现在是在大厂了,发现要学的实在是太多了,本厂的框架一堆一堆的,哎,慢慢来吧,基础是一样的区别是实现的思想。
    看,这是我当时列的书单
    语言精粹这个比较薄,是经常需要翻看,一下看完也记不牢,设计模式这本是汤姆大叔翻译的,普遍反映有点晦涩,在技术群里有推荐说腾讯大神曾探写的(JavaScript设计模式与开发实践)不错,讲的比较容易理解,就买了一本,看了前面三章,讲js的面向对象,原型以及闭包的,是设计模式的基础,觉得还不错。现在就打算根据看书写下设计模式的一系列的博客,水平不高,求指正,求交流。

    正文:设计模式--单例模式

    单例模式的定义是,保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。
    这个模式是常用的模式了,如果结合业务来讲的话,就应该是固定的功能,每次调用都没什么多大变化的,比如,弹窗的提示,登录功能的浮窗,

    1,实现单例模式

    代码如下

    var single = function (name) {
            this.name = name;
            this.instance = null;
        };
        single.prototype.getName = function (){
            alert(this.name);
        };
        single.getInstance = function (name) {
            if( !this.instance ){
                this.instance = new single(name);
            }
            return this.instance;
        };
        var a = single.getInstance('sev1');
        var b = single.getInstance('sev2');
        alert( a === b);
    我们通过 single.getInstance 来获取single类的唯一对象,这种方式相对简单,但有一个问题,就是增加了这个类的“不透明性”,single类的使用者必须知道这是个单例类,跟以往通过new XXX的方式获取对象不同,这里是使用single.getInstance来获取对象。

    2,透明的单例模式

    我们现在的目标是实现一个透明的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。在下边的例子中,我们将使用creatDiv单例类,它的作用是负责页面中创建唯一的div节点,代码如下:
    var creatDiv = (function () {
            var instance;
            var creatDiv = function (html) {
                if(instance){
                    return instance;
                }
                this.html = html;
                this.init();
                return instance = this;
            };
            creatDiv.prototype.init = function () {
                var div = document.createElement('div');
                div.innerHTML = this.html;
                document.body.appendChild(div);
            };
            return creatDiv;
        })();
        var a = new creatDiv('sev1');
        var b = new creatDiv('sev2');
        alert( a === b );
    虽然现在完成了一个透明的单例类的编写,但它同样有一些缺点。
    为了把instance封装起来,使用了自执行函数和闭包,并且让这个匿名函数返回真正的单例构造方法,这增加了一些程序的复杂度,阅读起来也不很舒服,
    观察下现在的构造函数,
     var creatDiv = function (html) {
                if(instance){
                    return instance;
                }
                this.html = html;
                this.init();
                return instance = this;
            };
    这个函数负责了两个事情,第一,创建对象并执行初始化函数,第二,保证只有一个对象,虽然还没有接触到 “单一职责原则”的概念,就这个构造函数来说,这是一种不好的做法。
    假设我们某天需要利用这个类,在页面中创建千千万万的div,即要让这个类从单例类变成一个普通的 可产生多个实例的类,那我们必须改写creatDiv函数,把控制唯一对象的那一段去掉,这种修改会给我们带来不必要的烦恼。

    3,用代理实现单例模式

    现在我们引入代理的方式来解决上面的问题,
    首先我们把负责管理单例的代码移除出来,使它成为一个普通的创建div的类,
    var creatDiv = function () {
            this.html = html;
            this.init();
        };
        creatDiv.prototype.init = function () {
            var div = document.createElement('div');
            div.innerHTML = this.html;
            document.body.appendChild(div);
        };
    接下来引入代理类,
    var ProxySingletonCreatDiv = (function () {
            var instance;
            return function (html) {
                if(!instance){
                    instance = new creatDiv( html );
                }
                return instance;
            }
        })();
        var a = ProxySingletonCreatDiv('sev1');
        var b = ProxySingletonCreatDiv('sev2');
        alert( a === b );
    通过引入代理类的方式,完成了一个单例模式的编写,跟之前不同是把负责单例的逻辑和实现创建div的类分开了,这个是缓存代理的应用之一,后续还会有关于代理的一些应用。

    4,JavaScript中的单例模式

    前面的几种单例模式的实现,更多的是传统面向对象语言中的实现,单例对象从类中创建而来,在以类为中心的语言中,这是很自然的做法,比如在java中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来。
    但是javascript其实是一门无类的语言,在js中创建对象的方法非常简单,既然我们需要一个唯一的对象,为什么还需要先创建一个类呢,传统的单例模式并不适用与JavaScript。
    全局变量不是单例模式,但是在js中我们经常会把全局变量当成单例模式来用,
    例如 var a = {};
    当用这种方式创建对象a的时候,对象a是独一无二的,如果a变量被声明在全局作用域下,则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的,这样就满足了单例模式的两个条件。
    作为一名js开发者,全局变量有多困扰就不用详细说了,连JavaScript的创造者本人也承认全局变量是设计失误,在es6中已经有对应的处理方式了。
    全局变量的污染也是有解决方式的,

    4.1,使用命名空间

    只能减少,不能杜绝。例如:
    var namespace = {
        a:function(){
            alert(1);
        },
        b:function(){
            alert(2);
        }
    }

    4.2,使用闭包封装私有变量

    这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信。
    var user = (function () {
            var _name = 'sven',
                _age = 29;
            return {
                getUserInfo : function () {
                    return _name + '-' + _age;
                }
            }
        })();

    5,惰性单例

    前面我们了解了单例模式的一些实现办法,现在来看下惰性单例。
    惰性单例指的是在需要的时候才创建对象的实例,惰性单例是单例模式的重点,这种技术在实际开发中非常有用。就像是一开始的instance 实例在我们调用getInstance 时才被创建,
    Singleton.getInstance = (function () {
            var instance = null;
            return function (name) {
                if(!instance){
                    instance = new Singleton(name);
                }
                return instance;
            }
        })();
    不过这是基于类的单例模式,前面已经说过,这种是不适用于javascript的,结合一个登录弹框的场景,来实现下惰性单例。
    第一种解决方案就是,页面加载的时候就创建好,一开始隐藏,点击登录的时候显示出来。
    这种方式有个问题,如果用户在这个页面只是想浏览其他内容,不需要登录,那么久浪费了一些dom节点。
    如果改写下,在点击登录的时候才开始创建弹窗,
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    <button id="btn">登录</button>
    </body>
    <script>
        var createLogin = function () {
            var div = document.createElement('div');
            div.innerHTML = '我是登录弹窗';
            div.style.display = 'none';
            document.body.appendChild(div);
            return div;
        }
        document.getElementById('btn').onclick = function () {
            var login  = createLogin();
            login.style.display = 'block';
        }
    </script>
    </html>
    虽然达到了惰性的目的,但是失去了单例的效果,但我们每次点击都会新创建一个弹窗,显然是不好,即使我们可以在右上设置个将页面删除掉的按钮,但是频繁的创建和删除节点明显是不合理的。
    我们可以用变量判断页面是否已经创建弹窗,这也是基于类的单例模式的做法。creatLogin就可以改写成这样
     
    var createLogin = (function () {
            var div;
            return function () {
                if(!div){
                    div = document.createElement('div');
                    div.innerHTML = '我是登录弹窗';
                    div.style.display = 'none';
                    document.body.appendChild(div);
                }
                return div;
            }
        })();

    6,通用的惰性单例

    上一节,我们实现了一个可用的惰性单例,但是他还是有一些问题,这段代码是违反了单一职责原则的,创建弹窗和管理单例的逻辑都在creatLogin对象内部,如果我们需要一个创建iframe的单例,那就要重新将这个函数在写一遍,能不能将管理单例的逻辑提出来呢,功能的实现是单独的函数。
    管理单例的逻辑都放到getSingle函数里面,
    var getSingle = function (fn) {
            var result;
            return function () {
                return result || (result = fn.apply(this, arguments));
            }
        }
    创建弹窗的函数就可以写成这样
    var createLogin = function () {
            var div = document.createElement('div');
            div.innerHTML = '我是登录弹窗';
            div.style.display = 'none';
            document.body.appendChild(div);
            return div;
        };
    creatSingleLogin 就是个惰性单例的函数
    var creatSingleLogin = getSingle(creatLogin);
    如有其他实现的,创建iframe等其他的,都可以由getSingle这个来创建。
    其实这种单例模式不只是创建对象,比如我们通常渲染完页面的中的一个列表之后,接下来要给这个列表绑定click事件,如果是通过ajax动态网列表里加数据,在使用事件代理的前提下,click事件实际上只需要在第一次渲染列表的时候绑定一次,但是我们不想去判断当前是否是第一次渲染列表,如果借助jquery,我们通常选择给节点绑定one事件:
     var bindEvent = function () {
            $('div').one('click', function () {
                alert('click');
            })
        };
        bindEvent();
        bindEvent();
        bindEvent();
    虽然函数执行3次,但是绑定事件还是只绑定了一次
    用getSingle函数也可以达到一样的效果,
        var bindEvent = getSingle(function () {
            document.getElementById('btn').addEventListener( 'click', function () { //原书用的onclick,验证过这个执行几次也是执行一次,改成了事件绑定的模式
                console.log('ssss')
            });
            return true;
        });
        bindEvent()
        bindEvent()
    这样的话,getSingle的入参函数必须要有个返回值,所以要 return true。
    其实,像这样的场景最好的肯定是事件委托,性能方面也会优化很多。

    小结

    单例模式是我们学习的第一个模式,我们先学习了传统的单例模式的实现,也了解因为语言的差异性,有更合适的方法在javascript中创建单例,这一章还提到了代理模式和单一职责原则。
    在getSingle函数中,实际上也提到了闭包和高阶函数的概念,单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个,更奇妙的是创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才有单例模式的威力。

    参考书

    http://book.douban.com/subject/26382780/

  • 相关阅读:
    HTML DOM 06 节点关系
    HTML DOM 05 事件(三)
    HTML DOM 05 事件(二)
    HTML DOM 05 事件(一)
    html DOM 04 样式
    html DOM 03 节点的属性
    html DOM 02 获取节点
    html DOM 01 节点概念
    JavaScript 29 计时器
    JavaScript 28 弹出框
  • 原文地址:https://www.cnblogs.com/jesse-band/p/5018534.html
Copyright © 2011-2022 走看看