搞懂javascript面向对象之创建型设计模式

101 阅读11分钟

代码复用是面向对象最基本的一条准则

简单工厂模式

  • 第一种通过类实例化对象创建的,如果这些类继承同一个父类,那么它们的父类原型上的方法是可以共用的
//bs基类
var bs=function(){
    this.info='bs'
}

bs.prototype={
    get:function(){console.log('get')}
}

//ft基类
var ft=function(){
    this.info='bs'
}

ft.prototype={
    get:function(){console.log('ft')}
}


function sport(name){
    switch(name){
        case 'ft':
            //返回实例化对象
            return new ft();
        case 'bs':
            return new bs();
    }
}


var fts=sport('ft');
// console.log(fts.get())
  • 第二种是通过创建一个新对象然后包装增强其属性和功能实现的,创建出来的对象都是一个新的个体
function creatBook(name,time){
    var o=new Object();
    o.name=name;
    o.time=time;
    o.getName=function(){
        console.log(this.name)
    }
    return o;
}


var b1=creatBook('js','2020')
var b2=creatBook('js1','2021')
// b1.getName();
// b2.getName();

工厂方法模式

  • 安全模式类即避免在没有(忘记)通过new运算符创建时,访问其原型上的属性方法时会报错的问题
function demo(){}
demo.prototype.show=function(){console.log('show')}
var d=new demo();
// d.show(); //show


//在没有(忘记)通过new运算符创建时,
访问其原型上的属性方法时会报错
var e=demo();
//e.show(); //TypeError: Cannot read property 'show' of undefined

  • 解决方案:
function safeDemo(){
    if(!(this instanceof safeDemo)){
        return new safeDemo();
    }
    console.log(this) //safeDemo {}
    // console.log(this instanceof safeDemo) //不加new是this指向global
}

// var safes=safeDemo();

抽象工厂模式/中间件工厂模式

  • 定义一个产品簇,并声明一些必备的方法,如果子类没有去重写则会抛出错误
var People=function(){};
People.prototype={
    getName:function(){return new Error('就算子类继承我我的这个方法也不能用')}
}
  • 幽灵工厂、中间件模式
var vehicleFactory=function(subType,superType){
    //判断该工厂中是否有是事先注册好该抽象类(子类)
    //有注册则该类型一定会是一个'function'
    if(typeof vehicleFactory[superType]==='function'){
        //利用中间件缓存类
        function F(){};
        //利用中间件继承父类的属性和方法
        F.prototype=new vehicleFactory[superType]();
        //将子类的constructor重新指向子类
        //因为在重写子类的原型时会丢失constructor属性
        subType.constructor=subType;
        //子类原型通过中间件继承‘父类’
        subType.prototype=new F();
    }else{
        //不存在该抽象子类时抛出错误
        throw new Error('不存在该抽象子类')
    }
}

//小汽车抽象类--是一个函数---等同于vehicleFactory['car']
vehicleFactory.Car=function(){
    this.type='car'
}

vehicleFactory.Car.prototype={
    getError:function(){return new Error('不能用我')},
    getName:function(){console.log('可以用我')}
}

//Car的抽象子类
var benz=function(price,speed){
    this.price=price;
    this.speed=speed;
}


//利用封装好的抽象工厂模式实现对Car抽象类的继承
vehicleFactory(benz,'Car')
benz.prototype.getPrice=function(){console.log('无法用抽象类Car,我就自己搞一个用我自己的方法')}


var benzs=new benz(1,2);
// benzs.getName();//可以用我
// benzs.getPrice()//无法用抽象类Car,我就自己搞一个用我自己的方法
console.log('1',benzs.getError()) //不能用我

建造者模式vs工厂模式

  • 建造者模式:将一个复杂对象的构建层与其表示层相互分离,不仅可得到创建的结果,也更关注创建的具体过程,针对性的处理细节问题
  • 工厂模式:工厂模式追求的是创建的结果,我们只了解得到的创建结果对象
