探究Javascript设想形式---单例形式

近来盘算体系的进修javascript设想情势,以便本身在开辟中遇到题目能够依据设想情势供应的思绪举行封装,如许能够进步开辟效力而且能够预先躲避许多未知的题目。

先从最基本的单例情势最先。

什么是单例情势

单例情势,从名字拆分来看,单指的是一个,例是实例,意义是说屡次经由过程某个类制造出来实例一直只返回同一个实例,它限定一个类只能有一个实例。单例情势重要是为了处置惩罚对象的建立题目。单例情势的特征:

  1. 一个类只要一个实例

  2. 对外供应唯一的接见接口

在一些以类为中心的言语中,比方java,每建立一个对象就必需先定义一个类,对象是从类建立而来。js是一门无类(class-free)的言语,在js中建立对象的要领异常简朴,不须要先定义类即可建立对象。

在js中,单例情势是一种罕见的情势,比方浏览器中供应的window对象,处置惩罚数字的Math对象。

单例情势的完成

1. 对象字面量

在js中完成单例最简朴的体式格局是建立对象字面量,字面量对象中能够包括多个属性和要领。

var mySingleton = {
    attr1:1,
    attr2:2,
    method:function (){
        console.log("method");    
    }
}

以上建立一个对象,放在全局中,就能够在任何处所接见,要接见对象中的属性和要领,必需经由过程mySingleton这个对象,也就是说供应了唯一一个接见接口。

2. 运用闭包私有化

扩大mySingleton对象,增加私有的属性和要领,运用闭包的情势在其内部封装变量和函数声明,只暴露大众成员和要领。

var mySingleton = (function (){
    //私有变量
    var privateVal = '我是私有变量';
    //私有函数
    function privateFunc(){
        console.log('我是私有函数');    
    }

    return {
            attr1:1,
            attr2:2,
            method:function (){
                console.log("method");    
                privateFunc();
            }
        }    
})()

privateValprivateVal被封装在闭包发生的作用域中,外界接见不到这两个变量,这避免了对全局定名污染。

3.惰性单例

不管运用对象字面量或许闭包私有化的体式格局建立单例,都是在剧本一加载就被建立。有时刻页面能够不会用到这个单例对象,如许就会形成资本糟蹋。关于这类状况,最好处置惩罚体式格局是运用惰性单例,也就是在须要这个单例对象时再初始化。

var mySingleton = (function (){
    function init(){
        //私有变量
        var privateVal = '我是私有变量';
        //私有函数
        function privateFunc(){
            console.log('我是私有函数');    
        }

        return {
            attr1:1,
            attr2:2,
            method(){
                console.log("method");    
                privateFunc();
            }
        }
    }

    //用来保留建立的单例对象
     var instance = null;
    return {
        getInstance (){
            //instance没有存值,就实行函数获得对象
            if(!instance){
                instance = init();
            }    
            //instance存了值,就返回这个对象
            return instance;
        }
    }
})();

//获得单例对象
var singletonObj1 = mySingleton.getInstance();
var singletonObj2 = mySingleton.getInstance();

console.log( singletonObj1 === singletonObj2 ); //true

顺序实行后,将建立单例对象的代码封装到init函数中,只暴露了猎取单例对象的函数getInstance。当有须要用到时,经由过程挪用函数mySingleton.getInstance()获得单例对象,同时运用instance将对象缓存起来,再次挪用mySingleton.getInstance()后获得的是同一个对象,如许经由过程一个函数不会建立多个对象,起到节约资本的目标。

4. 运用组织函数

能够运用组织函数的体式格局,制造单例对象:

function mySingleton(){
    //假如缓存了实例,则直接返回
    if (mySingleton.instance) {
        return mySingleton.instance;
    }

    //当第一次实例化时,先缓存实例
    mySingleton.instance = this;

}

mySingleton.prototype.otherFunc = function (){
    console.log("原型上其他要领");    
}

var p1 = new mySingleton();
var p2 = new mySingleton();

console.log( p1 === p2 );  //true

当第一次运用new挪用函数建立实例时,经由过程函数的静态属性mySingleton.instance把实例缓存起来,在第二次用new挪用函数,推断实例已缓存过了,直接返回,那末第一次获得的实例p1和第二次获得的实例p2是同一个对象。如许相符单例情势的特征:一个类只能有一个实例

如许做有一个题目,暴露了能够接见缓存实例的属性mySingleton.instance,这个属性的值能够被转变:

var p1 = new mySingleton();
//转变mySingleton.instance的值
//mySingleton.instance = null;
//或许
mySingleton.instance = {};
var p2 = new mySingleton();
console.log( p1 === p2 );  //false

转变了mySingleton.instance值后,再经由过程new挪用组织函数建立实例时,又会从新建立新的对象,那末p1p2就不是同一个对象,违反了单例情势一个类只能有一个实例。

闭包中的实例

不运用函数的静态属性缓存实例,而是从新改写组织函数:

function mySingleton(){
    //缓存当前实例
    var instance  = this;

    //实行完成后改写组织函数
    mySingleton = function (){
        return instance;    
    }
    //其他的代码
    instance.userName = "abc";

}

mySingleton.prototype.otherFunc = function (){
    console.log("原型上其他要领");    
}

var p1 = new mySingleton();
var p2 = new mySingleton();

console.log( p1 === p2 );  //true

第一次运用new挪用函数建立实例后,在函数中建立instance用来缓存实例,把mySingleton改写为另一个函数。假如再次运用new挪用函数后,应用闭包的特征,返回了缓存的对象,所以p1p2是同一个对象。

如许虽然也能够保证一个类只返回一个实例,但注重,第二次再次运用new挪用的组织函数是匿名函数,由于mySingleton已被改写:

