Skip to content

发布订阅模式 #47

@Wscats

Description

@Wscats

emit和on

这一点有点像vuexredux里面的某部分概念,也跟$emit$on和node自带的event模块作用很相像,其实可以这样理解,如果单独用对象把数据存起来,数据改变的时候没有人会追踪到,所以这里在每次改变数据前都放入一个或多个回调函数形成队列去监听on方法,这些回调函数在队列中等待,直到触发了某些机制,这些函数才按顺序逐一回来触发emit方法,从而可以在这个时刻监听到新的数据变化并完成逻辑

let weux = {};
// 这次换成一个对象类型的缓存列表
weux.list = {};
weux.on = function (key, fn) {
    // 如果对象中没有对应的key值
    // 也就是说明没有订阅过
    // 那就给key创建个缓存列表
    if (!this.list[key]) {
        this.list[key] = [];
    }
    // 把函数添加到对应key的缓存列表里
    this.list[key].push(fn);
};
weux.emit = function (key, param) {
    // 或者let key = [].shift.call(arguments);
    // 或者let fns = this.list[key];
    // 根据获取改函数数组队列
    let fns = this.list[key];
    // 如果缓存列表里没有函数就返回false
    if (!fns || fns.length === 0) {
        return false;
    }
    // 遍历key值对应的缓存列表
    // 依次执行函数的方法
    fns.forEach(fn => {
        // 传入参数
        fn(param);
        // 或者 fn.apply(this, arguments);
    });
};
// 测试用例
weux.on('test', (param) => {
    console.log('位置:' + param.position);
    console.log('技能:' + param.skill);
});
weux.emit('test', {
    position: '前端',
    skill: ['ps', 'css', 'js']
});

简单的队列

可以没有仓库存放中介值,也可以$emit$on的健值对,只有一个简单的队列,按放入的顺序,然后按顺序重新执行出来

class Subject {
    constructor() {
        this.observers = []
    }
    addObserver(observer) {
        this.observers.push(observer)
    }
    removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    notify() {
        this.observers.forEach(observer => {
            observer.update()
        })
    }
}
let subject = new Subject()
class Observer {
    constructor(vm, key, callback) {
        this.vm = vm // mvvm 对象
        this.key = key // data 的属性
        this.callback = callback //callback
    }
    // 更新值
    update() {
        console.log("update")
        this.callback()
    }
}
subject.addObserver(new Observer(document.body, {
    name: "wscats"
}, () => {
    console.log("回调函数")
}))
console.log(subject)
subject.notify();

在上面的基础上添加健值对,可以改写成下面这样,这样写可以应付大部分发布订阅模式的情况

class Subject {
    constructor() {
        this.observers = []
    }
    addObserver(key, observer) {
        var index = this.observers.indexOf(observer)
        if (!this.observers[key]) {
            // 没有创建新的队列
            this.observers[key] = []
        }
        this.observers[key].push(observer)
    }
    removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    // 这里其实不但可以放key,还可以再增加一个参数传进观察者里面,在触发的时候,使用该函数
    notify(key) {
        this.observers[key].forEach(observer => {
            observer.update()
        })
    }
}
let subject = new Subject()
// 可以添加观察者,并在里面存着数据和方法,并保留着一个update方法给notify时候触发
class Observer {
    constructor(vm, key, callback) {
        this.vm = vm // mvvm 对象
        this.key = key // data 的属性
        this.callback = callback //callback
    }
    // 更新值
    update() {
        console.log("update")
        this.callback()
    }
}
subject.addObserver("test1", new Observer(document.body, {
    name: "wscats"
}, () => {
    console.log("回调函数1")
}))
subject.addObserver("test2", new Observer(document.body, {
    name: "wscats"
}, () => {
    console.log("回调函数2")
}))
subject.notify("test1");
subject.notify("test2");
console.log(subject)

类似vuex和redux

继续往下改造,可以改成类似vuex的模式,在仓库中留着一份共享的数据,然后利用on缓存回调函数事件放入队列里面,然后利用emit在合适的时候释放该队列

let wuex = {
    store: {
        position: '温哥华',
        skill: ['jsx', 'ts']
    },
    list: {},
    on: function (key, fn) {
        // 如果有则继续加队列
        if (!this.list[key]) {
            // 没有创建新的队列
            this.list[key] = [];
        }
        this.list[key].push(fn);
    },
    emit: function (key, param) {
        let fns = this.list[key];
        // 遍历更改仓库的值
        for (p in param) {
            this.store[p] = param[p]
        }
        // 触发数组中的回调函数
        fns.forEach(fn => {
            fn(param);
        });
    }
};
wuex.on('test', (param) => {
    console.log('位置:' + param.position);
    console.log('技能:' + param.skill);
});
wuex.on('test', (param) => {
    console.log('位置:' + param.position);
    console.log('技能:' + param.skill);
});
wuex.emit('test', {
    position: "香港",
    skill: ['ps', 'css', 'js']
});

组件通信

发布订阅模式可以用在,两个组件之间的通信

实现双向数据绑定

配合Object.defineProperty实现类似Vue的双向数据绑定

let store = {
    list: {},
    on: function (key, fn) {
        // 如果有则继续加队列
        if (!this.list[key]) {
            // 没有创建新的队列
            this.list[key] = [];
        }
        this.list[key].push(fn);
    },
    emit: function (key, param) {
        let fns = this.list[key];
        fns.forEach(fn => {
            fn(param);
        });
    }
};