//创建一个人类
var Human=function(param){
    //添加技能点
    //先执行&&再执行||
    this.skill=(param&&param.skill) || '保密职位'
}

//类人原型方法---之后会基于这个构造函数拓展属性方法
Human.prototype={
    getSkill:function(){
        console.log(this)
        /*
         * { 
         *   skill: '保密职位',
         *   name:Named { wholeName: 'xiao hua', FirstName: 'xiao', SecondName: ' hua' },
         *   work: Work { work: '老师', workDescript: '我爱老师' } 
         * }
         */
        return this.skill
    }
}



var Named=function(name){
    // Named {}
    var that=this;
    
    //构造器--构造函数解析姓名的姓与名---例如'xiao hua'
    (function(name,that){
        that.wholeName=name;
        if(name.indexOf(' ')>-1){
            that.FirstName=name.slice(0,name.indexOf(' '));
            that.SecondName=name.slice(name.indexOf(' '));
        }
    })(name,that)
}


//实例化职位类
var Work=function(work){
    //Work {}
    var that=this;
    //构造函数中传入的职位来设置相应职位以及描述
    (function(work,that){
        // console.log('22222',that)
        switch(work){
            case 'teacher':
                that.work='老师';
                that.workDescript='我爱老师';
                break;
        }
    })(work,that)
}

//更换期望的工作
Work.prototype.changeWork=function(work){
    this.work=work;
}

//添加对职位的描述
Work.prototype.changeDescript=function(des){
    this.workDescript=des;
}


/**
 * 参数name:姓名
 * 参数work:期望职位
 */

var Person=function(name,work){
    //创建应聘者缓存对象并返回出去
    var _person=new Human();
    //创建应聘者姓名解析对象
    _person.name=new Named(name);

    //创建应聘者期望职位
    _person.work=new Work(work);

    return _person;
}

var person=new Person('xiao hua','teacher');

console.log(person.getSkill()) //保密

//Named { wholeName: 'xiao hua', FirstName: 'xiao', SecondName: ' hua' }
// console.log(person.name)

// console.log(person.name.wholeName) //'xiao hua'


//Work { work: '老师', workDescript: '我爱老师' }
// console.log(person.work)

// person.work.changeDescript('我真的爱学习吗?')
// Work { work: '老师', workDescript: '我真的爱学习吗?' }
// console.log(person.work)

原型模式

  • 原型模式就是将可复用的、可共享的、耗时大的从基类中提出来然后放在原型中,然后子类通过组合继承或者寄生组合式继承而将方法和属性继承下来
  • 原型模式的优点:任何时候都可以直接在基类或者子类的原型(.prototype)上进行方法的拓展,且所有被实例化的对象或者类都可以获得这些方法
var LoopImgs=function(imgArr,container){
    this.imgsArr=imgArr; //父类的属性
    this.container=container;  //父类的属性
}


LoopImgs.prototype={
    creatImg:function(){console.log('LoopImgs creating')},
    changeImg:function(){console.log('LoopImgs changing')}
}


var SlideLoopImg=function(imgArr,container){
    //构造函数继承LoopImgs类,通过call重新指定this
    LoopImgs.call(this,imgArr,container)
}

//意思是 SlideLoopImg.prototype.__proto__=LoopImgs.prototype
SlideLoopImg.prototype=new LoopImgs();

SlideLoopImg.prototype.creatImg=function(){
    //重写继承的creatImg方法
    console.log('SlideLoopImg creating')
}

var sli=new SlideLoopImg();
sli.creatImg(); //SlideLoopImg creating
sli.changeImg() //LoopImgs changing

console.log(SlideLoopImg.prototype.__proto__===LoopImgs.prototype) //true
  • 一个原型模式的对象复制方法,基于已经存在的模板对象克隆出新对象的模式,此例为浅复制
