js设计模式(更新中)

119 阅读8分钟

1、构造器模式

原始写法

const person1 = {
    name:'zyf',
    age:10
}
const person2 = {
    name:'pp',
    age:20
}
....

这些对象的模板都一样,有没有什么简单一点的写法呢?

构造器写法

function Person(name,age){
    this.name = name
    this.age = age
}
const person1 = new Person('zyf','10')
const person2 = new Person('ls','20')

2、原型模式

构造器写法

function Person(name,age){
    this.name = name
    this.age = age
    this.info = function(){
        console.log(this.name, this.age)
    }
}
const person1 = new Person("zyf", "10");
const person2 = new Person("ls", "20");
person1.info();
person2.info();

image.png

构造器模式有一个很明显的缺点,那就是在new一个对象时,如果里面存在有方法,这个方法就会反复的创建,这样会导致内存的冗余

原型写法

function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.info = function () {
    console.log(this.name, this.age);
};
const person1 = new Person("zyf", "10");
const person2 = new Person("ls", "20");
person1.info();
person2.info();

可以发现方法从始至终只创建了一次,且功能也完全得以实现

image.png

3、工厂模式

设计一个权限导航页面,根据用户角色,来决定展示什么页面

构造器模式

funtion User(role,page){
    this.role = role
    this.page = page
}
const user1 = new User('customer',['login','home','shopping'] )
const user2 = new User('admin',['login','home','shopping','manage'] )
const user3 = new User('customer',['login','home','shopping'] )
const user4 = new User('customer',['login','home','shopping'] )
...

会发现当创建用户时,后面的页面内容很多余,创建一百个用户时,难道页面的代码也要写一百遍吗?

工厂写法

function Factor(role) {
    function User(role, page) {
        this.role = role;
        this.page = page;
    }
    switch (role) {
        case "admin":
            return new User("admin", [
                "login",
                "home",
                "shopping",
                "manage",
            ]);
            break;
        case "superAdmin":
            return new User("superAdmin", [
                "login",
                "home",
                "shopping",
                "manage",
                "add",
                "delete",
            ]);
        default:
            return new User("customer", [
                "login",
                "home",
                "shopping",
            ]);
    }
}
const user1 = new Factor("customer");
const user2 = new Factor("admin");
console.log(user1);
console.log(user2);

image.png

4、抽象工厂模式

普通工厂返回的是一个实例对象,抽象工厂返回的是一个类,这样可以根据不同的角色类型,来写一些逻辑并统一进行封装

抽象工厂写法

需要用到寄生组合继承的知识

function User(name, role, pages) {
    this.name = name;
    this.role = role;
    this.pages = pages;
}
User.prototype.welcome = function () {
    console.log("欢迎回来", this.name);
};
User.prototype.data = function () {
    throw new Error("抽象方法需要被实现");
};

function SuperAdmin(name) {
    User.call(this, name, "superAdmin", [
        "login",
        "home",
        "shopping",
        "manage",
        "add",
        "delete",
    ]);
}
SuperAdmin.prototype = Object.create(User);
// 权限功能
SuperAdmin.prototype.dataShow = function () {
    console.log("superadmin-datashow");
};
SuperAdmin.prototype.addRight = function () {};

function Editor(name) {
    User.call(this, name, "editor", ["home", "shopping", "manage"]);
}
Editor.prototype = Object.create(User);
Editor.prototype = User.prototype;
// 权限功能
Editor.prototype.dataShow = function () {
    console.log("editor-datashow");
};

function getAbstractUserFactor(role) {
    switch (role) {
        case "superAdmin":
            return SuperAdmin;
        case "editor":
            return Editor;
        default:
            return null;
    }
}

const editorClass = getAbstractUserFactor("editor");
const user = new editorClass("zyf");
user.welcome();

image.png


可以用es6的class写法,结构看起来也比较清晰

class User {
    constructor(name, role, pages) {
        this.name = name;
        this.role = role;
        this.pages = pages;
    }
    welcome() {
        console.log("欢迎回来", this.name);
    }
    data() {
        throw new Error("抽象方法需要被实现");
    }
}