//第二次new mySingleton()时这个匿名函数才是真正的组织函数
mySingleton = function (){
    return instance;    
}

再次给原mySingleton.prototype上增加是属性,实际上这是给匿名函数的原型增加了属性:

var p1 = new mySingleton();

//再次给mySingleton的原型上增加属性
mySingleton.prototype.addAttr = "我是新增加的属性";

var p2 = new mySingleton();

console.log(p2.addAttr); //undefined

对象p2接见属性addAttr并没有找到。经由过程一个组织函数组织出来的实例并不能接见原型上的要领或属性,这是一种毛病的做法,还须要继承革新。

function mySingleton(){
        
    var instance;

    //改写组织函数
    mySingleton = function (){
        return instance;    
    }

    //把改写后组织函数的原型指向this
    
    mySingleton.prototype = this;

    //constructor改写为改写后的组织函数
    mySingleton.prototype.constructor = mySingleton;

    //获得改写后组织函数建立的实例
    instance = new mySingleton;

    //其他的代码
    instance.userName = "abc";

    //显现的返回改写后组织函数建立的实例
    return instance;

}

mySingleton.prototype.otherFunc = function (){
    console.log("原型上其他要领");    
}

var p1 = new mySingleton();

//再次给mySingleton的原型上增加属性
mySingleton.prototype.addAttr = "我是新增加的属性";

var p2 = new mySingleton();

console.log(p2.addAttr); //'我是新增加的属性'

console.log( p1 === p2 );  //true

以上代码重要做了以下几件事:

  1. 改写mySingleton函数为匿名函数

  2. 改写mySingleton的原型为第一次经由过程new建立的实例

  3. 由于改写了prototype,要把constructor指回mySingleton

  4. 显式返回经由过程改写后mySingleton组织函数组织出的实例

不管运用多少次new挪用mySingleton这个组织函数,都返回同一个对象,而且这些对象都同享同一个原型。

实践单例情势

1. 运用定名空间

依据上述,在js中建立一个对象就是一个单例,把一类的要领和属性放在对象中,都经由过程供应的全局对象接见。

var mySingleton = {
    attr1:1,
    attr2:2,
    method:function (){
        console.log("method");    
    }
}

如许的体式格局耦合度极高,比方:要给这个对象增加属性:

mySingleton.width = 1000;  //增加一个属性

//增加一个要领会掩盖原有的要领
mySingleton.method = function(){};

假如在多人合作中,如许增加属性的体式格局经常涌现被掩盖的风险,能够采纳定名空间的体式格局处置惩罚。

//A同砚
mySingleton.a = {};
mySingleton.a.method = function(){}
//接见
mySingleton.a.method();

//B同砚
mySingleton.b = {};
mySingleton.b.method = function(){}
//接见
mySingleton.b.method();

都在本身的定名空间中,掩盖的概率会很小。
能够封装一个动态建立定名空间的通用要领,如许在须要自力的定名空间时只须要挪用函数即可。

mySingleton.namespace = function(name){
    var arr = name.split(".");
    //存一下对象
    var currentObj = mySingleton;
    for( var i = 0; i < arr.length; i++ ){
        //假如对象中不存在,则赋值增加属性
        if(!currentObj[arr[i]]){
            currentObj[arr[i]] = {};
        }
        //把变量从新赋值,便于轮回继承建立定名空间
        currentObj = currentObj[arr[i]]
    }
}

//建立定名空间
mySingleton.namespace("bom");
mySingleton.namespace("dom.style");

以上挪用函数天生定名空间的体式格局代码等价于:

mySingleton.bom = {};
mySingleton.dom = {};
mySingleton.dom.style = {};

2. 单例登录框

运用面向对象完成一个登录框,在点击登录按钮后登录框被append到页面中,点击封闭就将登录框从页面中remove掉,如许频仍的操纵DOM不合理也不是必要的。

只须要在点击封闭时隐蔽登录框,再次点击按钮后,只须要show出来即可。

页面中只放一个按钮:

<input type="button" value="登录" id="loginBtn" />

js完成:

function Login(){
    var instance;

    Login = function(){
        return install;
    }

    Login.prototype = this;


    install = new Login;

    install.init();

    return install;
}

Login.prototype.init = function(){
    //获得登录框元素
    this.Login = this.createHtml();
    document.body.appendChild(this.Login);
    //绑定事宜
    this.addEvent();
}
Login.prototype.createHtml = function(){
    var LoginDiv = document.createElement("div");
    LoginDiv.className = "box";
    var html = `<input type="button" value="封闭弹框" class="close" /><p>这里做登录</p>`

    LoginDiv.innerHTML = html;

    return LoginDiv;
}
Login.prototype.addEvent = function(){
    var close = this.Login.querySelector(".close");
    var _this = this;
    close.addEventListener("click",function(){
        _this.Login.style.display = 'none';
    })
}
Login.prototype.show = function(){
    this.Login.style.display = 'block';
}
//点击页面中的按钮
var loginBtn = document.querySelector("#loginBtn");
loginBtn.onclick = function(){
    var login = new Login();
    //每次让登录框涌现即可
    login.show();
}

上面的代码依据单例情势的运用组织函数来完成的。如许在一最先天生了一个对象,以后运用的都是同一个对象。

总结

单例情势是一种异常有用的情势,特别是懒性单例手艺,在适宜时刻建立对象,而且只建立唯一一个,如许削减不必要的内存斲丧。

正在进修设想情势,不正确的处所迎接拍砖斧正。

    原文作者:戎马
    原文地址: https://segmentfault.com/a/1190000008435888
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