JS--设计模式

52 阅读4分钟

一、单例模式

  • 设计模式:
    • 为了实现某一类功能给出的简洁而优化的解决方案
  • 设计模式 - 单例模式
    • 核心意义: 一个构造函数(类) 一生只有一个实例
  • 思路:
    • 实例化一个类的时候
    • 需要先判断, 如果当前类以前被实例化过, 不需要再次实例化了
    • 而是直接拿到原先的实例化对象
    • 如果没有被实例化过, 那么新实例化一个实例对象
  • 解决:
    • 单独找一个变量, 初始化为 null
    • 每一次创建实例的时候, 都判断一下该变量的值
      • 如果为 null, 那么创建一个实例, 赋值给该变量
      • 如果不为 null, 那么我们不再创建实例, 直接给出该变量的值
class Dialog {
    constructor() {
        console.log("在 页面中 创建了一个 弹出层");
    }
}

let instance = null;
function newDialog() {
    if (instance == null) {
        instance = new Dialog();
    }
    return instance;
}

// 第一次调用, 创建一个 实例化对象然后给到 instance 中, 并返回 instance
let d1 = newDialog();

// 第二次调用, 直接返回 instance
let d2 = newDialog();

console.log(d1);
console.log(d2);
console.log(d1 === d2);

单例模式变形

  • 单例模式问题

    1. 向外部写了一个全局变量 instance
    2. 多次调用时, 无法传递不一样的参数
    3. 构造函数的类名与创建实例的函数名是两个东西
  • 解决:

    1. 利用闭包
    let newDialog = (function fn() {
        let instance = null;
        return function () {
            if (instance == null) {
                instance = new Dialog();
            }
            return instance;
        };
    })();
    
    1. 创建一个 init 方法
    class Dialog {
        constructor() {
            console.log("在 页面中 创建了一个 弹出层, 文本为: ");
            this.title = "";
        }
        setTitle(newTitle) {
            this.title = newTitle;
            console.log("将 title 属性 重新修改为了: ", this.title);
        }
    }
    let newDialog = (function outer() {
        let instance = null;
        return function inner(str) {
            // 没有接收到不同参数的原因在于, 这个 if 只有第一次才会执行, 后续永远不会执行
            if (instance == null) {
                instance = new Dialog();
            }
            // 但是, 这个位置却可以多次执行, 所以我们可以在这里, 给这个 类传递不同的参数
            instance.setTitle(str);
            return instance;
        };
    })();
    
    1. 将类放在闭包函数内, 然后将函数名修改为类名
    let Dialog = (function outer() {
        // 因为 Dialog 这个 类, 永远就使用一次, 所以函数局部 更合适
        class Dialog {
            constructor() {
                console.log("在 页面中 创建了一个 弹出层, 文本为: ");
                this.title = "";
            }
            setTitle(newTitle) {
                this.title = newTitle;
                console.log("将 title 属性 重新修改为了: ", this.title);
            }
        }
    
        let instance = null;
        return function inner(str) {
            if (instance == null) {
                instance = new Dialog();
            }
            instance.setTitle(str);
            return instance;
        };
    })();
    

二、策略模式

  • 设计模式 - 策略模式
    • 核心: 减少 if...else...
    • 作用: 把多种形式的内容罗列出来
  • 案例:
    • 已知一个购物总价 1376
    • 需要根据折扣计算最终价格
    • 折扣种类: 8 折, 7 折, 300-30, 500-50
  • 当前案例核心:
    • 用一个数据结构, 把各种折扣记录下来
    • 值存储的就是该折扣和总价的计算方式
const calcPrice = (function () {
    const calcList = {
        "80%": (total) => (total * 0.8).toFixed(2),
        "70%": (total) => (total * 0.7).toFixed(2),
    };
    // 任何引用数据类型, 都可以当作 对象去使用
    function inner(type, total) {
        return calcList[type](total);
    }
    inner.add = function (type, fn) {
        calcList[type] = fn;
    };
    inner.sub = function (type) {
        delete calcList[type];
    };
    inner.getList = function () {
        return calcList;
    };
    return inner;
})();
const res = calcPrice("80%", 1000);
console.log(res);

const res1 = calcPrice("70%", 1000);
console.log(res1);

calcPrice.add("300-30", (total) => total - Math.floor(total / 300) * 30);

const res2 = calcPrice("300-30", 1000);
console.log(res2);

calcPrice.sub("70%");
console.log(calcPrice.getList());

三、发布订阅模式

  • 设计模式-发布订阅
    • 首先明确 这个模式和一个叫做观察者模式类似但不是一个东西
    • 目前一批开发人员认为这两个是一个东西, 另外一批认为是两个东西
  • 案例: 买一本书
    • 以前
      1. 去到书店, 问店员有没有 JS 从入门到入土
      2. 没有, 回去
      3. 一会再回去问, 有没有
      4. 没有, 回去
      5. 一会再回去问, 有没有
      6. ...
      7. 一会再回去问, 有没有
      8. 有了, 购买
    • 现在
      1. 去到书店, 问店员有没有 JS 从入门到入土
      2. 没有, 给店员留下一个联系方式
      3. 直到店员给你打电话, 你触发技能(去买回来)
  • 在 JS 内 发布订阅 使用最多的地方 (addEventListener)
    • xxx.addEventListener('click', fn)
  • 思考: 以什么数据格式来记录
店员 ===
    {
        a: [fn1, fn2],
        b: [fn3, fn4],
    };
class Observer {
    constructor(name) {
        this.name = name; // 这步模拟店员名字, 可以不要
        // 店员的记录手册
        this.messages = {};
    }
    add(type, fn) {
        if (this.messages[type] === undefined) this.messages[type] = [];

        this.messages[type].push(fn);
    }
    remove(type, fn) {
        if (this.messages[type] === undefined) return;

        // if (fn === undefined) return this.messages[type] = []
        if (fn === undefined) return delete this.messages[type];

        this.messages[type] = this.messages[type].filter((item) => item !== fn);
    }
    trigger(type) {
        // console.log(type)
        // console.log(this.messages[type])
        this.messages[type].forEach((item) => item());
    }
}

const ob = new Observer("小李");

const A1 = () => {
    console.log("我是 张三, 我要买这本书");
};
const A2 = () => {
    console.log("我是 李四, 我要买这本书");
};
const B1 = () => {
    console.log("我是 王五, 我要买这本书");
};

ob.add("A1", A1);
ob.add("A1", A2);
ob.add("B1", B1);

// ob.remove('A1', A1)
// ob.remove('A1')
ob.remove("C1");

ob.trigger("A1");

console.log(ob);