搞懂javascript面向对象之结构型设计模式

160 阅读9分钟

外观模式

  • 外观模式:为一组复杂的子系统接口提供一个更高级的统一接口,通过接口使得对子接口的访问更容易,是一种‘套餐式’的较为统一的接口,是对接口方法的外层包装,以供上层代码调用。

  • 实现一个事件监听的'基础套餐'封装

function addEvent(dom,type,fn){
    //首先优先选择DOM2级事件addEventListener
    if(dom.addEventListener){
        //事件类型、回调函数、事件冒泡
        dom.addEventListener(type,fn,false)
    }else if(dom.attachEvent){
        dom.attachEvent('on'+type,fn)
    }else{
        dom['on'+type]=fn;
    }
}
  • 在‘基础套餐’的代码实现基础上实现一个事件监听‘豪华套餐’封装
//获取事件对象
var getEvent=function(event){
    return event || window.event;
}

//获取目标元素
var getTarget=function(e){
    var _event=getEvent(e)
    return e.target || e.srcElement
}

//阻止默认行为
var preventDefault=function(e){
    var _event=getEvent(e);
    e.preventDefault?e.preventDefault():e.returnValue=false
}

适配器模式

  • 将一个类(对象)的接口(方法或者属性)转换成另一个接口

  • 案例一:参数适配器

function todo(obj){
    var _adapter={
        name:'lth',
        tool:'hammer',
        weapon:'boom'
    };
    for(var j in _adapter){
        _adapter[j]=obj[j] || _adapter[j]
    }

    console.log(_adapter)
    /**
     * { name: 'lth1', tool: 'hammer', weapon: 'boom' }
     * 
     * 提示:如果在for-in中把_adapter换成obj有惊喜
     */
}


todo({
    name:'lth1',
    tool:'hammer',
    weapon:'boom',
    price:10,
    job:'secret'
})
  • 案例二:数据适配(目的是返回一个更好操纵的数据)
var arr=['english','1029','sun']

function dataAdapter(arr){
    return {
        name:arr[0],
        date:arr[1],
        mood:arr[2]
    }
}

var perData=dataAdapter(arr);
console.log(perData) //{ name: 'english', date: '1029', mood: 'sun' }
  • 案例三:参数方法适配
var oneMap={
    show:function(){
        console.log('开始渲染oneMap')
    }
}

var secondMap={
    // show:function(){
    //     console.log('开始渲染secondMap')
    // }

    //接口变了
    fade:function(){
        console.log('开始渲染secondMap')
    }
}

var secondMapAdapter={
    show:function(){
        return secondMap.fade()
    }
}



var renderMap=function(map){
    if(map.show instanceof Function){
        map.show()
    }
}

renderMap(oneMap)
renderMap(secondMapAdapter)

/**
 * 开始渲染oneMap
开始渲染secondMap
 */

代理模式

  • 由于一个对象不能直接引用另一个对象所以需要代理,在这两个对象之间起到中介的作用

  • 代理模式的运用之解决跨域的问题

  • 跨域的报错显示为XMLHttpRequest cannot load http://xx.com No 'Access-Control-Allow-Origin' header is present on the request resource

  • 统一域名不同端口号、同一域名不同协议、域名和域名对应的ip地址不同、主域和子域、子域和子域都存在跨域限制

  • 利用img的src属性向服务器单向发送(不会有响应数据)携带指定参数的信息

var info=(function(){
    var _img=new Image();

    //返回函数--此函数的目的是利用src属性将信息拼接上并传递出去
    //info是对象‘object’格式的
    return function(info){
        //供拼接的请求字符串
        var str='www.baidu.com/b.png?'
        //要拼接的字符串
        for(var i in info){
            str+= i + '=' + info[i]
        };
        //发送请求
        _img.src=str;
        console.log(_img.src)
    }
})()

info({number:100})

  • 代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
let lth = {
    sellHouse(num){
        console.log("我要卖"+ num + "元");
    }
}

let proxySeller = {
    sellHouse(hasSold,num){
        if(hasSold){
            // 抽成10元
            lth.sellHouse(num-10);
        }else{
            // 没卖没抽成
            lth.sellHouse(num);
        }
    }
}
// lth.sellHouse(100);

proxySeller.sellHouse(false,100);
  • 案例一:lth给lxm送药的故事
  • 方法一:lth自己给lxm送药
