JavaScript常用设计模式总结+代码实现

1. 观察者模式

设计模式思路:

  • 变化的数据作为被观察者, 受改数据影响的元素/对象作为被观察者
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>观察者模式</title>
    <style>
        .container {
            width: 400px;
            background-color: mediumpurple;
            margin: 0 auto;
            overflow: hidden;
            text-align: center;
            padding: 30px
        }

        h2 {
            margin: 20px 0;
            color: indianred;
        }

        input {
            width: 80%;
            border-radius: 5px
        }

        .boxes {
            margin: 30px auto;
            width: 80%;
        }

        .box {
            width: 100%;
            height: 40px;
            background-color: #e5e6e4;
            border-radius: 10px;
            margin-bottom: 7px;
            line-height: 40px;
            font-weight: bold;
        }

        .active {
            background-color: #d2561c;
            color: #e5e6e4;
        }
    </style>

</head>
<body>
<div class="container">
    <h2>观察者模式实现</h2>
    <label>
        <input type="text">
    </label>
    <div class="boxes">
        <!-- <div class="box" id="a">包含a</div>
         <div class="box" id="b">包含b</div>-->
    </div>
</div>
<script type="text/javascript">

    /**
     * 表单的数据, 作为被观察者
     */
    class Input {
        constructor(el) {
            this.observers = [];
            el.addEventListener("input", e => {
                this.notifyAll(e.target.value);
            })
        }

        notifyAll(value) {
            this.observers.forEach(ob => {
                ob.notify(value)
            })
        }

        addObserver(observer) {
            this.observers.push(observer)
        }
    }

    /**
     * 观察者基类, 可用继承此类的方式定义观察者
     */
    class Observer extends DocumentFragment {
        constructor(txt) {
            super();
            this.div = document.createElement("div")
            this.div.className = "box"
            this.div.innerText = txt;
            document.querySelector(".boxes").appendChild(this.div)
        }

        notify(value) {
            this.div.classList.toggle('active', this.handle(value))
        }
    }

    /**
     * 观察者实现类
     */
    class DefaultObserver extends Observer {
        constructor(txt) {
            super(txt);
            this.txt = txt;
        }

        handle(value) {
            return value.indexOf(this.txt) !== -1
        }
    }

    class NumberObserver extends Observer {
        constructor(txt) {
            super(txt);
        }

        handle(value) {
            return /\d/.test(value)
        }
    }

    const ipt = new Input(document.querySelector("input"));
    ipt.addObserver(new DefaultObserver("a"));
    ipt.addObserver(new DefaultObserver("b"));
    ipt.addObserver(new DefaultObserver("c"));
    ipt.addObserver(new NumberObserver('包含数字'))

</script>
</body>
</html>

2. 单例模式

2.1 设计原则

  • 无论程序员手动创建多少个对象, 存在的示例永远都只有一个

2.2 实现思路

  1. 使用闭包的特性, 一个函数内部返回另一个函数;
  2. 如果示例没有被创建过, 那么调用用户传递进来的回调函数,进行对象的创建并返回; 且将结果赋值给返回函数外部的那个变量(result)
  3. 如果示例已经被创建, 那么result必然不为null, 直接将result返回就可以啦, 这样就不会在去创建一个新对象了

2.3 Code

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>闭包实现单例模式</title>
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
</head>
<body>
<button id="btn">登录</button>
<script>
    console.log(window)
    const createLogin = function (a, b, c) {
        console.log(a, b, c)
        const div = document.createElement('div')
        div.innerHTML = "登录弹窗"
        div.style.display = "none"
        document.body.appendChild(div)
        return div
    }

    const getSingle = function (cb) {
        let result;
        return function () {
            return result || (result = cb.apply(this, arguments))
        }
    }

    const create = getSingle(createLogin)
    document.getElementById("btn").onclick = () => {
        const element = create(1, 2, 3)
        element.style = {
            display: "block",
            color: "red"
        }
    }
</script>
</body>
</html>

3. 适配器模式

3.1 目的

  1. 为了解决两个接口不兼容的情况
  2. 不需要改变已有的接口, 通过包装一层的方式实现两个接口之间的正常协作
/**
 * 适配器模式
 */

//已有地图的接口
const googleMap = {
        show() {
            console.log("rendering google map...");
        }
    }

const baiduMap = {
    display() {
        console.log("rendering baiduMap...")
    }
}

//已有的渲染接口
const renderMap = function (map) {
    if (map.show instanceof Function) {
        map.show()
    }
}

//定义百度地图的适配器, 适配renderMap的show方法
const baiduMapAdapter = {
    show() {
        return baiduMap.display()
    }
}

//测试
renderMap(googleMap) //rendering google map...
renderMap(baiduMapAdapter) //rendering baiduMap...

4. 装饰模式

4.1 目的

  1. 不需要改变已有接口, 给对象添加额外的功能

4.2 实现

/**
 * 装饰模式
 */
function readonly(target, key, descriptor) {
    descriptor.writeable = false;
    return descriptor;
}

@readonly
class Man {
    name = "naruto"
}

let m = new Man()
m.name = "sasuke"

console.log(m.name)

5. 代理模式

5.1 目的

  1. 某些情况下,一个对象不适合或不能直接引用另一个对象, 而代理对象可以再两个对象之间起到中介的作用

5.2 实现

/**
 * 代理模式
 */

//示例1: 司机开车

class Car {
    drive(): string {
        return "driving"
    }
}

class Driver {
    public age: number;

    constructor(age: number) {
        this.age = age;
    }
}

class CarProxy {
    private driver: Driver;

    constructor(driver: Driver) {
        this.driver = driver;
    }

    drive(): string {
        if (this.driver.age < 18) {
            return "to yong to drive"
        }
        return new Car().drive();
    }
}