function CopyPrototype(){
    var F=function(){}, //作为中间件缓存类,为实例化返回对象临时创建
    args=arguments, //实参数组对应着模板对象
    i=0, //用来遍历模板对象(这里指arguments)
    len=args.length; //长度
    for(i=0;i<len;i++){
        //遍历arguments[i]获取属性名
        for(var j in args[i]){
            //根据args[i][j]获取属性值并赋值添加在中间件函数F的原型上
            F.prototype[j]=args[i][j]
        }
    }
    //对对象的属性或者方法进行复制后直接返回中间件实例化对象
    //此时传递进来的模板对象(arguments)上面的属性和方法都添加在了该构造函数的原型上
    return new F();
}

var animal=CopyPrototype({
    age:10,
    yell:function(){console.log('yell')}
},{
    run:function(){
        console.log('run')
    }
})

console.log(animal.age) //10
console.log(animal.yell()) //'yell'
console.log(animal.run()) //'run'

单例模式

  • 又称单体模式,是只允许实例化一次的对象类
var lth={
    getDom:function(id){
        return document.getElementById(id)
    },
    css:function(id,key,value){
        this.getDom(id).style[key]=value
    }
}

console.log(lth.getDom())

  • 静态变量的设计,让函数自执行后返回一个带有可以访问函数内部静态变量属性的方法
var Dead=(function(){
    var dead={
        COUNT:1  //静态对象书写规则(respect)默认大写
    }
    //返回取值器对象
    return {
        get:function(name){
            return dead[name]?dead[name]:null;
        }
    }
})()

var count=Dead.get('COUNT')
console.log('tag', count) //tag 1
  • 有时候对于单例对象需要延迟创建
var LazySingle=(function(){

    //单例实例引用--作为最终返回值--一开始为null(经过判断会改变)
    var _instance=null;


    function Single(){
        /**这里定义静态属性和方法 */
        return {
            PUBLIC:'1.1.0'
        }
    }
    return function(){
        if(!_instance){
            _instance=Single();
        }
        return _instance;
    }
})()

console.log(LazySingle().PUBLIC) //1.1.0

  • 保证只有一个实例?可以通过记录是否被实例化类实现
class Person{
        static instance;
        constructor(name){
            if(!Person.instance){
                Person.instance  = this;
            }
            this.name = name;
            return Person.instance;
        }
    }
    let zhangsan = new Person("张三");
    let lisi  = new Person("李四");
    let wangwu  = new Person("王五");
    console.log(zhangsan===lisi); //true

class Person {
      constructor(name) {
          this.name = name;
      }
  }
function getSingle(fn){
      let instance;
      //两个...args用于不定参的接收和解析
      return function(...args){
          if(!instance){
              instance = new fn(...args);
          }
          return instance;
      }
  }


    let singlePerson = getSingle(Person);
    let zhangsan = new singlePerson("张三");
    let lisi = new singlePerson("李四");
    console.log(zhangsan===lisi); //true

var MyApp={};

MyApp.namespace=function(name){
    //'event'.split('.') --->["event"]
    //'event.style'.split('.') --->["event", "style"]
    var parts=name.split('.');
    var current =MyApp;
    for(var i in parts){
        if(!current[parts[i]]){
            //current['event']['style']={}
            current[parts[i]]={}
        }
        current=current[parts[i]]
    }
}

MyApp.namespace('event')
MyApp.namespace('dom.style')

console.log(MyApp)

/**
 * 等价于
 * 
 * var MyApp={
 *      event:{},
 *      dom:{
 *          style:{}
 *      }
 * }
 */
  • 惰性单例指的是在需要的时候才创建单例
var getSingle=function(fn){
    var result;
    return function(){
        return result || (result=fn.apply(this,arguments))
    }
}
  • 一个加载即执行的改造单例模式
var A={}
A.on=function(dom,type,fn){
    if(dom.addEventListener){
        return function(dom,type,fn){
            dom.addEventListener(dom,fn,false)
        }
    }else if(dom.attachEvent){
        return function(dom,type,fn){
            dom.attachEvent('on'+type,fn)
        } 
    }else{
        return function(dom,type,fn){
            dom['on'+type]=fn
        }
    }
}