class SuperAdmin extends User {
    constructor(name) {
        super(name, "superAdmin", [
            "login",
            "home",
            "shopping",
            "manage",
            "add",
            "delete",
        ]);
    }
    dataShow() {
        console.log("superadmin-datashow");
    }
    addRight() {}
}
class Editor extends User {
    constructor(name) {
        super(name, "editor", ["home", "shopping", "manage"]);
    }
    dataShow() {
        console.log("editor-datashow");
    };
}

class getAbstractUserFactor {
    constructor(role) {
        switch (role) {
            case "superAdmin":
                return SuperAdmin;
            case "editor":
                return Editor;
            default:
                return null;
        }
    }
}

const editorClass = new getAbstractUserFactor("editor");
const user = new editorClass("zyf");
user.welcome();

image.png

5、建造者模式

  • 建造者模式属于创建模式的一种,提供一种创建复杂对象的方式,它将一个复杂的对象构建与它的表示分离,使得同样的构建过程可以创建不同的表示

  • 建造者模式是一步一步的创建一个复杂的对象,它允许用户只通过指定复杂的对象的类型和内容就可以构建它们,用户不需要指定内部的具体构造细节

原始写法

class Cat
    init(){
        console.log('喵喵喵');
    }
    getDate(){
        console.log('爱吃鱼');
    }
    render(){
        console.log('可爱捏');
    }
}
class Dog{
    init(){
        console.log('汪汪汪');
    }
    getDate(){
        console.log('爱吃肉');
    }
    render(){
        console.log('忠诚捏');
    }
}
const cat = new Cat()
cat.init()
cat.getDate()
cat.render()

const dog = new Dog()
dog.init()
dog.getDate()
dog.render()

image.png

每个方法都得挨个去创建

建造者写法

class Cat {
    init() {
        console.log("喵喵喵");
    }
    getDate() {
        console.log("爱吃鱼");
    }
    render() {
        console.log("可爱捏");
    }
}
class Dog {
    init() {
        console.log("汪汪汪");
    }
    getDate() {
        console.log("爱吃肉");
    }
    render() {
        console.log("忠诚捏");
    }
}
class Animal {
    create(param) {
        param.init();
        param.getDate();
        param.render();
    }
}
const animal = new Animal();
animal.create(new Cat());
animal.create(new Dog());

image.png

6、单例模式

  • 利用闭包的不可回收机制来实现
  • 一个类仅有一个实例,并提供一个访问它的全局访问点
  • 主要解决一个全局使用的类频繁的创建和销毁,占用内存

简单的单例实现

const single = (function () {
    let instance;
    function User(name, age) {
        this.name = name;
        this.age = age;
    }
    return function (name, age) {
        if (!instance) {
            instance = new User(name, age);
        }
        return instance;
    };
})();

单例的应用场景:

  • redux 、vuex,这些技术都是基于,单例的每次打开的都是同样的实例这一性质来实现的
  • 对话框,每次点开好友的对话框都是同一个对话框,上面还有聊天记录

简单的场景实现

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
            .kerModal {
                position: fixed;
                width: 200px;
                height: 200px;
                line-height: 200px;
                text-align: center;
                left: 50%;
                top: 30%;
                transform: translate(-50%, -50%);
                background-color: rgb(33, 172, 214);
                display: none;
            }
        </style>
    </head>
    <body>
        <button id="open">打开</button>
        <button id="close">关闭</button>
        <script>
            const Modal = (function () {
                let instance = null;
                return function () {
                    if (!instance) {
                        instance = document.createElement("div");
                        instance.innerHTML = "登录对话框";
                        instance.className = "kerModal";
                        instance.style.display = "none";
                        document.body.appendChild(instance);
                    }
                    return instance;
                };
            })();
            document.querySelector("#open").onclick = function () {
                // 创建modal
                const modal = Modal();
                // 显示modal
                modal.style.display = "block";
            };

            document.querySelector("#close").onclick = function () {
                // 创建modal,每次创建调用函数时,都会进行里面的if判断,若已经创建,则沿用之前的实例
                const modal = Modal();
                // 关闭modal
                modal.style.display = "none";
            };
        </script>
    </body>