const driver = new Driver(19);
// @ts-ignore
const result = new CarProxy(driver).drive();

console.log(result);


//示例2: es6-proxy,在目标对象之间架设一层拦截
// @ts-ignore
const obj = {}

const proxy = new Proxy(obj, {
    get(target: {}, p: string | symbol, receiver: any): any {
        return Reflect.get(target, p, receiver)
    },
    set(target: {}, p: string | symbol, value: any, receiver: any): boolean {
        return Reflect.set(target, p, value, receiver)
    }
})


// @ts-ignore
proxy.count = 1;
// @ts-ignore
++proxy.count

// @ts-ignore
console.log(obj.count) //2

6. 发布, 订阅模式

6.1 目的

  • 实现对象间多对对的依赖关系
  • 通过事件中心管理多个事件
  • 目标对象并不直接通知观察者, 而是通过事件中心来派发通知

6.2 实现

//消息定义发布模式

declare interface IPubsub {
    list: Array<unknown>,
    addListener: (key: string, fn: Function) => void,
    publish: () => void
    removeListener: (key: string, fn: Function) => void | false
}

const shopObj: IPubsub = {
    //缓存列表,用来存放订阅函数
    list: [],
    //添加订阅者
    addListener: function (key, fn) {
        if (!this.list[key]) {
            this.list[key] = [];
        }
        this.list[key].push(fn);

    },
    //发布消息
    publish: function () {
        let key = Array.prototype.shift.call(arguments);
        const funcs = this.list[key];
        if (!funcs || funcs.length === 0) {
            console.log("no relevant subscriber")
            return
        }
        for (let i = 0; i < funcs.length; i++) {
            funcs[i].apply(this, arguments)
        }
    },
    //取消订阅

    removeListener(key: string, fn?: Function | null): void | false {
        const fns = this.list[key]
        if (fn === null) {
            fns.length = 0;
            return
        }
        if (!fns) {
            fns && (fns.length = 0);
            return false
        } else {
            for (let i = fns.length - 1; i >= 0; i--) {
                let _fn = fns[i]
                if (_fn === fn) {
                    fns.splice(i, 1)
                }
            }
        }
    }

}

//订阅者1
shopObj.addListener("xm", size => {
    console.log(`xm size is ${size}`)
})

//订阅者2
shopObj.addListener("张三", size => {
    console.log(`zhangsan size is ${size}`)
})

// @ts-ignore
//通知张三
shopObj.publish("张三", 120)

//张三取消订阅
shopObj.removeListener("张三", null)
// @ts-ignore
shopObj.publish("张三", 120)
//结果: no relevant subscriber

7. 策略模式

7.1 目的

  • 策略模式指的是定义一系列的算法,把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来
  • 一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类/策略对象,策略类封装了具体 的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用。

7.2 实现: 模拟antd表单校验组件逻辑

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>表单校验策略</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .my-form {
            width: 400px;
            margin: 50px auto;
            text-align: justify
        }

        .my-form label {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin: 0 0 15px;
        }

        .my-form label input {
            width: 280px;
        }
    </style>
</head>
<body>
<div class="my-form">
    <form action="">
        <label for="usrName">
            <span>请输入用户名:</span>
            <input type="text" id="usrName" name="usrName">
        </label>
        <label for="pwd">
            <span>请输入密码: </span>
            <input type="password" id="pwd" name="password">
        </label>
        <label for="email">
            <span>请输入邮箱: </span>
            <input type="text" id="email" name="email">
        </label>
        <label for="tel">
            <span>请输入手机号: </span>
            <input type="text" id="tel" name="tel">
        </label>
        <button type="submit">提交表单</button>
    </form>
</div>
<script src="./StrategyMode.js"></script>
</body>
</html>
/**
 * 策略模式. 实现表单验证的策略
 */

//策略对象,封装了具体的表单验证的算法逻辑
const ruleMap = {
        notNull(val: string, msg: string): string {
            if (!val.trim()) {
                return msg;
            }
        },
        isEmail(val: string, msg: string): string {
            const ePattern = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
            if (!ePattern.test(val)) {
                return msg;
            }
        },
        isPassword(val: string, msg: string): string {
            const pPattern = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/;
            if (!pPattern.test(val)) {
                return msg;
            }
        },
        isTel(val: string, msg: string): string {
            const mPattern = /^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/;
            if (!mPattern.test(msg)) {
                return msg;
            }
        }
    }

//环境类, 接受用户输入的数据
class Validator {
    private caches: Function[];

    constructor() {
        this.caches = []
    }

    addRules(ruleKey: string, ...args): void {
        const ruleFunc = ruleMap[ruleKey]
        this.caches.push(function () {
            return ruleFunc(...args)
        })
    }

    validate() {
        const errList = [];
        this.caches.forEach(fn => {
                const result = fn();
                if (result) {
                    //说明有错误信息
                    errList.push(result)
                }
            }
        )
        if (errList.length !== 0) {
            return Promise.reject(errList)
        } else {
            return Promise.resolve("验证通过!")
        }
    }
}

//表单绑定提交事件
const form: HTMLFormElement = document.querySelector(".my-form form")

form.addEventListener("submit", (event) => {
    event.preventDefault();
    event.stopPropagation();
    let formData = new FormData(form)
    console.log(formData)
    const validator = new Validator();
    validator.addRules("notNull", formData.get("usrName"), "请输入用户名")
    validator.addRules("isEmail", formData.get("email"), "邮箱格式不正确")
    validator.addRules("isPassword", formData.get("password"), "密码格式不正确")
    validator.addRules("isTel", formData.get("tel"), "手机号格式不正确")

    validator.validate().then((res) => {
        console.log(res)
    }).catch(error => {
        console.log(error)
    })

})
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值