// console.log(A.on)
  • 惰性执行单例模式,内部对元素dom执行能力检测并显示重写
var A={}
A.on=function(dom,type,fn){
    if(dom.addEventListener){
        A.on=function(dom,type,fn){
            dom.addEventListener(dom,fn,false)
        }
    }else if(dom.attachEvent){
        A.on= function(dom,type,fn){
            dom.attachEvent('on'+type,fn)
        } 
    }else{
        A.on= function(dom,type,fn){
            dom['on'+type]=fn
        }
    }

    A.on(dom,type,fn)
}

数据访问对象模式

  • 抽象与封装对数据源的访问与存储, 通过对数据源链接的管理方便对数据的访问与存储
/*
    本地存储类
    preId 本地存储数据库前缀
    timeSign 时间戳与存储数据之间的拼接符
*/

var BaseLocalStorage=function(preId,timeSign){
    this.preId=preId;
    this.timeSign=timeSign || '|-|'
}


BaseLocalStorage.prototype={

    status:{
        //操作状态
        SUCCESS:0,
        FAILURE:1,
        OVERFLOW:2, //溢出
        TIMEOUT:3 //过期
    },
    //保存本地存储连接
    storage:localStorage || window.localStorage,
    //获取本地存储数据库真实字段
    getKey:function(key){   
        //this.preId='LS_'
        //以key为'a'举例
        //会返回'LS_a'
        return this.preId + key;
    },
    /*
        添加数据
        key 数据字段标识
        value 数据值
        callback 回调函数
        time 添加时间
    */
    set:function(key,value,callback,time){
        //默认操作状态时成功
        var status=this.status.SUCCESS,
        //获取真实字段
        //以key为'a'举例会返回'LS_a'
        key=this.getKey(key);
        try{
            //参数时间获取时间戳
            /*
                1)new Date("month dd,yyyy hh:mm:ss"); 
                2)new Date("month dd,yyyy"); 
                3)new Date(yyyy,mth,dd,hh,mm,ss); 
            */
            time=new Date(time).getTime() || time.getTime()
        }catch(e){
            //如果没有传入时间参数或者参数有问题则默认为一个月
            time=new Date().getTime() + 1000 * 60 * 60 * 24 *31
        }

        try{
            //向数据库添加数据
            // 'LS_a' -->  time+'|-|'+'xiao ming'
            this.storage.setItem(key,time+this.timeSign+value)
        }catch(e){
            //溢出失败,返回溢出状态
            status=this.status.OVERFLOW
        }

        //如果有传入回调函数则执行回调函数并传入参数操作状态,真实数据字段标识,以及存储数据值
        //这里以function(){console.log(arguments)}该回调函数举例
        callback && callback(this,status,key,value)
    },

    /*  获取数据
        key 数据字段标识
        callback 回调函数


        1.该字段数据本来就不存在,返回失败状态
        2.操作成功但未获取值,返回失败状态
        3.获取到值,时间超时,应该删除数据并返回超时
        4.成功获取数据未超时并返回
    */

    get:function(key,callback){
        //默认操作状态时成功
        var status=this.status.SUCCESS,

        //获取数据
        //这里以获取key为'a'举例
        key=this.getKey(key),

        //value默认值为空
        value=null,

        // 时间戳与存储数据之间的拼接符的长度
        // '|-|'的length  为3
        timeSignLen=this.timeSign.length,

        //缓存当前对象
        that=this,

        //时间戳与存储数据之间的拼接符的起始位置
        // 'LS_a': "1607045017882|-|xiao ming"
        index,

        //获取当初为key值设置的时间戳
        //以'LS_a': "1607045017882|-|xiao ming"为例
        //该时间戳为1607045017882
        time,

        //最终获取的数据结果
        result;


        try{
            //获取字段对应的数据字符串
            value=that.storage.getItem(key);
        }catch(e){
            //获取失败则返回失败状态,数据结果为null
            result={
                status:that.status.FAILURE,
                value:null
            }

            //执行回调函数并返回
            callback && callback(this,result.status,result.value)
            return result
        }

        //如果成功获取到数据字符串
        if(value){
            //"1607045017882|-|xiao ming".indexOf('|-|')  结果为13
            //获取时间戳与存储数据之间的拼接字符符起始位置
            index=value.indexOf(that.timeSign);


            //获取时间戳并转换为number类型
            time=+value.slice(0,index);

            //对比如果时间超时过期
            if(new Date(time).getTime() > new Date().getTime() || time==0){
                //获取数结果
                //"1607045017882|-|xiao ming".slice(16) 为 'xiao ming'
                value =value.slice(index+timeSignLen);
            }else{
                // 过期结果为null
                value=null;
                //设置状态为过期状态
                status=that.status.TIMEOUT;
                //删除该字段
                that.remove(key);
            }
        }else{
            //未获取到数据字符串则状态设置为失败
            status=that.status.FAILURE;
        }

        //设置结果
        result={
                status:status,
                value:value
            }

        //执行回调函数并返回
        callback && callback(this,result.status,result.value)
        return result
    },


    /*
        remove 删除数据
        key 数据字段标识
        callback 回调函数
    */

    remove:function(key,callback){
        //设置操作状态默认为失败
        var status=this.status.FAILURE,
        //获取实际数据字段名称
        key=this.getKey(key),
        //设置默认数据结果为空
        value=null;


        try{
            //获取字段对应的数据
            value=this.storage.getItem(key)
        }catch(e){} //肯定拿得到无需再判断出错的情况

        if(value){
            try{
                this.storage.removeItem(key);
                status=this.status.SUCCESS;
            }catch(e){}
        }

        //执行回调函数
        //如果成功则返回重新改造的value值,失败则返回null
        callback && callback.call(this,status,status>0 ?null : value.slice(value.indexOf(this.timeSign)+this.timeSign.length))


    }

}

