js设计模式

270 阅读3分钟

五大设计原则

S-单一职责原则

  • 一个程序只做好一件事 (能过于复杂就差分开,每个部分保持独立)

开发封闭原则

  • 对扩展开发,对修改封闭(需求时,扩展新代码,而非修改已有代码)

李氏置换原则

  • 子类能覆盖父类(父类能出现的地方子类就能出现,JS中使用较少,弱类型,承使用较少)

I-接口独立原则

  • 保存接口的单一独立,避免出现“胖接口”(类似与单一职责原则,这里更关注接口)

D-依赖倒置原则

  • 面向接口编程,依赖于抽象而不依赖于具体(使用方只关注接口而不关注具体类的实现,JS中使用较少,没有接口,弱类型)

设计模式包含三种类型

创建型

  • 工厂模式(工厂方法模式,抽象工厂模式,建造者模式)/单例模式/原型模式

结构型

  • 适配器模式/装饰器模式/代理模式/外观模式/桥接模式/组合模式 /享元模式

行为型1

  • 策略模式/迭代器模式/模板方法模式/职责连模式/观察者模式/命令模式

行为型2

  • 备忘录模式/中介者模式/状态模式/解释器模式/访问者模式

工厂模式

  • 一般是将new操作单独封装,遇到nwe时就要考虑是否用了工厂模式

class Product{
    constructor(name){
        this.name = name
    }
    init(){
        alert("init")
    }
    fn1(){
        alert("fn1")
    }
}
class Creator{
    create(name) {
        return new Product(name)
    }
}
let crateor = new Creator("create")
let  p = crateor.create("cr1")
p.init()
p.fn1()

create相当于一个工厂,调用product零散组件 其他应用场景有 jQury 的$("div"), React.createElement 这种方式符合 构造函数和创建者和分离,符合开发封闭原则

单例模式

  • 系统中被唯一使用,一个类只能有一个实例 在java中
public class SingleObj{
    private SingleObj(){

    }
    private SingleObj instance = null;
    public SingleObj getInstance() { 
    // 这里面只能有一个实例,singleobj外部是不能实例化的
        if(instance==null){
            instance = new SingleObj();
        }
        return instance;
    }
    public void login(user,pwd) {
        //..
    }
}

在js中可以这样写

class SingleObj{
    login() {
        console.log("登录");
    }
}
SingleObj.getData = (function() {
    let data;
    return function(){
        if(!data) {
            data = new SingleObj();
        }
        return data;
    }
})();
let obj1 = SingleObj.getData();
obj1.login();
let obj2 = SingleObj.getData();
obj2.login();
console.log(obj1===obj2); // 这里的实例是同一个
let obj3 = new SingleObj();
obj3.login();
console.log(obj2===obj3); // 这里就不是同一个实例了
/*
登录
登录
true
登录
false
*/

场景:jQuery中只有一个$, 登录框,购物车,个人信息,vux和redux

适配器模式

就接口格式和使用者不兼容,中间加一个适配器转换接口 add类是最开始的类,为了不改变他里面的内容,创建个Target类,对其包装道,最后调用者使用Target类里面的方法就行了,

class Add{
    beforeData() {
        return "原始数据"
    }
}
class Target{
    constructor(){
        this.add = new Add()
    }
    afterData() {
        const addData = this.add.beforeData()
        return `${addData}-新数据`
    }
}

const targets  = new Target()
const rs = targets.afterData()
console.log(rs);
// 原始数据-新数据

这种模式在vue的Computed 中有使用,对于原始dada数据,为了不改变他,在Computed里面做下处理,重新返回一个值,就可以用了。

装饰器模式

  • 为对象添加新功能,不改变其原有的结构和功能
class Circle{
    draw(){
        console.log("画园");
    }
}
class Decorator{
    constructor(circle) {
        this.circle = circle;
    }
    draw() {
        this.circle.draw();
        this.setBorder();
    }
    setBorder() {
        console.log("画边框");
    }
}
const cr = new Circle();
cr.draw();
const dc = new Decorator(cr);
dc.draw();
/*
画园
画园
画边框
*/

es6的装饰器也是同样道理

function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}
// 给a 属性和name方法加上只读的装饰器,这两个就不允许被改变了
class Person{
    @readonly
    a = "p"
    constructor(){
        this.a = "a";
    }
    @readonly
    name() {
        this.a = "aa";
        return `${this.a}`
    }
}
let p = new Person()
console.log(p.name())
p.name = "aa"

代理模式

使用者无权访问目标对象,需要通过中介来访问

class Read{
    constructor(name) {
        this.name = name;
        this.load();
    }
    load(){
        console.log("加载"+name);
    }
    show() {
        console.log("展示" + name);
    }
}
// 通过代理类,访问里面的数据
class ProxyRead{
    constructor(name) {
        this.read = new Read(name);
    }
    show(){
        this.read.show();
    }
}
let proxyRd = new ProxyRead("张");  
proxyRd.show();

