хм~~~
Откройте дверь, на этот раз я не продаю Guan Zi, сегодня мы поговорим о режиме дизайна Javassript.Шаблон наблюдателя, прежде всего давайте узнаем, что такое паттерн Observer?
Что такое шаблон наблюдателя?
Шаблон наблюдателя (наблюдатель)
обычно называютшаблон публикации-подпискиилимеханизм сообщений, который определяет зависимость между объектами «один ко многим». Пока состояние объекта изменяется, все объекты, которые зависят от него, уведомляются и автоматически обновляются, что решает функцию между объектом-субъектом и наблюдателем. то есть проблема уведомления других объектов об изменении состояния объекта.
Просто глядя на определение, для фронтенд-друзей это понятие может быть относительно расплывчатым, и я все еще немного понимаю режим наблюдателя, хорошо, тогда я рассмотрю более подходящий пример из жизни, я думаю, вы поймете. сразу понял~
Модель наблюдателя в жизни
Каждый раз, когда Xiaomi выпускает новый мобильный телефон, он продается очень быстро.Мне понравился Xiaomi Mi 3, и я хотел пойти в Xiaomi Home, чтобы купить его, но когда я пришел в магазин, продавец сказал мне, что этот телефон очень популярен. .Есть в наличии, то я не могу приходить и спрашивать каждый день, это очень долго, поэтому я оставляю свой номер мобильного телефона продавщице, если у них есть запас в магазине, пусть она мне позвонит и сообщит , так что вам не нужно беспокоиться о том, что вы не знаете, когда он будет в наличии, и вам не нужно каждый день бегать спрашивать. Если вы успешно купили мобильный телефон, то продавщица не нужно уведомить вас~
Разве это не намного яснее~ Таких случаев еще много, поэтому я не буду их повторять.
Использование шаблона наблюдателя
По правде говоря, я могу гарантировать, что все, кто придет посмотреть, использовали режим наблюдателя~
что, не веришь?
Тогда взгляните на следующий код~
document.querySelector('#btn').addEventListener('click',function () {
alert('You click this btn');
},false)
Что, разве это не выглядит знакомым!
Да, мы обычноDOMПривязка событий является очень типичнымшаблон публикации-подписки, здесь нам нужно отслеживать действие пользователя, нажимающего кнопку, но мы не можем знать, когда пользователь нажимает, поэтому мы подписываемся на кнопку наclickсобытие, пока кнопка нажата, кнопка опубликует сообщение для подписчика, и мы можем выполнить соответствующую операцию.
Помимо нашего обычногоDOMВ дополнение к привязке событий существует множество применений шаблона Observer~
Например, сравните текущие популярныеvueFramework, который использует шаблон наблюдателя во многих местах, например:
Двустороннее обязательство данных
использоватьObject.defineProperty()Захватите данные и настройте прослушивательObserver, используется для мониторинга всех атрибутов, при изменении атрибута нужно сообщить подписчикуWatcherобновить данные и, наконец, дать указание парсеруCompileРазбирает соответствующую инструкцию, а затем выполняет соответствующую функцию обновления, тем самым обновляя представление и реализуя двустороннюю привязку~
Дочерний компонент взаимодействует с родительским компонентом
Vueв котором мы проходимpropsПередача данных родительского компонента завершена, родительские компонентные подсказки и подбаллии нас для связи через пользовательское событие, которое$on,$emitдобиться, по сути, через$emitпубликовать сообщения и подписчикам$onСделайте унифицированную обработку ~
Хорошо, сказав так много, пора нам показать свои руки.Далее, давайте создадим простой наблюдатель сами~
создать наблюдателя
Во-первых, нам нужно создать объект-наблюдатель, который содержит контейнер сообщений и три метода, а именно метод сообщения о подписке.on, метод сообщения отпискиoff, отправить сообщение о подпискеsubscribe.
const Observe = (function () {
//防止消息队列暴露而被篡改,将消息容器设置为私有变量
let __message = {};
return {
//注册消息接口
on : function () {},
//发布消息接口
subscribe : function () {},
//移除消息接口
off : function () {}
}
})();
Ок, наш прототип наблюдателя вышел, осталось улучшить три метода внутри~
метод регистрации сообщения
Функция метода зарегистрированного сообщения состоит в том, чтобы отправить сообщение, зарегистрированное подписчиком, в очередь сообщений, поэтому необходимо передать два параметра: тип сообщения и соответствующую функцию обработки.Если он существует, создайте тип сообщения и поместите сообщение в очередь сообщений. Если сообщение уже существует, поместите соответствующий метод в очередь методов выполнения.
//注册消息接口
on: function (type, fn) {
//如果此消息不存在,创建一个该消息类型
if( typeof __message[type] === 'undefined' ){
// 将执行方法推入该消息对应的执行队列中
__message[type] = [fn];
}else{
//如果此消息存在,直接将执行方法推入该消息对应的执行队列中
__message[type].push(fn);
}
}
Опубликовать метод сообщения
Объявил, что его функция - опубликовать сообщение, когда наблюдатель состоит в том, чтобы все подписчики подписывайтесь, что последовательно выполнено подписчики, необходимо пройти два параметра, а именно соответствующие типы сообщений и параметры, необходимые для выполнения функции, в которой требуется тип сообщения.
//发布消息接口
subscribe: function (type, args) {
//如果该消息没有注册,直接返回
if ( !__message[type] ) return;
//定义消息信息
let events = {
type: type, //消息类型
args: args || {} //参数
},
i = 0, // 循环变量
len = __message[type].length; // 执行队列长度
//遍历执行函数
for ( ; i < len; i++ ) {
//依次执行注册消息对应的方法
__message[type][i].call(this,events)
}
}
удалить метод сообщения
Функция удаления метода сообщения состоит в том, чтобы очистить сообщение о том, что подписчик вышел из очереди сообщений, а также передать два параметра, тип сообщения и функцию в очереди выполнения. Здесь, чтобы избежать ситуации, когда сообщение не существует при удалении, необходимо проверить существование сообщения.
//移除消息接口
off: function (type, fn) {
//如果消息执行队列存在
if ( __message[type] instanceof Array ) {
// 从最后一条依次遍历
let i = __message[type].length - 1;
for ( ; i >= 0; i-- ) {
//如果存在改执行函数则移除相应的动作
__message[type][i] === fn && __message[type].splice(i, 1);
}
}
}
хорошо, на данный момент мы реализовали базовую модель наблюдателя, и теперь пришло время показать наши навыки~ Возьмите его и протестируйте!
Покажи свои таланты
Прежде всего, давайте проведем простой тест, чтобы увидеть, насколько хорошо работает шаблон наблюдателя, который мы создали сами?
//订阅消息
Observe.on('say', function (data) {
console.log(data.args.text);
})
Observe.on('success',function () {
console.log('success')
});
//发布消息
Observe.subscribe('say', { text : 'hello world' } )
Observe.subscribe('success');
Мы находимся в типе сообщения какsayВ сообщениях, зарегистрированных в сообщении два метода, один из которых принимает параметры, другой не требует параметров, а затем проходитsubscribeвыпускатьsayа такжеsuccessсообщение, результат такой, как мы и ожидали, вывод консолиhello worldтак же какsuccess ~
Смотреть! Мы успешно реализовали наш наблюдатель ~ поставьте себе лайк!
Двухстороннее связывание пользовательских данных
Как указано выше,vueДвусторонняя привязка реализована путем перехвата данных и публикации-подписки.Теперь мы используем эту идею, чтобы реализовать простую двустороннюю привязку данных самостоятельно~
В первую очередь, конечно, должна быть структура страницы, мне здесь все равно, я просто закодирую~
<div id="app">
<h3>数据的双向绑定</h3>
<div class="cell">
<div class="text" v-text="myText"></div>
<input class="input" type="text" v-model="myText" >
</div>
</div>
Я полагаю, вы уже знаете, что мы должны сделать этоinputввод этикетки, черезv-textпривязать к классу с именемtextизdivна этикетке~
Для начала нам нужно создать класс, здесь он называетсяmyVueБар.
class myVue{
constructor (options){
// 传入的配置参数
this.options = options;
// 根元素
this.$el = document.querySelector(options.el);
// 数据域
this.$data = options.data;
// 保存数据model与view相关的指令,当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
this._directives = {};
// 数据劫持,重新定义数据的 set 和 get 方法
this._obverse(this.$data);
// 解析器,解析模板指令,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
this._compile(this.$el);
}
}
Здесь мы определяемmyVueконструктор, а некоторые операции инициализации выполняются в методе построения, который аннотирован выше, я не буду их повторять здесь, в основном, чтобы посмотреть на два ключевых метода в нем_obverseа также_compile.
прежде всего_observeметод, его роль заключается в обработке входящихdata, и переопределитьdataизsetа такжеgetспособ убедиться, что мы находимся вdataКогда происходят изменения, их можно отслеживать и уведомлять, в основном используетсяObject.defineProperty()Этот метод, друзья, которые не знакомы с этим методом, пожалуйстакликните сюда~
_observe
//_obverse 函数,对data进行处理,重写data的set和get函数
_obverse(data){
let val ;
//遍历数据
for( let key in data ){
// 判断是不是属于自己本身的属性
if( data.hasOwnProperty(key) ){
this._directives[key] = [];
}
val = data[key];
//递归遍历
if ( typeof val === 'object' ) {
//递归遍历
this._obverse(val);
}
// 初始当前数据的执行队列
let _dir = this._directives[key];
//重新定义数据的 get 和 set 方法
Object.defineProperty(this.$data,key,{
enumerable: true,
configurable: true,
get: function () {
return val;
},
set: function (newVal) {
if ( val !== newVal ) {
val = newVal;
// 当 myText 改变时,触发 _directives 中的绑定的Watcher类的更新
_dir.forEach(function (item) {
//调用自身指令的更新操作
item._update();
})
}
}
})
}
}
Приведенный выше код также очень прост, и комментарии очень понятны, но есть проблема, заключающаяся в том, что при рекурсивном обходе данных я украл немного лени — здесь я задействую только некоторые простые структуры данных, такие как сложные циклы, которые я не делал. не рассматривайте возможность ввода такого рода ссылки, вы можете добавить ее самостоятельно~
Давайте взглянем_compileЭтот метод, который на самом деле является синтаксическим анализатором, функция которого заключается в разборе инструкций шаблона, привязке узла, соответствующего каждой инструкции, к функции обновления, добавлении подписчиков для мониторинга данных, получении уведомления при изменении данных, а затем переходе к обновлению. просмотреть изменения, конкретная реализация выглядит следующим образом:
_compile
_compile(el){
//子元素
let nodes = el.children;
for( let i = 0 ; i < nodes.length ; i++ ){
let node = nodes[i];
// 递归对所有元素进行遍历,并进行处理
if( node.children.length ){
this._compile(node);
}
//如果有 v-text 指令 , 监控 node的值 并及时更新
if( node.hasAttribute('v-text')){
let attrValue = node.getAttribute('v-text');
//将指令对应的执行方法放入指令集
this._directives[attrValue].push(new Watcher('text',node,this,attrValue,'innerHTML'))
}
//如果有 v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件
if( node.hasAttribute('v-model') && ( node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')){
let _this = this;
//添加input时间
node.addEventListener('input',(function(){
let attrValue = node.getAttribute('v-model');
//初始化赋值
_this._directives[attrValue].push(new Watcher('input',node,_this,attrValue,'value'));
return function () {
//后面每次都会更新
_this.$data[attrValue] = node.value;
}
})())
}
}
}
Приведенный выше код тоже очень понятен, мы начинаем с корневого элемента#appНачните рекурсивно проходить каждый узел и определите, есть ли у каждого узла соответствующая инструкция, здесь мы сосредоточимся только наv-textа такжеv-model,Мыv-textвыполнено один разnew Watcher(), и вставьте егоmyTextВ инструкции даv-modelтакже был проанализирован, гдеinputграницаinputсобытие и передать его черезnew Watcher()а такжеmyTextсвязаны, то мы должны посмотреть на этоWatcherчто именно?
WatcherЭто на самом деле подписчик, да_observerа также_compileМост связи используется для привязки функции обновления для реализацииDOMобновление элемента
Warcher
class Watcher{
/*
* name 指令名称,例如文本节点,该值设为"text"
* el 指令对应的DOM元素
* vm 指令所属myVue实例
* exp 指令对应的值,本例如"myText"
* attr 绑定的属性值,本例为"innerHTML"
* */
constructor (name, el, vm, exp, attr){
this.name = name;
this.el = el;
this.vm = vm;
this.exp = exp;
this.attr = attr;
//更新操作
this._update();
}
_update(){
this.el[this.attr] = this.vm.$data[this.exp];
}
}
каждый раз, когда вы создаетеWatcher, будут переданы соответствующие параметры, и он также будет выполнен один раз._updateоперация, выше_compile, мы создаем дваWatcherпример, но два соответствующих_updateОперация отличается, т.div.textОперация фактически эквивалентнаdiv.innerHTML=h3.innerHTML = this.data.myText, дляinputэквивалентноinput.value=this.data.myText, чтобы каждый раз данныеset, мы вызовем два_updateработа, обновление отдельноdivа такжеinputв содержании~
Без дальнейших ADO давайте проверим это ~
Сначала инициализируйте его~
//创建vue实例
const app = new myVue({
el : '#app' ,
data : {
myText : 'hello world'
}
})
Далее, картинка выше~
Мы успешно реализовали простую двустороннюю привязку, отлично~
Эпилог
Теперь у вас есть более глубокое понимание шаблона наблюдателя? На самом деле, я так много сказал здесь, но это играет роль только в привлечении новых идей.Важным моментом является дизайнерская идея.Чтобы научиться разумно применять эту дизайнерскую идею к нашему фактическому процессу разработки, процесс может быть трудным, но на бумаге я стал чувствовать себя мелким, и я знаю, что это дело должно быть сделано, все, давайте~
Да, кстати, сегодня 1024 год, всех с праздником~