var LS=new BaseLocalStorage('LS_');
LS.set('a','xiao ming',function(){console.log(arguments)})
LS.get('a',function(){console.log(arguments)});
// LS.remove('a',function(){console.log(arguments)});
// LS.remove('a',function(){console.log(arguments)});
// LS.get('a',function(){console.log(arguments)});

等待者模式

  • 通过对多个异步进程监听来触发未来发生的动作
//等待对象
var Waiter=function(){
    //注册了的等待对象容器
    var dfd=[],
    
    //成功回调方法容器
    doneArr=[],

    //失败回调方法容器
    failArr=[],

    //缓存Array.prototype.slice方法
    slice=Array.prototype.slice,

    //保存当前等待者对象
    that=this;





    //监控对象类 --类
    var Primise=function(){
        //监控对象是否解决成功状态
        //还没解决成功状态
        this.resolved=false;

        //监控对象是否解决失败状态
        //还没解决失败状态
        this.rejected=false;
    }


    //监控对象 类 原型方法
    /**
     * 都是因为异步逻辑状态的改变而执行相应的操作的
     * 
     * resolve方法要对所有被监控的异步逻辑进行状态校验,成功才执行成功回调函数
     * 
     * reject方法是要执行失败回调函数的,只要有一个被监控的异步逻辑状态被改变为失败状态,
     * 就要执行失败回调函数
     */
    Primise.prototype={
        //解决成功
        resolve:function(){
            //设置当前监控对象解决成功
            this.resolved=true;
            //如果没有监控对象则取消执行
            //这里是first和second两个监控对象
            if(!dfd.length){
                return;
            }
            //遍历所有注册了的监控对象
            for(var i=dfd.length-1;i>=0;i--){
                //如果有任意一个监控对象没有被解决或者解决失败则返回
                if(dfd[i] && !dfd[i].resolved || dfd[i].rejected){
                    return;
                }
                //清除监控对象
                dfd.splice(i,1)
            }
            //执行解决成功回调函数方法
            _exec(doneArr);
        },
        //解决失败
        reject:function(){
            //设置当前监控对象解决失败
            this.rejected=true;
            //如果没有监控对象则取消执行
            if(!dfd.length){
                return;
            }
            //清除所有监控对象
            dfd.splice(0)
            //执行解决成功回调方法
            _exec(failArr)
        }
    }

    //创建监控对象--每一个创建的对象都互不相干
    that.Deferred=function(){
        return new Primise();
    }


    //回调执行方法
    function _exec(arr){
        var i=0;
        var len=arr.length;
        //遍历回调函数数据执行回调
        for(;i<len;i++){
            try{
                //执行回调函数
                arr[i] && arr[i]()
            }catch(e){}
        }
    }


    /**
     * waiter.when(first,second).done(function(){console.log('success')},function(){console. *log('success again')}).fail(function(){console.log('fail')})
     */
    //监控异步方法 参数是监控对象
    that.when=function(){
        //设置监控对象---这里是first和second两个监控对象
        dfd=slice.call(arguments);
        //获取监控对象数组长度
        var i=dfd.length;
        //从后向前遍历监控对象--进行一次过滤
        for(--i;i>0;i--){
            //如果不存在监控对象或者监控对象已被解决(要解决了成功或者失败才叫被解决)或者不是监控对象
            //存在 还没被解决 是其实例 不符合要求的清掉
            if(!dfd[i] || dfd[i].resolved || dfd[i].rejected || !dfd[i] instanceof Primise){
                //清理内存,清除当前监控对象
                dfd.splice(i,1)
            }
        }

        //返回等待者对象
        return that
    }

    //解决成功回调函数添加方法
    that.done=function(){
        // 向成功回调函数容器中添加回调方法
        // 成功回调方法容器 doneArr=[]
        //[].concat([function(){console.log(1)}])
        doneArr= doneArr.concat(slice.call(arguments))
        //返回等待者对象
        return that;
    }


    //解决失败回调函数添加方法
    that.fail=function(){
        //向失败回调函数容器中添加回调方法
        // failArr=[]
        failArr=failArr.concat(slice.call(arguments));
        //返回等待者对象
        return that;
    }
}