class Vue {
    constructor(options = {}) {
        this.$el = document.querySelector(options.el);
        let data = this.data = options.data;
        Object.keys(data).forEach((key) => {
            this.proxyData(key);

        });
        this.watcherTask = {}; // 需要监听的任务列表
        this.observer(data); // 初始化劫持监听所有数据
        this.compile(this.$el); // 解析dom
    }
    // 把data挂到Vue实例化后的vm对象里面,vm.data.name获取或者改变,则触发get或者set
    proxyData(key) {
        let that = this;
        Object.defineProperty(that, key, {
            configurable: false,
            enumerable: true,
            get() {
                return that.data[key];
            },
            set(newVal) {
                console.log(newVal)
                that.data[key] = newVal;
            }
        });
    }
    observer(data) {
        let that = this
        Object.keys(data).forEach(key => {
            let value = data[key]
            this.watcherTask[key] = []
            Object.defineProperty(data, key, {
                configurable: false,
                enumerable: true,
                get() {
                    return value
                },
                set(newValue) {
                    console.log(key)
                    if (newValue !== value) {
                        value = newValue
                        // 发布
                        // 更改vm的数据的时候触发set,然后会触发compileText的订阅store.on监听事件
                        store.emit(key, {})
                    }
                }
            })
        })
    }
    compile(el) {
        var nodes = el.childNodes;
        for (let i = 0; i < nodes.length; i++) {
            const node = nodes[i];
            if (node.nodeType === 3) {
                var text = node.textContent.trim(); //移除字串前後的空白字元以及行結束字元。
                if (!text) continue;
                this.compileText(node, 'textContent')
            }
        }
    }
    compileText(node, type) {
        var self = this;
        // 匹配出该节点下的{{}}这种写法
        let reg = /\{\{(.*?)\}\}/g,
            txt = node.textContent;
        if (reg.test(txt)) {
            node.textContent = txt.replace(reg, (matched, value) => {
                // matched    {{name}}
                // value      name
                // 订阅
                store.on(value, () => {
                    //node, this, value, type
                    node[type] = self.data[value]
                });
                return this[value]
            })
        }
    }
}

var vm = new Vue({
    el: "#demo",
    data: {
        skill: "ps"
    }
})

ES6的写法

class Subject {
    constructor() {
        this.observers = []
    }
    addObserver(key, observer) {
        var index = this.observers.indexOf(observer)
        if (!this.observers[key]) {
            // 没有创建新的队列
            this.observers[key] = []
        }
        this.observers[key].push(observer)
    }
    removeObserver(observer) {
        var index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    // 这里其实不但可以放key,还可以再增加一个参数传进观察者里面,在触发的时候,使用该函数
    notify(key) {
        this.observers[key].forEach(observer => {
            observer.update()
        })
    }
}
// 可以添加观察者,并在里面存着数据和方法,并保留着一个update方法给notify时候触发
class Observer {
    constructor(node, vm, value, type, callback) {
        this.node = node // node节点
        this.vm = vm // mvvm 对象
        this.value = value // data 的属性
        this.type = type // node类型
        this.callback = callback //callback
    }
    // 更新值
    update() {
        console.log("update")
        this.callback()
    }
}


class Vue {
    constructor(options = {}) {
        this.$el = document.querySelector(options.el);
        let data = this.data = options.data;
        Object.keys(data).forEach((key) => {
            this.proxyData(key);

        });
        this.subject = new Subject(); // 需要监听的任务列表
        this.observer(data); // 初始化劫持监听所有数据
        this.compile(this.$el); // 解析dom
    }
    // 把data挂到Vue实例化后的vm对象里面,vm.data.name获取或者改变,则触发get或者set
    proxyData(key) {
        let that = this;
        Object.defineProperty(that, key, {
            configurable: false,
            enumerable: true,
            get() {
                return that.data[key];
            },
            set(newVal) {
                console.log(newVal)
                that.data[key] = newVal;
            }
        });
    }
    observer(data) {
        let that = this
        Object.keys(data).forEach(key => {
            let value = data[key]
            this.subject.observers[key] = []
            Object.defineProperty(data, key, {
                configurable: false,
                enumerable: true,
                get() {
                    return value
                },
                set(newValue) {
                    console.log(key)
                    if (newValue !== value) {
                        value = newValue
                        // 发布
                        // 更改vm的数据的时候触发set,然后会触发compileText的订阅store.on监听事件
                        that.subject.notify(key)
                    }
                }
            })
        })
    }
    compile(el) {
        var nodes = el.childNodes;
        for (let i = 0; i < nodes.length; i++) {
            const node = nodes[i];
            if (node.nodeType === 3) {
                var text = node.textContent.trim(); //移除字串前後的空白字元以及行結束字元。
                if (!text) continue;
                this.compileText(node, 'textContent')
            }
        }
    }
    compileText(node, type) {
        var self = this;
        // 匹配出该节点下的{{}}这种写法
        let reg = /\{\{(.*?)\}\}/g,
            txt = node.textContent;
        if (reg.test(txt)) {
            node.textContent = txt.replace(reg, (matched, value) => {
                // matched    {{name}}
                // value      name
                // 订阅
                this.subject.addObserver(value, new Observer(
                    node, this, value, type, () => {
                        node[type] = self.data[value]
                    }))
                return this[value]
            })
        }
    }
}

var vm = new Vue({
    el: "#demo",
    data: {
        skill: "ps"
    },
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions