хм~~~
Откройте дверь, на этот раз я не продаю 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 год, всех с праздником~