JS设计模式

设计模式是为了更好的代码重用性,可读性,可靠性,可维护性。

1、适配器模式: 类似于生活中的转换器 让不兼容的东西兼容
相当于 我想要的不是你 但是把你转换成我想要的 这转换过程就是适配器模式
适配器模式可以看作一种“补偿模式”。
比如:
知道很多UI组件或者工具库会按指定的数据格式进行渲染,同一数据我们使用的地方有几个,格式不同。
可能功能1使用的数据格式是这样,需要将其一一对应列举起来,则接口返回格式:

[
  {
    "day": "周一",
    "uv": 6300
  },{
    "day": "周二",
    "uv": 7100
  },  {
    "day": "周三",
    "uv": 4300
  },  {
    "day": "周四",
    "uv": 3300
  },  {
    "day": "周五",
    "uv": 8300
  },  {
    "day": "周六",
    "uv": 9300
  }, {
    "day": "周日",
    "uv": 11300
  }
]

功能2处则Echarts图表图形需要的数据格式:

["周二", "周二", "周三", "周四", "周五", "周六", "周日"] //x轴的数据

[6300. 7100, 4300, 3300, 8300, 9300, 11300] //坐标点的数据

这时后端大哥不愿再提供接口,我们就需要适配器来解决问题:

//x轴适配器
function echartXAxisAdapter(res) {
  return res.map(item => item.day);
}

//坐标点适配器
function echartDataAdapter(res) {
  return res.map(item => item.uv);
}

还有我相当于引入一个插件库,我引入其中的一个方法,比如lodash里面的extend函数,
我需要将两个对象合并之后我再在对象的里面添加一个length属性来方便将这个对象通过array.from()转换成数组(上次分享的),则可以适配器来实现;

function extendLengthAdapter(obja, objb) {
    let obj =  _.extend(obja, objb);
    obj.length = Object.keys(obj).length;
    return obj;
}

vue的计算属性其实也是一种适配器思想,比如你玩游戏打赌输了,你需要把名字倒过来写,原来的输出你名字的接口不适用,则需要在原来的基础上适配出一个合适你的适配器。

//HTML代码
<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>

//javascirpt
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 计算属性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 实例
      return this.message.split('').reverse().join('')
    }
  }
})

//显示结果
Original message: "Hello"
Computed reversed message: "olleH"

2、状态模式
状态模式的使用场景也特别明确,有如下两点:
①一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
②一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。状态通常为一个或多个枚举常量的表示。
简而言之,当遇到很多同级if-else或者switch的时候,可以使用状态模式来进行简化
应用场景:灯泡状态、红绿灯切换等
常用的收藏功能
常规写法:

var Collection = function() {
    this.state = 'noCollection'; // 给电灯设置初始状态 off
    this.button = null; // 电灯开关按钮
};
Collection.prototype.init = function() {
    var button = document.createElement('button'),
        self = this;
    button.innerHTML = '💗';
    this.button = document.body.appendChild(button);
    this.button.onclick = function() {
        self.buttonWasPressed();
    }
};
Collection.prototype.buttonWasPressed = function() {
    if (this.state === 'noCollection') {
        this.collection()
    } else if (this.state === 'collectioned') {
        this.cancel();
    }
};
Collection.prototype.collection = function() {
    this.state = 'collectioned';
    console.log('收藏成功')
};
Collection.prototype.cancel = function() {
    this.state = 'noCollection';
    console.log('取消收藏成功')
};
let coll = new Collection();
coll.init();

使用状态模式修改:
借助于 JavaScript 的委托机制, 可以像如下实现状态模式:

const obj = {
    'noCollection': {
        press: function() { //把当前状态切换到下一个状态
            console.log('收藏成功')
            this.state = obj.collected;
        }
    },
    'collected': {
        press: function() {
            console.log('取消收藏成功')
            this.state = obj.noCollection;
        }
    },
  }
  
  const Collection = function() {
    this.state = obj.noCollection;
  }
  
  Collection.prototype.init = function() {
        const btn = document.createElement('button')
        btn.innerHTML = '❤️';
        document.body.append(btn)
        const self = this
        btn.addEventListener('click', function() {
            self.currentState.press.call(self) // 通过 call 完成委托
        })
  }
  
  const coll1 = new Collection()
  coll1.init()