</html>

7、 装饰器模式

装饰器模式能够很好的对已有功能进行拓展,这样不会更改原有的代码,对其他的业务产生影响,这方便我们在较少的改动下对软件功能进行拓展

简单的装饰器模式写法

Function.prototype.before = function (beforeFun) {
    const _this = this;
    return function () {
        beforeFun.apply(this, arguments);
        return _this.apply(this, arguments);
    };
};
Function.prototype.after = function (afterFn) {
    const _this = this;
    return function () {
        const ret = _this.apply(this, arguments);
        afterFn.apply(this, arguments);
        return ret;
    };
};
function test() {
    console.log(22222);
    return 44444;
}
const test1 = test
    .before(() => {
        // debugger;
        console.log(11111);
    })
    .after(() => {
        console.log(33333);
    });
console.log(test1());

输出结果为

image.png

console.log打印输出统一在这里运行

image.png

装饰器模式类似于一个可拔插的接口工具,可对前后的业务逻辑进行处理

简单的装饰器模式实践

<button id="filmbtn">点击</button>
<script>
    Function.prototype.before = function (beforeFun) {
        const _this = this;
        return function () {
            beforeFun.apply(this, arguments);
            return _this.apply(this, arguments);
        };
    };
    Function.prototype.after = function (afterFn) {
        const _this = this;
        return function () {
            const ret = _this.apply(this, arguments);
            afterFn.apply(this, arguments);
            return ret;
        };
    };
    function log() {
        console.log("上传uv,pv数据");
    }
    function render() {
        console.log("页面处理逻辑");
    }
    render = render.before(log);
    filmbtn.onclick = function () {
        render();
    };
</script>

运行结果

image.png

可以同步或异步的控制输出顺序

8、适配器模式

将一个类的接口转换成客户希望的另一个接口。适配器能让那些接口不兼容的类一起工作

实现一个统一运行接口的方法renderMap

普通写法

class TecentMap {
    show() {
        console.log("开始渲染腾讯地图");
    }
}
class BaiduMap {
    display() {
        console.log("开始渲染百度地图");
    }
}
function renderMap(map) {
    map.display?.() || map.show?.();
}
renderMap(new TecentMap());
renderMap(new BaiduMap());

可以看到,方法renderMap里还需要加判断语句,来对各种接口进行判断

适配器写法

class TecentMap {
    show() {
        console.log("开始渲染腾讯地图");
    }
}
class BaiduMap {
    display() {
        console.log("开始渲染百度地图");
    }
}
/* 
* 对TecentMap接口进行适配,在外面包一层display的方法名,来和baiduMap的display方法名保持一致
*/
class TencentAdapater extends TecentMap {
    constructor() {
        super();
    }
    display() {
        this.show();
    }
}
// 统一执行display方法
function renderMap(map) {
    map.display();
}
renderMap(new TencentAdapater());
renderMap(new BaiduMap());

9、策略模式

策略模式定义了一系列算法,并将每个算法封装起来,使它们可以相互转换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分隔开来,并委派给不同的对象对这些算法进行管理。

该模式主要解决在有多种算法相似的情况下,使用if...else所带来的复杂和难以维护。它的优点是算法可以自由切换,同时可以避免多重if...else判断,且具有良好的扩展性

普通写法

function calBonus(level, salary) {
    if (level === "A") {
        return salary * 4;
    }
    if (level === "B") {
        return salary * 5;
    }
    if (level === "C") {
        return salary * 6;
    }
}
calBonus("A", 10000);
calBonus("B", 100);

策略写法

const strategry = {
    A: (salary) => salary * 4,
    B: (salary) => salary * 5,
    C: (salary) => salary * 6,
};

function calBonus(level, salary) {
    return strategry[level](salary);
}
calBonus("A", 10000);
calBonus("B", 100);

10、代理模式

代理模式,为其它对象提供一种代理以控制对这个对象的访问

代理模式使得代理对象控制具体对象的引用,代理几乎可以是任何对象:文件,资源内存中的对象,或者是一些难以复制的对象

