javascript设计模式 之 1单例模式

1 单例模式定义

单例模式是指一个类只有一个实例,并且提供一个能够访问它的全局访问点。例如缓存池,全局缓存,浏览器中的window对象。

2 单例模式的实现

就是使用一个变量标识来判断当前是否为否个类创建了一个实例,如果已创建则返回,没有创建则立即创建并保留在实例对象中返回。

var Singleton = function (name) {
    this.name = name;
    this.instance = null;
}
Singleton.getInstance = function(name) {
    if (!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
}

var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a === b);
复制代码

使用闭包的方式完成上面的例子单例模式

var Singleton = function (name) {
    this.name = name;
}

Singleton.getInstance = (function() {
    var instance = null;
    return function(name) {
        if (!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a === b);
复制代码

3 透明的单例模式

实现一个透明的单例类,可以让我们在使用的时候,与其他创建任何普通类一样。下例子的缺点:为了将instance封装起来,我们利用了自执行的匿名函数与闭包,并让这个匿名函数返回真正的Singleton的构造方法。

var CreateDiv = (function() {
    var instance = null;
    var CreateDiv = function(html) {
        if (instance) {
            return instance;
        }
        this.html = html;
        this.init();
        return instance = this;
    }

    CreateDiv.prototype.init = function() {
        var div = document.createElement('div');
        div.innnerHTML = this.html;
        document.body.appendChild(div);
    }
    return CreateDiv;
})();

var a = new CreateDiv('X');
var b = new CreateDiv('Y');
console.log(a === b);
复制代码

4 用代理方式实现单例模式

现在来改造上面的例子(其实我理解的是:闭包 + 构造函数组合,闭包这里就是代理类)

  • 移动普通函数到外面
  • 创建代理类
function CreateDiv(html) {
    this.html = html;
    this.init();
}
CreateDiv.prototype.init = function() {
    var div = document.createElement('div');
    div.innnerHTML = this.html;
    document.body.appendChild(div);
}

// 引用代理类
var SingletonProxy = (function() {
    var instance = null;
    return function (html) {
        if (!instance) {
            instance = new CreateDiv(html);
        }
        return instance;
    }
})();

var a = new SingletonProxy('X');
var b = new SingletonProxy('Y');
console.log(a === b); // true
复制代码

5 javascript中的单例模式

上面都是模拟面向对象语言中的实现,单例对象是从中创建出来的。但是javascript是一门无类语言,因此在javascript中创建对象的方法非常简单,既然我们只要一个唯一的对象,何必创建类呢?传统的单例模式在javascript中并不适用。 单例模式是保证全局中只有一个实例,并提供全局访问。全局变量并不是单例模式,但是在javascript中经常把全局变量当作单例模式来进行使用。例如下面的例子:a对象时独一无二的。它满足只有一个实例,并且能够全局访问的条件。

var a = {};
复制代码

但是全局变量有很多问题,容易造成命名控件的污染。在程序中,不小心也会被变量覆盖。可以通过如下方式降低全局变量的命名污染。

  • 使用命名空间 最简单的方式就是使用字面量的方式创建。将a, 定义在namespace1中,减少了变量和全局作用域打交道的机会。
var namespace1 = {
    a: function() {

    },
    b: function() {

    }
};
复制代码
  • 使用闭包封装私有变量 使用比较封装将一些变量封装在闭包内部,只暴露出一些接口和外部通信。私有变量使用__开头,它们被封装在闭包产生的作用域中,外部访问不了这两个变量,这样避免了全局的命令污染。
var user = (function() {
    var __name = 'yezi';
    var __age = '10';
    return  {
        getName: function() {
            return __name;
        }
    }
})();
复制代码

6 惰性单例模式

例如在很多网站的右上角都会有一个登录或者注册按钮,点击后界面会出现一个弹出框,该弹出框应该只有一个,而这就适合使用单例模式。我们可以选择两种方式:

  • 页面加载的时候,创建好弹出框,并隐藏。在使用的时候显示,实现了单例模型,整个页面只有一个Dialog。但是浪费了内存,产生了不必要的消费。
  • 当使用的时候再创建弹出框,避免浪费资源内存(因为你可能并不会使用到该弹出框)。避免了不必要的消费,但是没有实现单一原则
var createDialog = function(loginTitle) {
    var loginDialog = document.createElement('div');
    loginDialog.id = 'loginDialog';
    loginDialog.value = loginTitle;
    loginDialog.style.display = false;
    document.appendChild(loginDialog);
}

// 加载页面则从创建
createDialog();
document.getElmentById('login').onClick = function() {
    document.getElementById('loginDialog').style.display = true;
};

// 点击登陆按钮再加载
document.getElmentById('login').onClick = function() {
    createDialog();
    document.getElementById('loginDialog').style.display = true;
};
复制代码

上面的问题已经出现了,那么我们如何实现满足两个条件的情况呢?下面我们利用闭包,封装了一个单例对象loginDialog,当在页面点击登录按钮,就生成loginDialog(存在直接返回,不存在则创建)

var createLoginDialog = (function() {
    var loginDialog = null;
    return function(loginTitle) {
        if (!loginDialog) {
            loginDialog = document.createElement('div');
            loginDialog.id = 'loginDialog';
            loginDialog.value = loginTitle;
            loginDialog.style.display = false;
            document.appendChild(loginDialog);
        }
        return loginDialog;
    }
})();

document.getElmentById('login').onClick = function() {
    createLoginDialog('loginTitle');
    document.getElementById('loginDialog').style.display = true;
};
复制代码

上面我们已经实现了延迟创建以及单例模式。但是假如我们页面有注册dialog,客户沟通dialog,那么我们就需要创建很多个类似的闭包createRegisterDialog, createCilentDialog。这样显然是不合理的。我们可以把创建实例对象的职责(createLogin)与管理单例的职责(createSingleton:抽出不变的创建单例对象函数)分别放置到两个方法。

  • createSingleton:不变的,管理单例职责。创建单例的函数作为参数,只生成一个对象
  • createLogin || createRegister: 可变的,创建对象函数
var createSingleton = function(typeFunc) {
    var intance = null;
    return function(title) {
        if (!instance) {
            var instance = typeFunc.call(this, title);
        }
        return instance;
    }
}

var createLogin = function(loginTitle) {
    var loginDialog = document.createElement('div');
    loginDialog.id = 'loginDialog';
    loginDialog.value = loginTitle;
    loginDialog.style.display = false;
    document.appendChild(loginDialog);
    return loginDialog;
}

var createRegister = function(registerTitle) {
    var registerDialog = document.createElement('div');
    registerDialog.id = 'registerDialog';
    registerDialog.value = registerTitle;
    registerDialog.style.display = false;
    document.appendChild(registerDialog);
    return registerDialog;
}

// 点击登陆按钮创建单例对象
var createLoginSingleton = createSingleton(createLogin);
document.getElmentById('login').onClick = function() {
    createLoginSingleton('loginTitle');
    document.getElementById('loginDialog').style.display = true;
};

// 点击注册按钮单例对象
var createRegisterSingleton = createSingleton(createRegister);
document.getElmentById('register').onClick = function() {
    createRegisterSingleton('registerTitle');
    document.getElementById('registerDialog').style.display = true;
};
复制代码

这种单例模式的用途不仅仅局限于创建对象。例如当页面得到ajax请求数据渲染列表完毕后,需要对一个列表添加注册事件。通过单例模式只需要绑定一次。

// 不变的单例管理函数
var getSingleton = function(func) {
    var instance = null;
    return function() {
        return instance || (instance = func.apply(this, arguments));
    }
}
// 可变的单例构造函数(操作)
var addListener = function() {
    document.getElementById('addEl').addEventListener('click', function() {
		console.log('add click');
	});
	return true;
}

// 获取单例对象
var addListenerSingle = getSingleton(addListener);

// 调用
var render = function() {
    // 页面上一个按钮,点击后创建一个按钮,并注册事件(不敢点击多少次只会注册一个事件)
    document.getElementById('cl').onclick = function() {
        var a = document.createElement('button');
        a.id = 'addEl';
        a.innerHTML = 'A';
        document.body.appendChild(a);
        addListenerSingle();
    };
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值