var Drug=function(){}

var lth={
    sendDrug:function(target){
        var drug=new Drug();
        target.receiveFlower(drug)
    }
}

var lxm={
    receiveDrug:function(drug){
        console.log('我收到了' + drug)
    }
}

lth.sendDrug(lxm)
  • 方法二:引入代理B,lth通过代理B来给lxm送药
  • 进阶版本为引入代理B后,代理B通过监视lxm的身体状况来送药(结合定时器)
var lth={
    sendDrug:function(target){
        var drug=new Drug();
        target.receiveDrug(drug)
    }
}

var B={
    receiveDrug:function(drug){
        lxm.watch(function(){
            lxm.receiveDrug(drug)
        })
    }
}

var lxm={
    receiveDrug:function(drug){
        console.log('我收到了' + drug)
    },
    watch:function(fn){
        setTimeout(function(){fn()},1000)
    }
}

lth.sendDrug(B)
  • 案例二:通过代理缓存的方式节省向服务器发送的请求数量,此例为如果我一直快速点击单选框,会向服务器一直发送验证的数据,如果我把每隔几秒内的数据打包起来再一起发送会好很多。
  • 该html页面的代码为
<body>
    <input type="checkbox" id="1">
    <input type="checkbox" id="2">
    <input type="checkbox" id="3">
    <input type="checkbox" id="4">
    <input type="checkbox" id="5">
    <input type="checkbox" id="6">
    <input type="checkbox" id="7">
    <input type="checkbox" id="8">
    <input type="checkbox" id="9">
</body>
  • 该js逻辑为:
 var syFile=function(id){
            console.log('文件id为'+id)
        }

        var checkBox=document.getElementsByTagName('input')
        console.log(checkBox)

        // for(var i=0,c;c=checkBox[i++];){
        //     c.onclick=function(){
        //         // console.log(this)
        //         // 如果点击迅速的话会实时不断的出现打印
        //         if(this.checked===true){
        //             syFile(this.id)
        //         }
        //     }
        // }

	//引入一个代理
        var prFile=(
            function(){
                var cache=[], //保存一段时间需要同步的id
                timer; //定时器

                return function(id){
                    cache.push(id);
                    // console.log(cache)
                    if(timer){
                        return;
                    }
                    timer=setTimeout(function(){
                        syFile(cache.join())
                        clearTimeout(timer)
                        timer=null;
                        cache.length=0
                    },2000)
                }
            }
        )()

        for(var i=0,c;c=checkBox[i++];){
            c.onclick=function(){
                // console.log(this)
                // 如果点击迅速的话会实时不断的出现打印
                if(this.checked===true){
                    prFile(this.id)
                }
            }
        }
  • 案例三:代理缓存
var mult=function(){
            var a=1;
            for(var i=0,l=arguments.length;i<l;i++){
                a=a*arguments[i]
            }
            return a;
        }
        mult(3,4)



        var proMult=(function(){
            var cache={};
            return function(){
                //var args=Array.prototype.join.call([1,2,3,4],',') --> '1,2,3,4'
                var args=Array.prototype.join.call(arguments,',');
                if(args in cache){
                    return cache[args]
                }
                return cache[args]=mult.apply(this,arguments)
            }
        })()

        proMult(1,2,3)

装修者模式

  • 在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法),使原有对象可以满足需求,装饰已有的功能对象,对已有的功能原封不动
/**
 * 
 * @param {要装饰的输入框} input 
 * @param {要新增给指定输入框的新函数} fn 
 */
var decorator=function(input,fn){
    //获取到要添加装饰的输入框
    
    var input=document.querySelector('input');

    //判断此时的输入框之前已经有添加过点击事件
    if(typeof input.onclick === 'function'){
        //记录缓存下原有的点击回调函数
        var oldFn=input.click;
        //装饰者模式开始发力为input框重新定义新的事件
        input.onclick=function(){
            //原有回调函数
            oldFn();
            //新增的回调函数
            fn();
        }
    }else{
        //如果之前没有给这个input框添加过点击事件则直接为其新增上新的回调函数
        input.onclick=fn;
    }
}

class Lth {
    constructor(){
        this.name = "lth";
    }
    release(){
        console.log("释放压力");
        // console.log("释放了"+num+"压力");
    }
}