类似于明星接戏,都有一个经纪人的

class Star {
    play() {
        console.log("演戏");
    }
}
// 经纪人
class StarProxy {
    constructor() {
        this.superStar = new Star();
    }
    talk(price) {
        if (price >= 10000) {
            this.superStar.play();
        } else {
            console.log("价钱不合适");
        }
    }
}
const jr = new StarProxy();
jr.talk(1000);
jr.talk(10000);

打印结果

image.png


ES13中的代理proxy

const star = {
    name: "tiechui",
    workPrice: 10000,
};
const proxy = new Proxy(star, {
    get(target, key) {
        if (key === "workPrice") {
            return "已经访问了";
        }
    },
    set(target, key, value) {
        if (key === "workPrice") {
            console.log("设置了");
            if (value > 10000) {
                console.log("可以合作");
            } else {
                throw error("价钱不合适");
            }
        }
    },
});

打印结果

image.png

11、观察者模式

观察者模式包含观察目标和观察者两类对象

一个目标可以有任意数目的与之相依赖的观察者

一旦观察者目标的状态发生改变,所有的观察者都将得到通知

当一个对象的状态发生改变时,所有的依赖于它的对象都得到通知并被自动更新,解决了主题对象与观察者之间功能的耦合,即一个对象状态改变和其他对象的通知的问题

观察者写法

class Subject {
    constructor() {
        this.observe = [];
    }
    add(observe) {
        this.observe.push(observe);
    }
    remove(observe) {
        this.observe = this.observe.filter(
            (item) => item !== observe
        );
    }
    notify() {
        this.observe.forEach((item) => {
            item.update();
        });
    }
}
class Observer {
    constructor(name) {
        this.name = name;
    }
    update() {
        console.log("我叫", this.name);
    }
}
const subject = new Subject();
const observe1 = new Observer("zyf");
const observe2 = new Observer("ls");

subject.add(observe1);
subject.add(observe2);
subject.remove(observe1);
subject.notify();

应用场景

  • 面包屑
  • rxjs
  • ...

面包屑案例编写

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    <style>
        .box {
            display: flex;
            height: 600px;
        }
        .box .left {
            width: 150px;
            background-color: yellow;
        }
        .box .right {
            flex: 1;
            background-color: aqua;
        }
    </style>
    <body>
        <header class="header"></header>
        <div class="box">
            <div class="left">
                <ul>
                    <li>首页</li>
                    <li>用户管理</li>
                    <li>权限管理</li>
                    <li>新闻管理</li>
                </ul>
            </div>
            <div class="right">
                <div class="bread"></div>
            </div>
        </div>
        <script>
            class Subject {
                constructor() {
                    this.observe = [];
                }
                add(observe) {
                    this.observe.push(observe);
                }
                remove(observe) {
                    this.observe = this.observe.filter(
                        (item) => item !== observe
                    );
                }
                notify(data) {
                    this.observe.forEach((item) => {
                        item.update(data);
                    });
                }
            }
            class Observer {
                constructor(element) {
                    this.element = document.querySelector(element);
                }
                update(data) {
                    this.element.innerHTML = data;
                }
            }
            const subject = new Subject();
            const observe1 = new Observer(".bread");
            const observe2 = new Observer(".header");
            let oli = document.querySelectorAll(".left li");
            for (let i = 0; i < oli.length; i++) {
                oli[i].onclick = function () {
                    subject.notify(this.innerHTML);
                };
            }
            subject.add(observe1);
            subject.add(observe2);
        </script>
    </body>
</html>

当点击左侧的导航栏时,右侧以及上面会出现点击的导航栏信息

image.png

12、发布订阅模式

观察者和目标要相互知道

发布者和订阅者不用相互知道,通过第三方实现调度,属于经过解耦合的观察者模式

发布订阅简洁版

const PubSub = {
    list: [],
    publish() {
        this.list.forEach((item) => item());
    },
    subscript(cb) {
        this.list.push(cb);
    },
};
function testA() {
    console.log("testA");
}
function testB() {
    console.log("testB");
}
PubSub.subscript(testA);
PubSub.subscript(testB);
PubSub.publish();