《JavaScript 形式》知识点小手本(下)

引见

近来最先给本身每周订个进修使命,进修结果反应为一篇文章的输出,做好进修纪录。
这一周(02.25-03.03)我定的目的是《JavaScript 情势》的第七章进修一遍,进修结果的反应就是本篇文章啦。
由于内容着实太长,我将本文分为两部分:

本文内容中重要参考《JavaScript 情势》,个中也有些案例是来自网上材料,有备注出处啦,如构成不轻易,请联络我编削。

过两天我会把这篇文章收录到我整顿的知识库 【Cute-JavaScript】 中,并已同步到 【github】上面。

六.表面情势(Facade Pattern)

1.观点引见

表面情势(Facade Pattern)是一种简朴又罕见的情势,它为一些庞杂的子体系接口供应一个更高等的一致接口,轻易对这些子体系的接口接见。

它不仅简化类中的接口,还对接口和挪用者举行解耦,表面情势也常被认为是开发者必备,它可以将一些庞杂操纵封装起来,并建立一个简朴接口用于挪用。

2.优瑕玷和运用场景

2.1长处

  • 轻量级,削减体系相互依靠。
  • 进步灵活性。
  • 进步了安全性。

2.2瑕玷

  • 不符合开闭准绳,假如要改东西很贫苦,继续重写都不适宜。

2.3运用场景

  • 为庞杂的模块或子体系供应外界接见的模块。
  • 子体系相对自力。
  • 防备低水平职员带来的风险。
  • 项目重构。

3.基础案例

常常我们在处置惩罚一些特殊情况的时刻,须要一同挪用好几个要领,我们运用表面情势,就可以将多个要领包装成一个要领,那里须要运用直接挪用这个包装好的要领就可以。
比方我们常常处置惩罚浏览器事宜,须要同时挪用stopPropagation()preventDefault(),因而我们就可以新建一个表面要领,完成这两个要领同时挪用:

let myEvent = {
    // ...
    stop: e => {
        e.stopPropagation();
        e.preventDefault();
    }
};

然后我们也可以运用表面情势,来做IE事宜的兼容性:

let myEvent = {
    // ...
    stop: e => {
        // 其他 
        if(typeof e.preventDefault === 'function'){
            e.preventDefault();
        }
        if(typeof e.stopPropagation === 'function'){
            e.stopPropagation();
        }
        // IE
        if(typeof e.returnValue === 'boolean'){
            e.returnValue = false;
        }
        if(typeof e.cancelBubble === 'boolean'){
            e.cancelBubble = true;
        }
    }
};

七、代办情势(Proxy Pattern)

1.观点引见

代办情势(Proxy Pattern) 为其他对象供应一种代办,来掌握这个对象的接见,代办是在客户端和实在对象之间的介质。

《《JavaScript 形式》知识点小手本(下)》

简朴的明白:如我们须要请明星来做广告,我们会先经由过程联络Ta的经纪人,谈好前提才会给明星签合同。

2.优瑕玷和运用场景

2.1长处

  • 职责单一且清晰。
  • 庇护实在对象。
  • 开闭准绳,高拓展性。

2.2瑕玷

  • 由于在客户端和实在对象间增添代办对象,致使要求处置惩罚速率变慢。
  • 完成代办情势须要分外事变,有些代办情势完成起来非常庞杂。

2.3运用场景

  • 须要隐蔽或庇护某个类,则为这个类增添代办。
  • 须要给差别接见者供应差别权限,则在代办类上做推断。
  • 须要为某个类增添功用,如增添日记缓存等,我们可以在代办的类做增添,而不论去改本来封装好的类。

3.基础案例

这里我们以吃午餐题目来进修代办情势。通常情况下,我们会有两种体式格局处理午餐题目:“去餐厅吃”和“叫外卖”。
去餐厅吃的话,我们就是本身过去用饭了呗,假如是叫外卖,我们就会经由过程外卖小哥来拿到午餐才吃起来。

  • 去餐厅吃(没有运用代办情势)
// 定义午餐类 参数 菜名
let Lunch = function(greens){
    this.greens = greens;
}
Lunch.prototype.getGreens = function(){
    return this.greens;
}
// 定义我这个对象
let leo = {
    buy: function(greens){
        console.log(`午餐吃${greens.getGreens()}`);
    }
}
// 去餐厅吃
leo.buy(new Lunch('青椒炒肉')); // 午餐吃青椒炒肉
  • 叫外卖(有运用代办情势)
