Vue实现数据双向绑定的设计模式到底叫什么?

面试官:“请问 vue 实现双向绑定的原理是什么?”
我:“是利用 Object.defineproperty 进行数据劫持,实现观察者模式......”
面试官:“是观察者模式么?”
我:“?是啊”
面试官:“那你能和我说一下观察者模式和发布订阅模式一样么?不一样的话区别又在哪?”
我:“???”

前言

不知道各位在面试中/被面试中有没有遇到以上的场景,观察者模式和发布订阅模式到底只是翻译不同,还是真的不是一个东西呢? vue 使用的又到底是哪种呢?让我们一起探讨一下。

为什么要使用观察者/发布订阅模式

假如你是一位托儿所的老师,当孩子们放学来到你这里时,你需要看着他们一个个的写作业,他们来自不同的学校,年纪也不尽相同,这让你的工作非常繁琐,就像:

    const children = [
        {
            name: '张三',
            homework: '三张数学卷子'
        },
        {
            name: '李四',
            homework: '一张数学卷子加两张语文卷子'
        },
        {
            name: '王二麻子',
            homework: '上课捣乱罚抄课本'
        },
        // ......
    ];
    function yourJob() {
        children.map(({homework}) => console.log(homework));
    }
    if(time === '17:00') {
        yourJob();
    }

“这样看上去好像还好嘛”

不过实际场景不可能这么简单,真实的情况很可能是:

    const children = [
        {
            name: '张三',
            homework: '三张数学卷子',
            sport: '打一场篮球'
        },
        {
            name: '李四',
            homework: '一张数学卷子加两张语文卷子',
            music: '学会唱鸡你太美'
        },
        {
            name: '王二麻子',
            homework: '上课捣乱罚抄课本',
            doHomework: false,
            playGame: '手游抽卡',
            extendActivity: '计划明天上课的捣乱计划'
        },
        // ......
    ];

“哦我的天啊,考虑到每个孩子要做的不仅不同,每天都是变化的,就算我今天努力按照情况制定好计划,明天又要推倒重来!
要是这些孩子懂事点就好了,我就不用这么费心了......”

怎么才叫懂事呢?比起挨个盯着孩子们完成任务,如果一声令下,他们都能自己去完成自己该做的,是不是就太好了?
而以上的痴心妄想写出来就像这样:

    const yourJobMap = (function () {
        let children = [];
        return {
            doYourJob() {
                children.forEach(({doOwnJob}) => doOwnJob());
            },
            comeIn(...args) {
                children = children.concat(args);
            },
            getOut(...args) {
                children = children.filter(child => !args.find(curChild => curChild === child));
            }
        };
    })();

    const {doYourJob, comeIn, getOut} = yourJobMap;
    // 孩子们来了
    comeIn(children1);
    // 有的孩子又走了
    getOut(children2);
    // 通知在场的孩子们干活啦
    doYourJob();

“这也太方便了!因为孩子们都懂事,自己知道自己要做什么,这样我只要通知他们开始工作就好,并不需要关心他们具体要做什么!”

正是如此,观察者也好,发布订阅也好,他们都解决了一个问题:对于多个不同对象基于同一个对象的变化而执行某些不同的操作时,如何更好的维护代码,也就是降低耦合——即实现对设计模式六大原则中 limit 原则(最少知道原则,尽量降低类与类之间的耦合)的体现。

什么是观察者模式

上述例子就是最简单的观察者模式,即观察者对象( observer,比如上述的孩子 )可以向某个主题( subject ,比如上述的托儿所)注册、注销等,当有事件发生的时候(到时间了,该做作业了),观察者可以接收到通知去进行自己对应的处理。

在前端实际生产中,最常用的观察者模式的实践应该是 EventListener 了吧:

    // 简单的事件监听对象
    const eventListener = (function () {
        const events = {};
        return {
            // 增加监听事件
            addEventListener(eventName, callback) {
                if(events[eventName]) {
                    events[eventName].push(callback);
                } else {
                    events[eventName] = [callback];
                }
            },
            // 移除监听事件
            removeEventListener(eventName, callback) {
                if(events[eventName]) {
                    events[eventName] = events[eventName].filter(fun => fun !== callback);
                } else {
                    events[eventName] = [];
                }
            },
            // 触发事件
            triggerEvent(eventName) {
                if(event[eventName]) {
                    event[eventName].forEach(callback => callback());
                }
            }
        };
    })();

什么是发布订阅模式

Observer vs Pub-Sub

