JS之设计模式介绍

1 单例模式 单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再

1.单例模式

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

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

适用场景:一个单一对象。比如:弹窗,无论点击多少次,弹窗只应该被创建一次。

class User{
    constructor(name){
        this.name = name
    }
    getName(){
        return this.name
    }
}

const Proxy = (function(){
    let user = null
    return function(name){
        if(!user){
            user = new User(name)
        }
        return user
    }
})()

const u1 = Proxy('lyh')
const u2 = Proxy('fjh')
console.log(u1 == u2)

以上程序运算结果为true。

 

2.代理模式

代理模式的定义:为一个对象提供一个代用品或占位符,以便控制对它的访问。

常用的虚拟代理形式:某一个花销很大的操作,可以通过虚拟代理的方式延迟到这种需要它的时候才去创建(例:使用虚拟代理实现图片懒加载)

图片懒加载的方式:先通过一张loading图占位,然后通过异步的方式加载图片,等图片加载好了再把完成的图片加载到img标签里面。

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>

</body>
<script type="text/javascript">
    const loadingPic = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fi0.hdslb.com%2Fbfs%2Farticle%2Fwatermark%2F17a5be8be1aa3f75acbab507617aa3e851030642.gif&refer=http%3A%2F%2Fi0.hdslb.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1669341629&t=3e651a0c04ff783cc83592e8345c32f1'
    const killuaPic = 'https://img0.baidu.com/it/u=3308819396,1624138451&fm=253&fmt=auto&app=138&f=JPEG?w=700&h=393'

    const imgFunc = (function(){
        const img = document.createElement('img')
        document.body.appendChild(img)
        return {
            setSrc: function(url){
                img.src = url
            }
        }
    })()

    const imgProxy = (function(){
        const img = new Image
        img.onload = function(){
            setTimeout(()=>imgFunc.setSrc(img.src),2000)
        }
        return {
            setSrc: function(url){
                imgFunc.setSrc(loadingPic)
                img.src = url
            }
        }
    })()

    // imgFunc.setSrc(killuaPic)
    imgProxy.setSrc(killuaPic)
</script>
</html>

使用代理模式实现图片懒加载的优点还有符合单一职责原则。减少一个类或方法的粒度和耦合度。

 

3.中介者模式

中介者模式,顾名思义,确实是需要有一位中介,也就是中间人,来将其他对象联系在一起,从而达到了解除其他对象与对象之间紧密耦合的关系。

举个例子,马上要过年了,年后我们需要走访各个亲戚家,什么七大姑八大姨的,数不胜数,关系乱七八糟,平日里经常往来的没几个,有些甚至连对方叫什么都不知道就拜年要红包。

这时,我们在想,要是有个中介来帮你走访亲戚,拜年要红包就好了,这样你就不在需要记住那么多的亲戚了,只需要通知中介一个人便可。这就是中介者模式的思想。

让我们来用代码实现一下。

分析:我们通过封装了一个类,将所有亲戚保存下来,这样就不用我们自己来记忆,只要通过暴露出来的方法,与中介沟通便可。

通过 add 函数来添加我们与某个亲戚之间的关系,如果某一亲戚不幸挂了我们可以通过 remove 将其从亲戚列表中删除,通过 payNewYear 来向亲戚 拜年要红包。

如果还有什么需求我们可以自己添加。

class Mediator{
    constructor(){
        this.relatives = {}
    }

    add(name,relation){
        this.relatives[name] = relation
    }

    remove(name){
        delete this.relatives[name]
    }

    payNewYear(name){
        this.relatives[name] && console.log(`${this.relatives[name]},新年快乐,红包拿来`)
    }
}

const mediator = new Mediator
mediator.add('小锋','叔叔')
mediator.add('大勇','伯伯')

mediator.payNewYear('小锋')
mediator.payNewYear('大勇')

mediator.remove('大勇')
mediator.payNewYear('大勇')

 

4.装饰者(装饰器)模式

定义为在不改变对象自身的基础上,在程序运行期间给对象动态地添加方法。

适用的场景:原有方法维持不变,在原有方法上再挂载其他方法来满足现有需求;

函数的解耦,将函数拆分成多个可复用的函数,再将拆分出来的函数挂载到某个函数上,实现相同的效果但增强了复用性。

例:用AOP装饰函数实现装饰者模式

Function.prototype.before = function(beforeFn) {
    const self = this
    return function(){
        beforeFn.apply(this,arguments)
        return self.apply(this,arguments)
    }
};

Function.prototype.after = function(afterFn) {
    const self = this
    return function(){
        const ret = self.apply(this,arguments)
        afterFn.apply(this,arguments)
        return ret
    }
};

function f(){
    console.log(2)
}

function f1(){
    console.log(1)
}

function f3(){
    console.log(3)
}

f = f.before(f1).after(f3)
f()

 

5.工厂模式

工厂模式定义一个用于创建对象的接口,这个接口由子类决定实例化哪一个类。该模式使一个类的实例化延迟到了子类。而子类可以重写接口方法以便创建的时候指定自己的对象类型。

简单说:假如我们想在网页面里插入一些元素,而这些元素类型不固定,可能是图片、链接、文本。

根据工厂模式的定义,在工厂模式下,工厂函数只需接受我们要创建的元素的类型,其他的工厂函数帮我们处理。

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>

</body>
<script type="text/javascript">
class Text{
    constructor(text){
        this.text = text
    }

    insert(where){
        const node = document.createTextNode(this.text)
        where.appendChild(node)
    }
}

class Link{
    constructor(url){
        this.url = url
    }

    insert(where){
        const node = document.createElement('a')
        node.innerText = this.url
        node.href = this.url
        node.target = '_blank'
        where.appendChild(node)
    }
}

class Img{
    constructor(url){
        this.url = url
    }

    insert(where){
        const node = document.createElement('img')
        node.src = this.url 
        where.appendChild(node)
    }
}

// const text = new Text('lyh')
// text.insert(document.body)

// const link = new Link('http://www.baidu.com')
// link.insert(document.body)

// const img = new Img('https://img0.baidu.com/it/u=3308819396,1624138451&fm=253&fmt=auto&app=138&f=JPEG?w=700&h=393')
// img.insert(document.body)

class DomFactory{
    constructor(type){
        // this.type = type
        return new (this[type]())
    }

    text(){
        return Text
    }

    link(){
        return Link
    }

    img(){
        return Img
    }
}

const linkFactory = new DomFactory('link')
linkFactory.url = ('http://www.baidu.com')
linkFactory.insert(document.body)

const imgFactory = new DomFactory('img')
imgFactory.url = ('https://img0.baidu.com/it/u=3308819396,1624138451&fm=253&fmt=auto&app=138&f=JPEG?w=700&h=393')
imgFactory.insert(document.body)
</script>
</html>

 

6.外观模式

定义:为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用

核心:可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统

实现:外观模式在JS中,可以认为是一组函数的集合

function start(){
    console.log('start')
}

function doing(){
    console.log('doing')
}

function end(){
    console.log('end')
}

//外观函数
function doSth(){
    start()
    doing()
    end()
}

function init(){
    doSth()
}
init()

 

7.策略模式

参考JS使用策略模式优化条件选择结构一文。