//创建一个等待者对象
var waiter=new Waiter();


//前提回顾:页面中有几个随机运动的彩蛋,当每一个彩蛋结束后我们要展示一个动作
//彩蛋在某一时刻会结束。我们要在彩蛋方法执行内部创建监听对象

//第一个彩蛋,5秒后停止
var first=function(){
    
    /**
     * that.Deferred=function(){
     *      return new Promise()
     * }
     */
    //创建监听对象
    var dtd =waiter.Deferred();

    setTimeout(function(){
        console.log('first finish');
        //发布解决信息
        dtd.resolve();

        //发布解决失败的信息
        // dtd.reject()
        /**
         * first finish
         * fail
         * second finish
         */


    },5000)
    //返回监控对象
    return dtd;
}()

//第二个彩蛋,10秒后停止
var second=function(){
    
    /**
     * that.Deferred=function(){
     *      return new Promise()
     * }
     */
    //创建监听对象
    var dtd =waiter.Deferred();

    setTimeout(function(){
        console.log('second finish');
        //发布解决信息
        dtd.resolve();
    },10000)
    //返回监控对象
    return dtd;
}()

//监听两个彩蛋的工作状态
//并执行相应的成功回调函数与失败回调函数

//first,second是两个监控对象

waiter.when(first,second).done(function(){console.log('success')},function(){console.log('success again')}).fail(function(){console.log('fail')})

/**
 * 第二个彩蛋结束后我们注册的两个回调函数成功执行
 * first finish
 * second finish
 * success
 * success again
 */

还没学完>>>