// 定义午餐类 参数 菜名
let Lunch = function(greens){
    this.greens = greens;
}
Lunch.prototype.getGreens = function(){
    return this.greens;
}
// 定义外卖小哥这个对象
let brother = {
    buy: function(lunch){
        leo.buy(lunch.getGreens());
    }
}
// 定义我这个对象
let leo = {
    buy: function(greens){
        console.log(`午餐吃${greens}`);
    }
}
// 叫外卖
brother.buy(new Lunch('青椒炒肉')); // 午餐吃青椒炒肉

而且外卖小哥还会帮我们做一些其他事,比方帮我们带瓶可乐,我们革新brotherleo这2个对象,再看看结果:

let brother = {
    buy: function(lunch){
        if(leo.needCola) leo.buyCola();
        leo.buy(lunch.getGreens());
    }
}

let leo = {
    needCola: true,
    buy: function(greens){
        console.log(`午餐吃${greens}`);
    },
    buyCola: function(){
        console.log(`随手买瓶可乐!`);
    }
}
brother.buy(new Lunch('青椒炒肉'));
// 随手买瓶可乐!
// 午餐吃青椒炒肉

4.庇护代办

照样借用 3.基础案例 的叫外卖的例子,我们现在要完成庇护代办,而我们须要外卖小哥为了我们的身体健康,凌驾晚上9点,就不帮我们买可乐。
照样革新上面买可乐的brother对象代码:

let brother = {
    buy: function(lunch){
        let nowDate = new Date();
        if(nowDate.getHours() >= 21){
            console.log('亲,这么晚不要喝可乐哟!');
        }else{
            if(leo.needCola) leo.buyCola();
            leo.buy(lunch.getGreens());
        }
    }
}
brother.buy(new Lunch('青椒炒肉'));
// 随手买瓶可乐!
// 午餐吃青椒炒肉

5.假造代办

假造代办能把一些开支大的对象,延晚到真正须要的时刻才去建立和实行。
我们这里举个图片懒加载的例子:
这个案例参考自JS设想情势-代办情势.

// 图片加载
let ele = (function(){
    let node = document.createElement('img');
    document.body.appendChild(node);
    return{
        setSrc : function(src){
            node.src = src;
        }
    }
})()

// 代办对象
let proxy = (function(){
    let img = new Image();
    img.onload = function(){
        ele.setSrc(this.src);
    }
    return {
        setSrc : function(src){
            img.src = src;
            ele.setSrc('loading.png');
        }
    }
})()

proxy.setSrc('example.png');

6.缓存代办

缓存代办是将一些开支大的运算结果供应暂存功用,当下次盘算时,参数和之前一向,则将缓存的结果返回:
这个案例参考自JS设想情势-代办情势.

//盘算乘积
let mult = function(){
    let result = 1;
    for(let i = 0; i<arguments.length; i++){
        result *= arguments[i];
    }
    return result;
}

// 缓存代办
let proxy = (function(){
    let cache = {};
    return function(){
        let args = Array.prototype.join.call(arguments, '',);
        if(args in cache){
            return cache[args];
        }
        return cache[args] = mult.apply(this,arguments);
    }
})();

八、中介者情势(Mediator Pattern)

1.观点引见

中介者情势(Mediator Pattern) 是用来下降多个对象和类之间的通讯庞杂性,增进构成松耦合,进步可保护性。

《《JavaScript 形式》知识点小手本(下)》

在这类情势下,自力的对象之间不能直接通讯,而是须要中间对象(mediator对象),当个中一个对象(colleague对象)状况转变后,它会关照mediator对象,
然后mediator对象会把该变更关照到恣意须要晓得此变化的colleague对象。

2.优瑕玷和运用场景

2.1长处

  • 下降类的庞杂度,从一对多转成一对一。
  • 为各个类之间解耦。
  • 进步代码可保护性。

2.2瑕玷

中介者会愈来愈巨大,变得难以保护。

2.3运用场景

  • 体系中对象之间存在比较庞杂的援用关联,而且难以复用该对象。
  • 须要天生起码的子类,完成一个中间类封装多个类中的行动的时刻。

别的: 不要在职责杂沓的时刻运用。

3.基础案例

这里我们完成一个简朴的案例,一场测试完毕后,宣布结果,示知解答出题目的人应战胜利,不然应战失利:
这个案例来自JavaScript 中罕见设想情势整顿

const player = function(name) {
    this.name = name;
    playerMiddle.add(name);
}

player.prototype.win = function() {
    playerMiddle.win(this.name);
}

player.prototype.lose = function() {
    playerMiddle.lose(this.name);
}

