zoukankan      html  css  js  c++  java
  • [JS设计模式]:单例模式(1)

    什么是单例模式

    所谓单例,就是一个类只有一个实例,实现的方法一般是先判断是否存在实例,如果存在就直接返回,如果不存在就创建了再返回。这样确保了一个类只有一个实例对象。

    实现的单例有很多种方式,最简单的一种方式就是对象字面量的方法,其字面量里面可以包含大量的属性和方法。

    var mySingleton = {
        property1: "something",
        property2: "something else",
        method1: function () {
            console.log('hello world');
        }
    };

    如果想要扩展该对象,使用闭包的方式,在其内部创建私有属性和方法,只需要return暴露出你想暴露的公有属性跟方法,代码如下:

    var mySingleton = function () {
        /* 这里声明私有变量和方法 */
        var privateVariable = 'something private';
        function showPrivate() {
            console.log(privateVariable);
        }
    
        /* 公有变量和方法(可以访问私有变量和方法) */
        return {
            publicMethod: function () {
                showPrivate();
            },
            publicVar: 'the public can see this!'
        };
    };
    
    var single = mySingleton();
    single.publicMethod();  // 输出 'something private'
    console.log(single.publicVar); // 输出 'the public can see this!'

    为了节约资源,怎样在使用的时候才去初始化?我们可以另外一个构造函数里来初始化这些代码,如下:

    var Singleton = (function () {
        var instantiated;
        function init() {
            /*这里定义单例代码*/
            return {
                publicMethod: function () {
                    console.log('hello world');
                },
                publicProperty: 'test'
            };
        }
    
        return {
            getInstance: function () {
                if (!instantiated) {
                    instantiated = init();
                }
                return instantiated;
            }
        };
    })();
    
    /*调用公有的方法来获取实例:*/
    Singleton.getInstance().publicMethod();

    知道了单例如何实现了,但单例用在什么样的场景比较好呢?其实单例一般是用在系统间各种模式的通信协调上,下面的代码是一个单例的最佳实践:

    var SingletonTester = (function () {
        //参数:传递给单例的一个参数集合
        function Singleton(args) {
            //设置args变量为接收的参数或者为空(如果没有提供的话)
            var args = args || {};
            //设置name参数
            this.name = 'SingletonTester';
            //设置pointX的值
            this.pointX = args.pointX || 6; //从接收的参数里获取,或者设置为默认值
            //设置pointY的值
            this.pointY = args.pointY || 10;
    
        }
    
        //实例容器
        var instance;
    
        var _static = {
            name: 'SingletonTester',
            //获取实例的方法
            //返回Singleton的实例
            getInstance: function (args) {
                if (instance === undefined) {
                    instance = new Singleton(args);
                }
                return instance;
            }
        };
        return _static;
    })();
    
    var singletonTest = SingletonTester.getInstance({ pointX: 5 });
    console.log(singletonTest.pointX); // 输出 5 

    其它实现方式

    方法1

    function Universe() {
        // 判断是否存在实例
        if (typeof Universe.instance === 'object') {
            return Universe.instance;
        }
    
        // 其它内容
        this.start_time = 0;
        this.bang = "Big";
    
        // 缓存
        Universe.instance = this;
    
        // 隐式返回this
    }
    
    // 测试
    var uni = new Universe();
    var uni2 = new Universe();
    console.log(uni === uni2); // true

    方法2

    function Universe() {
        // 缓存的实例
        var instance = this;
    
        // 其它内容
        this.start_time = 0;
        this.bang = "Big";
    
        // 重写构造函数
        Universe = function () {
            return instance;
        };
    }
    
    // 测试
    var uni = new Universe();
    var uni2 = new Universe();
    uni.bang = "123";
    console.log(uni === uni2); // true
    console.log(uni2.bang); // 123

    方法3

    function Universe() {
        // 缓存实例
        var instance;
        // 重新构造函数
        Universe = function Universe() {
            return instance;
        };
        // 后期处理原型属性
        Universe.prototype = this;
        // 实例
        instance = new Universe();
        // 重设构造函数指针
        instance.constructor = Universe;
        // 其它功能
        instance.start_time = 0;
        instance.bang = "Big";
    
        return instance;
    }
    
    
    // 测试
    var uni = new Universe();
    var uni2 = new Universe();
    console.log(uni === uni2); // true
    
    // 添加原型属性
    Universe.prototype.nothing = true;
    
    var uni = new Universe();
    
    Universe.prototype.everything = true;
    
    var uni2 = new Universe();
    
    console.log(uni.nothing); // true
    console.log(uni2.nothing); // true
    console.log(uni.everything); // true
    console.log(uni2.everything); // true
    console.log(uni.constructor === Universe); // true

    方式4

    var Universe;
    (function () {
        var instance;
        Universe = function Universe() {
            if (instance) {
                return instance;
            }
            instance = this;
            // 其它内容
            this.start_time = 0;
            this.bang = "Big";
        };
    } ());
    
    //测试代码
    var a = new Universe();
    var b = new Universe();
    console.log(a === b); // true
    a.bang = "123";
    console.log(b.bang); // 123

    实例

    比如有这样一个常见的需求,点击某个按钮的时候需要在页面弹出一个遮罩层。

    代码如下:

    var createMask = function(){
       return document.body.appendChild(  document.createElement(div)  );
    }
    $( 'button' ).click( function(){
       Var mask  = createMask();
       mask.show();
    })

    问题是, 这个遮罩层是全局唯一的, 那么每次调用createMask都会创建一个新的div, 虽然可以在隐藏遮罩层的把它remove掉. 但显然这样做不合理.

    再看下第二种方案, 在页面的一开始就创建好这个div. 然后用一个变量引用它.

    var mask = document.body.appendChild( document.createElement( ''div' ) );
    
    $( ''button' ).click( function(){
       mask.show();
    } )

    这样确实在页面只会创建一个遮罩层div, 但是另外一个问题随之而来, 也许我们永远都不需要这个遮罩层, 那又浪费掉一个div, 对dom节点的任何操作都应该非常吝啬.

    如果可以借助一个变量. 来判断是否已经创建过div呢?

    var mask;
    var createMask = function(){
      if ( mask ) return mask;
      else{
        mask = document,body.appendChild(  document.createElement(div)  );
        return mask;
      }
    }

    看起来不错, 到这里的确完成了一个产生单列对象的函数. 我们再仔细看这段代码有什么不妥.

    首先这个函数是存在一定副作用的, 函数体内改变了外界变量mask的引用, 在多人协作的项目中, createMask是个不安全的函数. 另一方面, mask这个全局变量并不是非需不可. 再来改进一下.

    var createMask = function(){
      var mask;
      return function(){
          return mask || ( mask = document.body.appendChild( document.createElement('div') ) )
      }
    }()

    前面那个单例还是有缺点. 它只能用于创建遮罩层. 假如我又需要写一个函数, 用来创建一个唯一的xhr对象呢? 能不能找到一个通用的singleton包装器.

    js中函数是第一型, 意味着函数也可以当参数传递. 看看最终的代码.

    var singleton = function( fn ){
        var result;
        return function(){
            return result || ( result = fn .apply( this, arguments ) );
        }
    }
     
    var createMask = singleton( function(){
        return document.body.appendChild( document.createElement('div') );
     })

    用一个变量来保存第一次的返回值, 如果它已经被赋值过, 那么在以后的调用中优先返回该变量. 而真正创建遮罩层的代码是通过回调函数的方式传人到singleton包装器中的. 这种方式其实叫桥接模式.

    然而singleton函数也不是完美的, 它始终还是需要一个变量result来寄存div的引用. 遗憾的是js的函数式特性还不足以完全的消除声明和语句.

    参考

  • 相关阅读:
    Java实现 LeetCode 833 字符串中的查找与替换(暴力模拟)
    Java实现 LeetCode 833 字符串中的查找与替换(暴力模拟)
    Java实现 LeetCode 833 字符串中的查找与替换(暴力模拟)
    Java实现 LeetCode 832 翻转图像(位运算)
    Java实现 LeetCode 832 翻转图像(位运算)
    Java实现 LeetCode 832 翻转图像(位运算)
    Java实现 LeetCode 831 隐藏个人信息(暴力)
    Java实现 LeetCode 831 隐藏个人信息(暴力)
    Java实现 LeetCode 831 隐藏个人信息(暴力)
    how to use automapper in c#, from cf~
  • 原文地址:https://www.cnblogs.com/moqiutao/p/9915332.html
Copyright © 2011-2022 走看看