不得不说,如果仅仅是上述的观察者模式,已经足够应对我们的“托儿所”问题了,如果发布订阅模式和观察者模式不同的话,它和观察者模式的区别在哪呢?又是为了解决什么问题呢?
再用托儿所的例子举例,现在是疫情隔离期间,作为线下的托儿所没有办法只能歇业,老师没法接触到学生了,尽管孩子们懂事,但毕竟还是需要有人监督的,那老师该怎么做呢?
老师想到一个办法,他把所有孩子的家长拉进了一个群,每天定时在群里发消息,告诉家长现在孩子要写作业/要锻炼了,然后再由家长面对面地监督孩子。渐渐的他发现,他并不知道哪些孩子真的收到了他的指令去行动了,孩子们也不知道老师到底通知了些什么,老师和孩子,仿佛从没接触过,只有家长在负责两头传话。
如果说观察者模式中, Subject 和 Observer 是一种松耦合状态,那在发布订阅模式中, Publisher 和 Subscriber 就是解耦的,它们两者之间的联系全部通过家长(?)来实现。

Observer vs Pub-Sub

什么是“家长”

在上述举例中的“家长”角色在生活中无处不在,购物链中的超市,企业中的hr等等,毕竟这个世界总是有“中间商赚差价”的。
这里就要引入另一个设计模式——代理模式了。
这个命名非常贴切,就像明星的经纪人一样,你看到的明星都是经纪人包装过的样子,黑粉的言语也会被经纪人公关处理,尽量不会让明星本人看到。你看似每天在微博上和你的偶像互动,实际上你和你的偶像是完全解耦的 hhh......
在实际生产中,es6已经为前端开发者提供了一套代理模式的 API —— Proxy ,大家可以通过 Proxy API 实际感受下代理模式:

    const Angelababy = {
        name: '杨颖',
        age: '35',
        fansCount: 5000000
    };
    const AgentOfAb = new Proxy(Angelababy, {
        get(star, key) {
            const value = star[key];
            switch(key) {
            case 'age':
                return `${value - 5}岁`;
            default:
                return value;
            }
        },
        set(star, key, value) {
            switch(key) {
            case 'fansCount':
                Reflect.set(target, key, value * 10);
            default:
                Reflect.set(target, key, value);
            }
        }
    });

真正的发布订阅模式

我们已经讨论过托儿所如何从观察者模式发展成发布订阅模式了,为了让“托儿所”与“孩子”们之间实现从松耦合变为解耦,我们带入了“家长”,所以真正的发布订阅模式应该是:

发布订阅模式 = 观察者模式 + 代理模式

所以现在的托儿所已经变成如下这种模式了:

    class Publisher {
        constructor(proxy) {
            this.observer = proxy;
        }
        doYourJob(jobName) {
            this.observer.watchYourChildren(jobName);
        }
    }
    class Watcher {
        constructor(children) {
            this.observer = children;
        }
        addChild(child) {
            this.observer.push(child);
        }
        removeChild(curChild) {
            this.observer = this.observer.filter(child => child !== curChild);
        }
        watchYourChildren(jobName) {
            this.observer.forEach(child => childDoSomething(jobName, child));
        },
        childDoSomething(jobName, child) {
            switch(jobName) {
            case 'doHomework':
            case 'sport':
                child.doHomework();
                return;
            case 'sleep':
                child.rest();
                setTimmeout(() => child.doHomework(), 60000);
                return;
            case 'music':
                child.listen('English Listening');
                return;
            default:
                return;
            }
        }
    }

    const children = [
        // 可怜的孩子们
    ];

    // 狠毒的家长
    const parent = new Watcher(children);

    // 啥也不知道的老师
    const yourJob = new Publisher(parant);

    // 你以为孩子在做
    yourJob.doYourJob('doHomework');
    yourJob.doYourJob('sport');
    yourJob.doYourJob('sleep');

    // 实际上孩子在做
    // homework homework homework

Vue使用的设计模式到底是?

Vue双向绑定原理

上面是 Vue 双向绑定的原理,我们可以清楚地看出,数据和视图并不是耦合的,而是由 Watcher 去处理两边的状态变化, Vue 中使用的正是发布订阅模式。使用这种模式不仅可以让数据的变化可以实时反映在视图上,更让 Vue 有了获取数据变化( watch ),处理数据再展示( computed ),异步变化数据等等仅靠观察者无法做到的事。

小结

其实对于观察者模式和发布订阅模式的关系与区别众说纷纭,这方面的理解也很多元,以上只是我对于自己理解的一番阐述,希望大家和平探讨,尤其是和面试候选人哦。

参考: https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

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

推荐阅读更多精彩内容