const playerMiddle = (function() { // 将就用下这个 demo,这个函数当做中介者
    const players = [];
    const winArr =  [];
    const loseArr = [];
    return {
        add: function(name) {
            players.push(name)
        },
        win: function(name) {
            winArr.push(name)
            if (winArr.length + loseArr.length === players.length) {
                this.show()
            }
        },
        lose: function(name) {
            loseArr.push(name)
            if (winArr.length + loseArr.length === players.length) {
                this.show()
            }
        },
        show: function() {
            for (let winner of winArr) {
                console.log(winner + '应战胜利;')
            }
            for (let loser of loseArr) {
                console.log(loser + '应战失利;')
            }
        },
    }
}())

const a = new player('A 选手');
const b = new player('B 选手');
const c = new player('C 选手');

a.win()
b.win()
c.lose()

// A 选手应战胜利;
// B 选手应战胜利;
// C 选手应战失利;

4.书籍案例

这个案例来自 《JavaScript 情势》第七章 中介者情势 的案例。
这里我们有这么一个游戏例子,规则是两个玩家在划定时候内,比比谁点击按钮次数更多,玩家1按按键2,玩家2按按键0,而且计分板及时更新。

《《JavaScript 形式》知识点小手本(下)》

这里的中介者须要晓得一切其他对象信息,而且它须要晓得哪一个玩家点击了一次,随后关照玩家。玩家举行游戏的时刻,还要关照中介者它做的事变,中介者更新分数并显现比分。

这里的player对象都是经由过程Player()组织函数天生,而且都有pointsname属性,每次挪用play()都邑增添1分并关照中介者。

function Player(name){
    this.points = 0;
    this.name   = name;
}
Player.prototype.play = function(){
    this.points += 1;
    mediator.played();
}

计分板有个update()要领,当玩家回合完毕就会挪用,它不晓得任何玩家的信息也没有保留分值,只是完成展现当前分数。

let scoreboard = {
    // 待更新HTML元素
    ele: document.getElementById('result');
    // 更新比分
    update: function (score){
        let msg = '';
        for(let k in score){
            if(score.hasOwnProperty(k)){
                msg = `<p>${k} : ${score[k]}<\/p>`
            }
        }
        this.ele.innerHTML = msg;
    }
}

接下来建立mediator对象:

let mediator = {
    players: {},       // 一切玩家
    setup: function(){ // 初始化
        let players = this.players;
        players.homw = new Player('Home');
        players.guest = new Player('Guest');
    },
    // 当有人玩时 更新分数
    played: function(){
        let players = this.players 
        let score = {
            Home: players.home.points,
            Guest: players.guest.points,
        }
        scoreboard.update(score);
    }
    // 处置惩罚用户交互
    keypress: function(e){
        e = e || window.event;  // 兼容IE
        if(e.which === 49){     // 按键1
            mediator.players.home.play();
        }
        if(e.which === 48){     // 按键0
            mediator.players.guest.play();
        }
    }
}

末了就是须要运转和卸载游戏了:

mediator.setup();
window.onkeypress = mediator.keypress;
// 游戏30秒后完毕
setTimeout(function(){
    window.onkeypress = null;
    alert('游戏完毕');
}, 30000)

九、视察者情势(Observer Patterns)

1.观点引见

视察者情势(Observer Patterns) 也称定阅/宣布(subscriber/publisher)情势,这类情势下,一个对象定阅定一个对象的特定运动,并在状况转变后取得关照。
这里的定阅者称为视察者,而被视察者称为宣布者,当一个事宜发作,宣布者会宣布关照一切定阅者,并常常以事宜对象情势通报音讯。

一切浏览器事宜(鼠标悬停,按键等事宜)都是该情势的例子。

我们还可以这么明白:这就跟我们定阅微信民众号一样,当民众号(宣布者)群发一条图文音讯给一切粉丝(视察者),然后一切粉丝都邑接受到这篇图文音讯(事宜),这篇图文音讯的内容是宣布者自定义的(自定义事宜),粉丝阅读后可以就会买买买(实行事宜)。

2.视察者情势 VS 宣布定阅情势

2.1视察者情势

一种一对多的依靠关联,多个视察者对象同时监听一个主题对象。这个主题对象在状况上发作变化时,会关照一切视察者对象,使它们可以自动更新本身。

《《JavaScript 形式》知识点小手本(下)》

2.2宣布定阅情势

宣布定阅情势理念和视察者情势雷同,然则处置惩罚体式格局上差别。
在宣布定阅情势中,宣布者和定阅者不晓得对方的存在,他们经由过程调理中间串连起来。
定阅者把本身想定阅的事宜注册到调理中间,当该事宜触发时刻,宣布者宣布该事宜到调理中间(并照顾上下文),由调理中间一致调理定阅者注册到调理中间的处置惩罚代码。

《《JavaScript 形式》知识点小手本(下)》