外观模式

为子系统中的一组接口提供了一个高层接口,使用者使用这个高层接口 不同的参数数量可以达到不同的效果 但是外观模式不符合单一职责原则和开放封闭原则,因此需要谨慎使用

发布订阅模式

案例

class Subject{
    constructor() {
        this.state = 0;
        this.observeArr = [];
    }
    getState() {
        return this.state;
    }
    setState(state) {
        this.state = state;
        this.notifyAllObserve();
    }
    notifyAllObserve() {
        this.observeArr.forEach(observe=>{
            observe.update();
        })
    }
    attach(observe) {
        this.observeArr.push(observe);
    }
}
class Observe{
    constructor(name, subject) {
        this.name = name;
        this.subject = subject;
        this.subject.attach(this);
    }
    update() {
        console.log(`${this.name} update, state ${this.subject.getState()}`);
    }
}

let s = new Subject();
let o = new Observe("1", s);
let a= new Observe("2", s);
let b = new Observe("3", s);
s.setState("as"); // s类添加一个状态时,其他的观察者都能监听到
s.setState("ab");
/*
1 update, state as
2 update, state as
3 update, state as
1 update, state ab
2 update, state ab
*/

平时的使用场景有jq的绑定事件,一般我们是一个dom绑定一个事件,当我们一个dom绑定多个click事件的时候,当点击的时候其他事件也会触发。 还有nodejs的流读取文件的时候监听data和end事件,

迭代器模式

  • 顺序访问一个集合,使用者无需知道集合的内部结构 比如jq中dom集合,可以使用$.each 来循环遍历里面的dom进行操作。还有es6的Iterator迭代器,其next函数方法可以按顺序迭代
function each(data) {
    let ite = data[Symbol.iterator](); 
    let item = { done: false }; 
    // 当iterator迭代完成了,done为true,
    while (!item.done) {
        item = ite.next();
        if (!item.done) { console.log(item.value) }
    }
};
each(["1", 2, 58, 4, 4, 12]);
/*
1
2
58
2* 4
12
*/

for of就是Iterator的实现

状态模式

  • 一个对象有状态变化,每次状态变化都会触发一个逻辑,不能总是用if else来控制 案例:
class State{
    constructor(color){
        this.color = color;
    }
    handle(context) {
        console.log("状态" + this.color);
        context.setState(this);
    }
}
class Context{
    constructor() { 
        this.state = null;
    }
    getState() {
        return this.state;
    }
    setState(state)  {
        this.state = state;
    }
}

let context = new Context();
const a = new State("state1");
const b = new State("state2");
 a.handle(context);
 console.log(context.getState());
 b.handle(context);
 console.log(context.getState());
 /*
 状态state1
State {color: "state1"}color: "state1"__proto__: Object
 状态state2
 State {color: "state2"}
 */

状态主体context和状态state本身区分开,让状态主体可以获取不同状态 这里可以实际用的时候可以使用一个插件库javascript-state-machine状态机

<div id="btn1"></div>
    <script src="./src/state-machine.js"></script>
    <script>
        let fsm = new StateMachine({
            init: "收藏", // 初始化的时候的状态
            transitions: [{ // 几种状态变化
                name: "doStore",
                from: "收藏",
                to: "取消收藏"
            }, {
                name: "deleteStore",
                from: "取消收藏",
                to: "收藏"
            }],
            methods: {
                onDoStore: function () {
                    alert("收藏成功");
                    updateState(); // 通知状态
                },
                onDeleteStore: function () {
                    alert("取消收藏");
                    updateState();
                }
            }
        })
        let $btn = document.querySelector("#btn1")
        $btn.addEventListener("click", function () {
            if (fsm.is("收藏")) {
                fsm.doStore();
            } else {
                fsm.deleteStore();
            }
        });

        function updateState() {
            console.log("状态已经改变");
            $btn.innerHTML = fsm.state;
        }

        // 初始化btn状态,
        updateState();
    </script>

原型模式

在js中new一个对象开销是比较大的,一般可以复制一个原型来新生成一个对象使用,消耗就比较小了。一般会使用Object.create()函数来创建

let prototypes= {
        getName: function () {
            return this.first +" " + this.last
        },
        getName: function () {
            console.log("hello");
        }
    }
  
    let x =  Object.create(prototypes);
    x.first = "1";
    x.last = "2";
    console.log(x.getName());
    /*
    hello
    */
function obj(name) {
        this.name = name;
        function setName() {
            this.name = name;
            console.log("setName");
        }
    }
    obj.prototype.getName = function () {
        console.log("getName:" + this.name);
    }
    let a = new obj("xx");
    let x = Object.create(a);
    x.getName();
    /*
    getName:xx
    */