Function.prototype.Decorator = function(fn,num){
    // console.log(this);
    this();
    fn(num);
}

// 在执行了hurt(num)
function hurt(num){
    console.log("释放了"+num+"压力");
}

let lth = new Lth();
// 先执行了lth.release();
lth.release.Decorator(hurt,10);

桥接模式

  • 在系统沿着多个维度变化的同时,有不增加其复杂度并已达到解耦,将实现层与抽象层解耦分离,使两部分可以独立变化

  • 场景描述:页面上有许多小组件需要绑定事件并在事件发生后做出相应的改变,例如改变颜色和改变背景色,这时候我们可以把这些操作提取共同点.

  • 共同点一:我们需要获取节点,共同点二:我们需要获取改变后的颜色,共同点三:我们需要获取改变后的背景色,封装为一个‘桥梁’供事件使用

function change(dom,color,bgColor){
    dom.style.color=color;
    dom.style.backgroundColor=bgColor;
}   

xx.onclick=function(){
    change(this,'red','pink')
}
  • 多维变量类的应用。
  • 移动单元和弹跳单元可以归纳是球类的属性和方法
//移动单元
function Move(a,b){
    this.a=a;
    this.b=b
}

Move.prototype.motion=function(){
    console.log('移动起来')
}

//弹跳单元
function Bounce(c){
    this.c=c;
}

Bounce.prototype.dap=function(){
    console.log('弹跳起来')
}

//归纳为可以移动和弹跳的球类
function Ball(a,b,c){
    //实现移动单元
    this.move=new Move(a,b);
    //实现弹跳单元
    this.bounce=new Bounce(c);
}


Ball.prototype.init=function(){
    //实现移动
    this.move.motion();
    //实现弹跳
    this.bounce.dap();

    //true
    // console.log(this.move.__proto__===Move.prototype)
}

var b=new Ball(1,2,3)
// console.log(b.move) //Move { a: 1, b: 2 }
// console.log(b.move.a) //1
// console.log(b.bounce) //Bounce { c: 3 }
b.init(); //移动起来 弹跳起来
  • 如果我们想要在创建一个球类它可以移动、弹跳和消失呢?
function Fade(d){
    this.fade=d;
}

Fade.prototype.fadeIn=function(){
    console.log('消失不见')
}



//归纳为可以移动、弹跳和消失的新球类
function NewBall(a,b,c,d){
    //实现移动单元
    this.move=new Move(a,b);
    //实现弹跳单元
    this.bounce=new Bounce(c);
    //实现消失
    this.fade=new Fade(d)
}

NewBall.prototype.init=function(){
    //实现移动
    this.move.motion();
    //实现弹跳
    this.bounce.dap();
    //实现消失
    this.fade.fadeIn();
}

var nb=new NewBall(1,2,3,4);
nb.init();
/**移动起来
弹跳起来
消失不见 */

组合模式

  • 组合模式又称‘套餐模式’,又称部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构,组合模式使得用户对单个对象和组合对象使用具有一致性
//组合对象
var MacroCommand=function(){
    return {
        commandsList:[],
        add:function(command){
            this.commandsList.push(command)
            return this
        },
        //并不执行真正的execute操作,而是遍历它所包含的叶对象
        //把真正的execute请求委托给这些叶对象,不在于控制叶对象的访问
        execute:function(){
            for(var i=0,command;command=this.commandsList[i++];){
                command.execute()
            }
        }
    }
}

//叶对象
var openFridgeCommand={
    execute:function(){
        console.log('打开冰箱')
    }
}

//叶对象
var openCurtainsCommand={
    execute:function(){
        console.log('打开窗帘')
    }
}


// var macroCommand= MacroCommand()
// macroCommand.add(openFridgeCommand).add(openCurtainsCommand)
// macroCommand.execute() //打开冰箱 打开窗帘

  • 为了防止对叶对象进行误操作比试图在叶对象中使用.add方法,可以在客户给叶对象误操作时抛出异常
var openTabletCommand={
    execute:function(){
        console.log('打开平板')
    },
    add:function(){
        throw new Error('叶对象不能添加子节点')
    }
}

// openTabletCommand.add(macroCommand); //Error: 叶对象不能添加子节点
  • 组合模式的运用:扫描文件夹和文件---从上往下
//文件夹Folder
var Folder=function(name){
    this.name=name;
    this.files=[]
} 