2.3二者异同点

  • 视察者情势中,视察者晓得宣布者是谁,并宣布者坚持对视察者举行纪录。而宣布定阅情势中,宣布者和定阅者不晓得对方的存在。它们只是经由过程调理中间举行通讯。
  • 宣布定阅情势中,组件是松懈耦合的,恰好和视察者情势相反。
  • 视察者情势大多是同步,如当事宜触发,宣布者就会去挪用视察者的要领。而宣布定阅情势大多是异步的(运用音讯行列)。
  • 视察者情势须要在单个运用顺序地点空间中完成,而宣布-定阅更像交织运用情势。

只管存在差别,但也有人说宣布-定阅情势是视察者情势的变异,由于它们观点上类似。

2.4二者优瑕玷

雷同长处:

  • 都可以一对多
  • 顺序便于扩大

差别长处:

  • 视察者情势:单向解耦,宣布者不须要清晰定阅者何时何地定阅,只须要保护定阅行列,发送音讯即可
  • 宣布定阅情势:双向解耦,宣布者和定阅者都不必清晰对方,悉数由定阅中间做处置惩罚

瑕玷:

  • 假如一个被视察者和多个视察者的话,会增添保护的难度,而且会斲丧许多时候。
  • 假如视察者和宣布者之间有轮回依靠,可以会致使轮回挪用引发体系奔溃。
  • 视察者没法得知视察的目的对象是怎样发作变化,只能晓得目的对象发作了变化。
  • 宣布定阅情势,中间使命太重,一旦崩溃,一切定阅者都邑受到影响。

4.基础案例

我们寻常一向运用的给DOM节点绑定事宜,也是视察者情势的案例:

document.body.addEventListener('click', function(){
    alert('ok');
},false);
document.body.click();

这里我们定阅了document.bodyclick事宜,当body点击它就向定阅者发送音讯,就会弹框ok。我们也可以增添许多的定阅。

4.视察者情势 案例

本案例来自 javascript 视察者情势和宣布定阅情势

class Dom {
    constructor() {
        // 定阅事宜的视察者
        this.events = {}
    }

    /**
    * 增添事宜的视察者
    * @param {String} event  定阅的事宜
    * @param {Function} callback 回调函数(视察者)
    */
    addEventListener(event, callback) {
        if (!this.events[event]) {
            this.events[event] = []
        }
        this.events[event].push(callback)
    }

    removeEventListener(event, callback) {
        if (!this.events[event]) {
            return
        }
        const callbackList = this.events[event]
        const index = callbackList.indexOf(callback)
            if (index > -1) {
            callbackList.splice(index, 1)
        }
    }

    /**
    * 触发事宜
    * @param {String} event
    */
    fireEvent(event) {
        if (!this.events[event]) {
            return
        }
        this.events[event].forEach(callback => {
            callback()
        })
    }
}

const handler = () => {
    console.log('fire click')
}
const dom = new Dom()

dom.addEventListener('click', handler)
dom.addEventListener('move', function() {
    console.log('fire click2')
})
dom.fireEvent('click')

5.宣布定阅情势 案例

本案例来自 javascript 视察者情势和宣布定阅情势

class EventChannel {
    constructor() {
        // 主题
        this.subjects = {}
    }

    hasSubject(subject) {
        return this.subjects[subject] ? true : false
    }

    /**
    * 定阅的主题
    * @param {String} subject 主题
    * @param {Function} callback 定阅者
    */
    on(subject, callback) {
        if (!this.hasSubject(subject)) {
            this.subjects[subject] = []
        }
        this.subjects[subject].push(callback)
    }

    /**
    * 作废定阅
    */
    off(subject, callback) {
        if (!this.hasSubject(subject)) {
            return
        }
        const callbackList = this.subjects[subject]
        const index = callbackList.indexOf(callback)
        if (index > -1) {
            callbackList.splice(index, 1)
        }
    }

    /**
    * 宣布主题
    * @param {String} subject 主题
    * @param {Argument} data 参数
    */
    emit(subject, ...data) {
        if (!this.hasSubject(subject)) {
            return
        }
        this.subjects[subject].forEach(callback => {
            callback(...data)
        })
    }
}

const channel = new EventChannel()

channel.on('update', function(data) {
    console.log(`update value: ${data}`)
})
channel.emit('update', 123)

参考材料

  1. 《JavaScript Patterns》
Author王安然
E-mailpingan8787@qq.com
博 客www.pingan8787.com
微 信pingan8787
逐日文章引荐https://github.com/pingan8787…
JS小册js.pingan8787.com
微信民众号前端自习课

《《JavaScript 形式》知识点小手本(下)》

    原文作者:pingan8787
    原文地址: https://segmentfault.com/a/1190000018373975
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