Разные языки - один шаблон
Недавно я обратил внимание на знание шаблонов проектирования, и когда я выполнял некоторые требования к управлению на работе, мне довелось напрямую использовать модель публикации-подписки для ее достижения, что сделало меня более заинтересованным в модели проектирования публикации-подписки. поэтому я воспользовался этой возможностью, чтобы рассказать всем об этом хорошем материале!
На самом деле, в первые дни разработки с помощью jq было много мест, где у нас будет тень публикации и подписки, таких как триггер и методы on.
В текущем vue используются методы emit и on. Кажется, что все они неизменно имеют свои собственные атрибуты публикации и подписки, что делает разработку более эффективной и простой в использовании.
Итак, без лишних слов, давайте взглянем на неприкосновенность модели публикации-подписки.
модель публикации-подписки
Когда дело доходит до модели «публикация-подписка», это на самом деле отношение зависимости между объектами «один ко многим» (а не разновидность варьете «один ко многим»), когда состояние объекта изменяется, все объекты, которые зависят от он получит уведомление о состоянии изменения
Как говорится, количество слов мало, это не значит, что эффект невелик, тогда продолжайте видеть его эффект.
эффект
- Широко используется в асинхронном программировании (вместо передачи функций обратного вызова)
- Напишите код, слабо связанный между объектами
Только эти двое? Правильно, очков не так много, всего достаточно. Все мы знаем известную поговорку о том, что Рим не за один день строился.
Конечно, толстяков за день не едят. Так что если мы хотим реализовать собственную модель публикации-подписки, и использовать ее в работе в дальнейшем, нам тоже нужно немного поработать.Я тороплюсь.Начнем с самого простого.
пользовательское событие
let corp = {}; // 自定义一个公司对象
// 这里放一个列表用来缓存回调函数
corp.list = [];
// 去订阅事件
corp.on = function (fn) {
// 二话不说,直接把fn先存到列表中
this.list.push(fn);
};
// 发布事件
corp.emit = function () {
// 当发布的时候再把列表里存的函数依次执行
this.list.forEach(cb => {
cb.apply(this, arguments);
});
};
// 测试用例
corp.on(function (position, salary) {
console.log('你的职位是:' + position);
console.log('期望薪水:' + salary);
});
corp.on(function(skill, hobby) {
console.log('你的技能有: ' + skill);
console.log('爱好: ' + hobby);
});
corp.emit('前端', 10000);
corp.emit('端茶和倒水', '足球');
/*
你的职位是:前端
期望薪水:10000
你的技能有: 前端
爱好: 10000
你的职位是:端茶和倒水
期望薪水:足球
你的技能有: 端茶和倒水
爱好: 足球
*/
В приведенном выше примере реализован простой режим публикации-подписки через пользовательские события, но, судя по напечатанным результатам, это немного смущает. Почему?
Потому что при нормальных обстоятельствах я надеюсь напечатать соус фиолетовым:
/*
你的职位是:前端
期望薪水:10000
你的技能有: 端茶和倒水
爱好: 足球
*/
Причина, по которой это происходит, заключается в том, что при использовании метода on все функции fn помещаются в список. Однако все, что нужно, — это простое значение ключа, которое можно решить. Давайте перепишем код выше
let corp = {};
// 这次换成一个对象类型的缓存列表
corp.list = {};
corp.on = function(key, fn) {
// 如果对象中没有对应的key值
// 也就是说明没有订阅过
// 那就给key创建个缓存列表
if (!this.list[key]) {
this.list[key] = [];
}
// 把函数添加到对应key的缓存列表里
this.list[key].push(fn);
};
corp.emit = function() {
// 第一个参数是对应的key值
// 直接用数组的shift方法取出
let key = [].shift.call(arguments),
fns = this.list[key];
// 如果缓存列表里没有函数就返回false
if (!fns || fns.length === 0) {
return false;
}
// 遍历key值对应的缓存列表
// 依次执行函数的方法
fns.forEach(fn => {
fn.apply(this, arguments);
});
};
// 测试用例
corp.on('join', (position, salary) => {
console.log('你的职位是:' + position);
console.log('期望薪水:' + salary);
});
corp.on('other', (skill, hobby) => {
console.log('你的技能有: ' + skill);
console.log('爱好: ' + hobby);
});
corp.emit('join', '前端', 10000);
corp.emit('join', '后端', 10000);
corp.emit('other', '端茶和倒水', '足球');
/*
你的职位是:前端
期望薪水:10000
你的职位是:后端
期望薪水:10000
你的技能有: 端茶和倒水
爱好: 足球
*/
придумать общий
Теперь сделаем общую реализацию модели публикации-подписки, которая похожа на предыдущую, но на этот раз название будет более торжественным, просто назовем ее событием, смотрим код
let event = {
list: {},
on(key, fn) {
if (!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
},
emit() {
let key = [].shift.call(arguments),
fns = this.list[key];
if (!fns || fns.length === 0) {
return false;
}
fns.forEach(fn => {
fn.apply(this, arguments);
});
},
remove(key, fn) {
// 这回我们加入了取消订阅的方法
let fns = this.list[key];
// 如果缓存列表中没有函数,返回false
if (!fns) return false;
// 如果没有传对应函数的话
// 就会将key值对应缓存列表中的函数都清空掉
if (!fn) {
fns && (fns.length = 0);
} else {
// 遍历缓存列表,看看传入的fn与哪个函数相同
// 如果相同就直接从缓存列表中删掉即可
fns.forEach((cb, i) => {
if (cb === fn) {
fns.splice(i, 1);
}
});
}
}
};
function cat() {
console.log('一起喵喵喵');
}
function dog() {
console.log('一起旺旺旺');
}
event.on('pet', data => {
console.log('接收数据');
console.log(data);
});
event.on('pet', cat);
event.on('pet', dog);
// 取消dog方法的订阅
event.remove('pet', dog);
// 发布
event.emit('pet', ['二哈', '波斯猫']);
/*
接收数据
[ '二哈', '波斯猫' ]
一起喵喵喵
*/
Таким образом реализуется модель публикации и подписки, которую можно использовать.На самом деле, это относительно просто сказать.Давайте думать об этом снова и снова.
идеи:
- Создать объект (список кеша)
- Метод on используется для добавления функции обратного вызова fn в список кеша.
- Метод emit принимает первый из аргументов в качестве ключа и выполняет функцию в соответствующем списке кеша в соответствии со значением ключа.
- Метод удаления может отписаться на основе значения ключа
приложение на работе
промежуточный
Позвольте мне сначала показать вам один.В этом проекте страницы перекодирования новостей я отвечаю за написание содержимого рекомендуемого потока ниже (то есть те, кому это нравится, также читают его там). Как показано ниже
Рекламная часть, обведенная кружком, здесь не входит в мои обязанности, и для ее достижения нужна еще одна большая корова, которая отвечает за стыковку рекламного бизнеса. Так как же он хочет вставить рекламу в мою ленту рекомендаций?Ведь я не могу отдать ему свой код и пусть он его разрабатывает заново.Это не достаточно сложно.Я должен ознакомиться с кодом и начать писать логику вставки рекламы.Это очень расстраивает,и времени не должно быть тратится вот так.
Так что используется модель публикации-подписки, и мне не нужно обращать внимание на логику вставки рекламы здесь. Я все еще я, это фейерверк разных цветов, хахаха, далеко
После общения мне просто нужно передать номер страницы, на которую просматривает пользователь. Так что мне просто нужно написать предложение в коде, который я разработал, и использовать событие, реализованное выше, чтобы выразить его.
// 省略....
render() {
// 我只在渲染的时候
// 把约定好的key和他需要的page页码传过去就可以了
event.emit('soAd', page);
}
// 省略...
РБИ
Еще один взгляд, друг. Цель Dot в основном состоит в том, чтобы записывать поведение пользователей, поэтому, когда будет разработана новая версия мобильного поиска по карте, будет также добавлен код Dot, а затем будут подсчитаны данные pv, uv, ctr и другие статистические данные, затем прямо смотреть на картинку и говорить
Как показано на рисунке, когда пользователь проводит пальцем вверх, будет отображаться следующий контент (об этом я и хочу рассказать)Часть «думаю, вам нравится» в кружке здесь также отображается после получения данных путем отправки запроса. Что я собираюсь сделать, однако, так это придать фразе «угадай, что вам нравится» выразительный RBI. Ключевой вопрос заключается в сроках, когда я должен увеличить RBI?Это очень просто, я могу добавить эту точку, когда запрос будет завершен и отрендерен на странице. Давайте посмотрим на простой код (это не код проекта, просто каштан)
// main.js
render() {
// 省略...
// 当渲染到页面的时候,发送这个打点事件
// 然后另外的一个专门负责打点的模块里去监听
event.emit('relatedDD', 'related');
}
// log.js
event.on('relatedDD', type => {
console.log(type); // 'related'
// monitor是个打点工具,由超级大牛开发
monitor.log({
type
}, 'disp');
});
Приведенный выше код — это просто простой пример, если вы еще чего-то не знаете о RBI, то я кратко опишу это.
Наиболее часто используемое управление — отправить запрос на картинку, и данные будут подсчитываться по количеству запросов, а статистика будет различаться по разным параметрам посередине.
Например, если вы хотите узнать, сколько пользователей просмотрели контент «Угадай, что вам нравится», при фильтрации данных вы напрямую запишете тип как связанный.
Здесь следует поднимать так называемые каштаны, если вы поднимете слишком много, у вас будут болеть руки. Ха-хаНо это не конец, потому что я обнаружил, что одним из основных модулей (событий) в узле является именно упомянутая выше модель публикации-подписки, что не является совпадением или упражнением. Так сердце весны трепещет и танцует. следитьapi, давайте сделаем его вместе и улучшим свои навыки, Let's Go!
По правде говоря, это основной модуль узла.
Друзья, которые использовали узел, должны быть знакомы с этим модулем.Можно сказать, что этот модуль действительно важен в узле.После его использования я обнаружил, что это полностью написанная с большой буквы модель публикации-подписки.
Это вездесущее существование, поэтому бессмыслицы больше нет, а реализация остается прежней. Давайте сначала посмотрим, как его использовать.прецедентВзгляни
прецедент
/ {'失恋', [findboy, drink]}
// 监听的目的 就是为了构造这样一个对象 一对多的关系 on
// 发布的时候 会让数组的函数依次执行 emit
// [findboy, drink]
// let EventEmitter = require('events');
// 这里用接下来我们写的
let EventEmitter = require('./events');
let util = require('util');
function Girl() {
}
// Girl继承EventEmitter上的方法
util.inherits(Girl, EventEmitter); // 相当于Girl.prototype.__proto__ = EventEmitter.prototype
let girl = new Girl();
let drink = function (data) {
console.log(data);
console.log('喝酒');
};
let findboy = function () {
console.log('交友');
};
girl.on('newListener', function (eventName) {
// console.log('名称: ' + eventName);
});
girl.on('结婚', function() {});
girl.setMaxListeners(3);
console.log(girl.getMaxListeners());
girl.once('失恋', drink); // {'失恋': [drink]}
girl.once('失恋', drink); // {'失恋': [drink]}
girl.prependListener('失恋', function () {
console.log('before');
});
girl.once('失恋', drink); // {'失恋': [drink]}
girl.emit('失恋', '1');
Приведенный выше код показывает, как использовать основной модуль событий, не скупитесь, просто начните печатать
Реализовать EventEmitter
Теперь наступает самый важный и волнующий момент, приступим к реализации EventEmitter
function EventEmitter() {
// 用Object.create(null)代替空对象{}
// 好处是无杂质,不继承原型链的东东
this._events = Object.create(null);
}
// 默认最多的绑定次数
EventEmitter.defaultMaxListeners = 10;
// 同on方法
EventEmitter.prototype.addListener = EventEmitter.prototype.on;
// 返回监听的事件名
EventEmitter.prototype.eventNames = function () {
return Object.keys(this._events);
};
// 设置最大监听数
EventEmitter.prototype.setMaxListeners = function (n) {
this._count = n;
};
// 返回监听数
EventEmitter.prototype.getMaxListeners = function () {
return this._count ? this._count : this.defaultMaxListeners;
};
// 监听
EventEmitter.prototype.on = function (type, cb, flag) {
// 默认值,如果没有_events的话,就给它创建一个
if (!this._events) {
this._events = Object.create(null);
}
// 不是newListener 就应该让newListener执行以下
if (type !== 'newListener') {
this._events['newListener'] && this._events['newListener'].forEach(listener => {
listener(type);
});
}
if (this._events[type]) {
// 根据传入的flag来决定是向前还是向后添加
if (flag) {
this._events[type].unshift(cb);
} else {
this._events[type].push(cb);
}
} else {
this._events[type] = [cb];
}
// 监听的事件不能超过了设置的最大监听数
if (this._events[type].length === this.getMaxListeners()) {
console.warn('警告-警告-警告');
}
};
// 向前添加
EventEmitter.prototype.prependListener = function (type, cb) {
this.on(type, cb, true);
};
EventEmitter.prototype.prependOnceListener = function (type, cb) {
this.once(type, cb, true);
};
// 监听一次
EventEmitter.prototype.once = function (type, cb, flag) {
// 先绑定,调用后删除
function wrap() {
cb(...arguments);
this.removeListener(type, wrap);
}
// 自定义属性
wrap.listen = cb;
this.on(type, wrap, flag);
};
// 删除监听类型
EventEmitter.prototype.removeListener = function (type, cb) {
if (this._events[type]) {
this._events[type] = this._events[type].filter(listener => {
return cb !== listener && cb !== listener.listen;
});
}
};
EventEmitter.prototype.removeAllListener = function () {
this._events = Object.create(null);
};
// 返回所有的监听类型
EventEmitter.prototype.listeners = function (type) {
return this._events[type];
};
// 发布
EventEmitter.prototype.emit = function (type, ...args) {
if (this._events[type]) {
this._events[type].forEach(listener => {
listener.call(this, ...args);
});
}
};
module.exports = EventEmitter;
Выше мы усердно работали, чтобы реализовать события основного модуля узла и доработали функцию EventEmitter, Это приятно, приятно, и поставьте себе лайк!
Доработка завершена, но всем еще предстоит писать и всматриваться в нее снова и снова. Ведь ни у кого из них нет возможности их запомнить. Им еще предстоит потрудиться. Давай, давай.
Ха-ха, тогда в конце напишем небольшое резюме
Суммировать
преимущество:
- Развязка между объектами
- В асинхронном программировании можно написать более слабосвязанный код.
недостаток:
- Само создание подписчика занимает определенное количество времени и памяти
- Когда несколько издателей и подписчиков объединены вместе, программу трудно отслеживать и поддерживать.
Более сильная, чем модель «публикация-подписка», это также причина, по которой крепкое вино — это хорошо, не жадничайте. Если его использовать чрезмерно, возникнут проблемы, упомянутые выше. Тем не менее, разумное развитие и разумное использование, это не большая проблема.
Хорошо используйте этот наиболее распространенный шаблон и привнесите много сублимации в свое программирование! Сегодня напишу сюда, спасибо за просмотр. Ха-ха, увидимся в следующий раз случайно! Увидимся