好处就是不需要进行多层判断,press函数封闭的,需要增加的时候不需要再去判断修改press函数
新增状态后的使用状态模式和不用状态模式的对比
普通版本新增:

Collection.prototype.buttonWasPressed = function() {
    if (this.state === 'noCollection') {
        this.collection()
    } else if (this.state === 'collecting') {
        this.collected();
    }else if (this.state === 'collected') { // 新增两个状态
        this.cancel();
    }else if (this.state === 'canceling') {
        this.canceled();
    }
};
// 新增两个函数
Collection.prototype.collection = function() {
    this.state = 'collecting';
    console.log('收藏中。。。')
};
Collection.prototype.collected = function() {
    this.state = 'collected';
    console.log('收藏成功')
};
Collection.prototype.cancel = function() {
    this.state = 'canceling';
    console.log('取消收藏中。。。')
};
Collection.prototype.canceled = function() {
    this.state = 'noCollection';
    console.log('取消收藏成功')
};
let coll3 = new Collection();
coll3.init();

使用状态模式的新增:

const obj = {
   'noCollection': {
       press: function() { //把当前状态切换到下一个状态
           console.log('收藏中。。。')
           this.state = obj.collecting;
       }
   },
   'collecting': {
       press: function() { //把当前状态切换到下一个状态
           console.log('收藏成功')
           this.state = obj.collected;
       }
   },
   'collected': {
       press: function() {
           console.log('取消收藏中。。。')
           this.state = obj.canceling;
       }
   },
   'canceling': {
       press: function() {
           console.log('取消收藏成功')
           this.state = obj.noCollection;
       }
   },
 }

普通写法:
~buttonWasPressed 方法是违反开放-封闭原则的,每次新增或者修改 light 的状态,都需要改动 buttonWasPressed 方法中的代码,这使得 buttonWasPressed 成为了一个非常不稳定的方法
~所有跟状态有关的行为,都被封装在 buttonWasPressed 方法里;如果以后增加状态和其他行为时,我们将无法预计这个方法将膨胀到什么地步,buttonWasPressed 将会很庞大且难以阅读和维护。
状态模式:
~解决了普通写法的问题
~使用面向对象的方式的话会写很多类,占用很大篇幅
~由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。

3、装饰器模式: 简单说就是在不改变自身的情况下 对原有的类进行功能增加和扩展
为对象添加新功能
不改变其原有的结构和功能

class Circle {
    draw() {
        console.log("画一个圆形");
    }
}

class Decorator {
    constructor(circle){
        this.circle = circle;
    }
    draw() {
        this.circle.draw();
        console.log("设置红色边框")
    }
}
// 测试代码
let circle = new Circle();
circle.draw()

let dec = new Decorator(circle);
dec.draw();

目前 TS 和 ES7 已经支持装饰器的使用,可以装饰类、方法,不能装饰函数
下面是新语法中方法装饰器的使用。
方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
target——装饰类的时候是类的构造函数,装饰类的方法时候是类的原型对象。(装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型)
name——成员的名字。
descriptor——成员的属性描述符。

// 定义border方法装饰器
let border = (target, name, descriptor) => {
    const oldValue = descriptor.value
    // 装饰器中替代原先的draw方法
    descriptor.value = function () {
        oldValue.apply(null, arguments)
        //扩展
        console.log(`${name}功能被增强`)
        console.log("我能设置红色边框")
    }
}

class Circle {
    @border
    draw(s: string) {
      // 如果没有装饰器 直接执行这里
        console.log(s);
    }
}
let circle = new Circle();
circle.draw('i can draw circle'); 
输出:
i can draw circle
draw功能被增强
我能设置红色边框

4、观察者模式和发布订阅着模式
概念:
观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。
在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

 class Subject {
  constructor () {
    this.state = 0;
    this.observers = [];
  }
  getState () {
    return this.state;
  }
  setState (state) {
    this.state = state;
    this.notify();
  }
  notify () {
    this.observers.forEach(observer => {
      observer.update();
    })
  }
  attach (observer) {
    this.observers.push(observer);
  }
}


class Observer {
  constructor (name, subject) {
    this.name = name;
    this.subject = subject;
    this.subject.attach(this);
  }
  update () {
    console.log(`${this.name} update, state: ${this.subject.getState()}`);
  }
}

let sub = new Subject();
let observer1 = new Observer('o1', sub);
let observer2 = new Observer('o2', sub);

sub.setState(1);

发布-订阅模式是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

发布-订阅模式实现思路
1、创建一个对象
2、在该对象上创建一个缓存列表(调度中心)
3、on 方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
4、emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
5、off 方法可以根据 event 值取消订阅(取消订阅)
6、once 方法只监听一次,调用完毕后删除缓存函数(订阅一次)```

let eventEmitter = {
    // 缓存列表
    list: {},
    // 订阅
    on (event, fn) {
        let _this = this;
        // 如果对象中没有对应的 event 值,也就是说明没有订阅过,就给 event 创建个缓存列表
        // 如有对象中有相应的 event 值,把 fn 添加到对应 event 的缓存列表里
        (_this.list[event] || (_this.list[event] = [])).push(fn);
        return _this;
    },
    // 监听一次
    once (event, fn) {
        // 先绑定,调用后删除
        let _this = this;
        function on () {
            _this.off(event, on);
            fn.apply(_this, arguments);
        }
        on.fn = fn;
        _this.on(event, on);
        return _this;
    },
    // 取消订阅
    off (event, fn) {
        let _this = this;
        let fns = _this.list[event];
        // 如果缓存列表中没有相应的 fn,返回false
        if (!fns) return false;
        if (!fn) {
            // 如果没有传 fn 的话,就会将 event 值对应缓存列表中的 fn 都清空
            fns && (fns.length = 0);
        } else {
            // 若有 fn,遍历缓存列表,看看传入的 fn 与哪个函数相同,如果相同就直接从缓存列表中删掉即可
            let cb;
            for (let i = 0, cbLen = fns.length; i < cbLen; i++) {
                cb = fns[i];
                if (cb === fn || cb.fn === fn) {
                    fns.splice(i, 1);
                    break
                }
            }
        }
        return _this;
    },
    // 发布
    emit () {
        let _this = this;
        // 第一个参数是对应的 event 值,直接用数组的 shift 方法取出
        let event = [].shift.call(arguments),
            fns = [..._this.list[event]];
        // 如果缓存列表里没有 fn 就返回 false
        if (!fns || fns.length === 0) {
            return false;
        }
        // 遍历 event 值对应的缓存列表,依次执行 fn
        fns.forEach(fn => {
            fn.apply(_this, arguments);
        });
        return _this;
    }
};

function user1 (content) {
    console.log('用户1订阅了:', content);
}

function user2 (content) {
    console.log('用户2订阅了:', content);
}

function user3 (content) {
    console.log('用户3订阅了:', content);
}

function user4 (content) {
    console.log('用户4订阅了:', content);
}

// 订阅
eventEmitter.on('article1', user1);
eventEmitter.on('article1', user2);
eventEmitter.on('article1', user3);

// 取消user2方法的订阅
eventEmitter.off('article1', user2);

eventEmitter.once('article2', user4)

// 发布
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');
eventEmitter.emit('article2', 'Javascript 观察者模式');

// eventEmitter.on('article1', user3).emit('article1', 'test111');

/*
    用户1订阅了: Javascript 发布-订阅模式
    用户3订阅了: Javascript 发布-订阅模式
    用户1订阅了: Javascript 发布-订阅模式
    用户3订阅了: Javascript 发布-订阅模式
    用户4订阅了: Javascript 观察者模式
*/

区别:
1、在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
2、在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 设计模式是为了更好的代码重用性,可读性,可靠性,可维护性。1、工厂模式:简单工厂模式 工厂模式 抽象工厂模式新...
    小螃蟹_5f4c阅读 189评论 0 0
  • JS设计模式读后感 最近在看《Javascript设计模式》这本书,此书并没有空谈设计模式,而是每种模式都结合了具...
    风之化身呀阅读 422评论 0 1
  • 设计模式,从设计到模式 设计:设计原则(统一指导思想) 模式:通过概念总结出的一些模板,可以效仿的固定式的东西(根...
    jia林阅读 4,046评论 2 9
  • 2018任务繁重,今年打算把重点放在js的深入上,往大前端方向发展.年初有空看完了《JavaScript高级程序设...
    丶丶夏天阅读 782评论 1 2
  • 设计模式是什么 设计模式就是前人总结出来的一套更加容易阅读、维护以及复用的写代码的方式 模式就是一个我们如何解决问...
    行走的蛋白质阅读 143评论 0 0