Folder.prototype.add=function(file){
    this.files.push(file)
}

Folder.prototype.scan=function(){
    console.log('现在遍历到哪一个文件夹:'+this.name);
    for(var i=0,file,files=this.files;file=files[i++];){
        file.scan();
    }
}


//文件File
var File=function(name){
    this.name=name
}

File.prototype.add=function(){
    throw new Error('文件下面不能在添加文件,它相当于叶对象')
}

File.prototype.scan=function(){
    console.log('现在遍历到哪一个文件并开始扫描文件:'+ this.name)
}

var folder1=new Folder('文件夹一');
var folder2=new Folder('文件夹二');
var folder3=new Folder('文件夹三');


var file1=new File('文件一');
var file2=new File('文件二');
var file3=new File('文件三');


// folder1.scan() // 现在遍历到哪一个文件夹:文件夹一
// folder1.add(file1);
// folder1.scan() //现在遍历到哪一个文件夹:文件夹一 现在遍历到哪一个文件并开始扫描文件:文件一
  • 组合模式的运用:扫描文件夹和文件---从下往上--引用父对象--记录父对象通过父对象来删除叶对象
//文件夹Folder
var Folder=function(name){
    this.name=name;
    this.files=[];
    this.parent=null; //增加this.parent属性
} 

Folder.prototype.add=function(file){
    file.parent=this; //设置父对象
    this.files.push(file)
    console.log(this.files)
}

Folder.prototype.scan=function(){
    console.log('现在遍历到哪一个文件夹:'+this.name);
    for(var i=0,file,files=this.files;file=files[i++];){
        file.scan();
    }
}

Folder.prototype.remove=function(){
    if(!this.parent){
        return;
    }
    console.log('folder start',this.parent.files)
    /**
     *  [ File {
    name: '文件5',
    parent: Folder { name: '文件夹5', files: [Circular], parent: null } },
    Folder {
    name: '文件夹6',
    files: [],
    parent: Folder { name: '文件夹5', files: [Circular], parent: null } } ]
     */
    for(var files=this.parent.files,l=files.length-1;l>=0;l--){
        var file=files[l];
        if(file===this){
            files.splice(l,1)
            console.log('folder over',files)
            /**
             *  folder over [ File {
                name: '文件5',
                parent: Folder { name: '文件夹5', files: [Circular], parent: null } } ]
             */
        }
    }
}

//文件File
var File=function(name){
    this.name=name;
    this.parent=null;
}

File.prototype.add=function(){
    throw new Error('文件下面不能在添加文件,它相当于叶对象')
}

File.prototype.scan=function(){
    console.log('现在遍历到哪一个文件并开始扫描文件:'+ this.name)
}


File.prototype.remove=function(){
    if(!this.parent){
        return;
    }
    for(var files=this.parent.files,l=files.length-1;l>=0;l--){
        var file=files[l];
        if(file===this){
            files.splice(l,1)
            console.log('file over')
        }
    }
}


var folder5=new Folder('文件夹5');
folder5.add(new File('文件5'))
var folder6= new Folder('文件夹6')
folder5.add(folder6)
/**
 * 
 * folder over
    现在遍历到哪一个文件夹:文件夹5
    现在遍历到哪一个文件并开始扫描文件:文件5 
 */
/**
 * [ File {
    name: '文件5',
    parent: Folder { name: '文件夹5', files: [Circular], parent: null } },
    Folder {
    name: '文件夹6',
    files: [],
    parent: Folder { name: '文件夹5', files: [Circular], parent: null } } ]
 */
// folder5.scan();//现在遍历到哪一个文件夹:文件夹5 现在遍历到哪一个文件并开始扫描文件:文件5
folder6.remove()
folder5.scan()

亨元模式

  • 运用共享技术有效的支持大量的细粒度的对象,主要是对数据、方法共享分离,它将数据和方法分成内部数据、内部方法、外部数据和外部方法
var Human=function(age){
    //内部状态
    this.age=age;
};

Human.prototype.show=function(){
    console.log(this.age+' external state '+this.externalState)
}

//创建出共享对象---只有当共享状态被需要时才被创建出来
var people=new Human('20');

for(var i=1;i<20;i++){
    //在这里定义外部状态
    people.externalState='externalState'+i;
    people.show();
}

还没学完>>>