简单的订阅发布模式

    xiaoxiao2023-10-30  157

    const Vue = (function () { let uid = 0; // 消息管理员,用于存储订阅者并发布消息 class Dep { constructor () { this.id = uid++; // 设置ID,用于区分新watcher this.subs = []; // 存储订阅者的数组 } depend () { // 触发target上的Watcher中的addDep方法,参数为dep的实例本身,方法作用就是将watcher放进subs数组 Dep.target.addDep(this); } addSub (sub) { // 添加订阅者 this.subs.push(sub); } notify () { this.subs.forEach(sub => sub.update()); } } Dep.target = null; // 监听者,监听对象属性值的变化 class Observer { constructor (value) { this.value = value; this.walk(value); } walk (value) { // 遍历属性值并监听 Object.keys(value).forEach(key => this.convert(key, value[key])) } convert (key, val) { // 执行监听的具体方法 defineReactive(this.value, key, val); } } function defineReactive (obj, key, val) { const dep = new Dep(); // 一个属性一个消息管理器? let childOb = observer(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { console.log('我不是get了吗'); // 如果Dep类存在target属性,将其添加至dep实例的subs数组中,target指向一个watcher实例 // 每个watcher都是一个订阅者,watcher实例在实例化的过程中,会读取data中的某个属性,从而触发当前的get方法 if (Dep.target) { dep.depend(); } return val; }, set: newVal => { console.log('我不是set了吗', newVal, val); if (newVal !== val) { // 发生改变了,可能由一个普通变量转变为对象,所以需要继续监听 val = newVal; childOb = observer(newVal); dep.notify(); } } }) } function observer (val) { if (val && typeof val === 'object') { return new Observer(val); } return ''; } // 订阅者,指的是它需要知道某个对象发生改变而做出反应,需要先订阅 class Watcher { constructor (vm, expOrFn, callback) { this.depIds = {}; // hash储存订阅者的ID,避免重复的订阅者 this.vm = vm; // 被订阅的数据一定要来自于当前的vue实例 this.callback = callback; // 回调函数 this.exOrFn = expOrFn; // 被订阅的数据 this.val = this.get(); // 维护更新之前的数据 } // 订阅中心在监听到数据发生改变时会触发notify方法,方法中调用update方法 update () { this.run(); } run () { let val = this.get(); this.val = val; this.callback.call(this.vm, val); } addDep (dep) { // 如果在depIds中的hash中没有当前ID,可以判断是新的Watcher,因此可以添加到dep的数组中存储,此判断是为了避免同ID的watcher被多次存储 // 一个新的watcher对象只可以被一个订阅中心储存一次 if (!this.depIds.hasOwnProperty(dep.id)) { dep.addSub(this); this.depIds[dep.id] = dep; } } get () { // 将当前的watcher传到Dep.target属性里面,方便获取 Dep.target = this; const val = this.vm._data[this.exOrFn]; // 触发了observer的get方法,并将当前订阅者存放到了sups数组 Dep.target = null; // 置空 return val; } } class vue { constructor (options = {}) { let data = (this._data = options.data); // vue绑定的数据 this.methods = options.methods; // vue绑定的方法 Object.keys(data).forEach(key => this._proxy(key)) // 将data的属性都代理到vue实例上,这样可以监听到他的改变 observer(data); } $watch (expOrFn, cb) { new Watcher(this, expOrFn, cb) } _proxy (key) { Object.defineProperty(this, key, { configurable: true, enumerable: true, get: () => { return this._data[key] }, set: val => { this._data[key] = val; } }) } } return vue })(); let demo = new Vue({ data: { text: '', }, }); const p = document.getElementById('p'); const input = document.getElementById('input'); input.addEventListener('keyup', function(e) { demo._data.text = e.target.value; }); demo.$watch('text', str => p.innerHTML = str); // 基于proxy升级的订阅发布模式 const Vue = (function () { let uid = 0 // 用于储存订阅者并发布消息 class Dep { constructor () { // 设置id,用于区分新Watcher和只改变属性值后新产生的Watcher this.id = uid++ // 储存订阅者的数组 this.subs = [] } // 触发target上的Watcher中的addDep方法,参数为dep的实例本身 depend () { Dep.target.addDep(this) } // 添加订阅者 addSub (sub) { this.subs.push(sub) } notify () { // 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理 this.subs.forEach(sub => sub.update()) } } // 为Dep类设置一个静态属性,默认为null,工作时指向当前的Watcher Dep.target = null // 监听者,监听对象属性值的变化 class Observer { constructor (value) { this.value = value this.walk(value) } // 遍历属性值并监听 walk (value) { Object.keys(value).forEach(key => this.convert(key, value[key])) } // 执行监听的具体方法 convert (key, val) { defineReactive(this.value, key, val) } } function defineReactive (obj, key, val) { const dep = new Dep() // 给当前属性的值添加监听 let chlidOb = observe(val) if (typeof val === 'object') { obj[key] = new Proxy(val, { set (target, property, val2, receiver) { if (val === val2) return // 对新值进行监听 chlidOb = observe(val2) // 通知所有订阅者,数值被改变了 let returnData = Reflect.set(target, property, val2, receiver) dep.notify() return returnData }, get (target, key, receiver) { if (Dep.target) { dep.depend() } return Reflect.get(target, key, receiver) } }) } else { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { // 如果Dep类存在target属性,将其添加到dep实例的subs数组中 // target指向一个Watcher实例,每个Watcher都是一个订阅者 // Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法 if (Dep.target) { dep.depend() } return val }, set: newVal => { if (val === newVal) return val = newVal // 对新值进行监听 chlidOb = observe(newVal) // 通知所有订阅者,数值被改变了 dep.notify() } }) } } function observe (value) { // 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听 if (!value || typeof value !== 'object') { return } return new Observer(value) } class Watcher { constructor (vm, expOrFn, cb) { this.depIds = {} // hash储存订阅者的id,避免重复的订阅者 this.vm = vm // 被订阅的数据一定来自于当前Vue实例 this.cb = cb // 当数据更新时想要做的事情 this.expOrFn = expOrFn // 被订阅的数据 this.val = this.get() // 维护更新之前的数据 } // 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用 update () { this.run() } addDep (dep) { // 如果在depIds的hash中没有当前的id,可以判断是新Watcher,因此可以添加到dep的数组中储存 // 此判断是避免同id的Watcher被多次储存 if (!this.depIds.hasOwnProperty(dep.id)) { dep.addSub(this) this.depIds[dep.id] = dep } } run () { const val = this.get() this.val = val this.cb.call(this.vm, val) } get () { // 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者 Dep.target = this let val = this.vm._data[this.expOrFn] // 触发一个get if (typeof val === 'object') { // 没有被get到,没存进去 if (Object.prototype.toString.call(val) === '[object Array]') { val = Object.assign([], this.vm._data[this.expOrFn]) } else if (Object.prototype.toString.call(val) === '[object Object]') { val = Object.assign({}, this.vm._data[this.expOrFn]) } } Dep.target = null return val } } class Vue { constructor (options = {}) { // 简化了$options的处理 this.$options = options // 简化了对data的处理 let data = (this._data = this.$options.data) // 监听数据 observe(this._data) // 将所有data最外层属性代理到Vue实例上 Object.keys(this._data).forEach(key => this._proxy(key, this._data[key])) } // 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者 $watch (expOrFn, cb) { new Watcher(this, expOrFn, cb) } // obj[key] = new Proxy(value, // defineObjectReactive(data, k, data[k]) _proxy (key, value) { let _this = this if (typeof value === 'object') { this[key] = new Proxy(value, { set (target, key, val, receiver) { return Reflect.set(target, key, val, receiver) }, get (target, key, receiver) { return Reflect.get(target, key, receiver) } }) } else { Object.defineProperty(this, key, { configurable: true, enumerable: true, get: () => this._data[key], set: val => { this._data[key] = val }, }) } } } return Vue })() let demo = new Vue({ data: { text: '', list: [] }, }) const p = document.getElementById('p') const input = document.getElementById('input') input.addEventListener('keyup', function (e) { demo.text = e.target.value }) demo.$watch('text', str => p.innerHTML = str) const list = document.getElementById('list') const btn = document.getElementById('btn') btn.addEventListener('click', function () { demo.list.push(1) }) const render = arr => { const fragment = document.createDocumentFragment() if (document.querySelectorAll('li').length === arr.length) { // 说明只是改了数据内容,没改长度 let spanArray = document.querySelectorAll('span') for (let i = 0; i < arr.length; i++) { spanArray[i].innerHTML = arr[i] } } else { let li = document.createElement('li') let span = document.createElement('span') span.innerHTML = arr[arr.length - 1] let input = document.createElement('input') input.value = arr[arr.length - 1] input.className = 'testInput' li.appendChild(span) li.appendChild(input) fragment.appendChild(li) list.appendChild(fragment) let allInput = document.querySelectorAll('.testInput') allInput[arr.length - 1].oninput = function (e) { demo.list[arr.length - 1] = e.target.value } } } demo.$watch('list', list => { render(list) })

     

    最新回复(0)