vue源码原理学习笔记—响应式之依赖收集与订阅发布更新
Author:zhoulujun Date:
响应式原理
Vue.js的响应式原理依赖于Object.defineProperty。双向数据绑定demo(Observer/Watcher/Dep)浅析
将数据data变成可观察(observable)的
那么Vue是如何将所有data下面的所有属性变成可观察的(observable)呢?
let app = new Vue({ el: '#app', data: { text: 'text', text2: 'text2' }, render () { console.log('render') } }) class Vue { constructor (options) { this._data = options.data // 在initData中会调用observe这个函数将Vue的数据设置成observable的 observe(this._data, options.render) } } function observe (value, cb) {// 源码:src/core/observer/index.js Object.keys(value).forEach((key) => defineReactive(value, key, value[key], cb)) } // 当_data数据发生改变的时候就会触发set,对订阅者进行回调(在这里是render) function defineReactive (obj, key, val, cb) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { /* ....依赖收集等.... */ /* Github:https://github.com/answershuto */ return val }, set: newVal => { val = newVal cb()/* 订阅者收到消息的回调 */ } }) }
需要对app._data.text操作才会触发set。为了偷懒,我们需要一种方便的方法通过app.text直接设置就能触发set对视图进行重绘。那么就需要用到代理。
_proxy.call(this, options.data)/* 构造函数中 */ /* 代理 */ function _proxy (data) { const that = this Object.keys(data).forEach(key => { Object.defineProperty(that, key, { configurable: true, enumerable: true, get: function proxyGetter () { return that._data[key] }, set: function proxySetter (val) { that._data[key] = val } }) }) }
我们就可以用app.text代替app._data.text了。
为什么要依赖收集
new Vue({ template: `<div> <span>text1:</span> {{text1}} <span>text2:</span> {{text2}} <div>`, data: { text1: 'text1', text2: 'text2', text3: 'text3'//test 修改,并不需要触发渲染 } })
当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。
Dep依赖收集类
class Dep { constructor () { this.subs = [] } addSub (sub: Watcher) { this.subs.push(sub) } removeSub (sub: Watcher) { remove(this.subs, sub) } /* Github:https://github.com/answershuto */ notify () { // stabilize the subscriber list first const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } function remove (arr, item) { if (arr.length) { const index = arr.indexOf(item) if (index > -1) { return arr.splice(index, 1) } } }
订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。
Watcher订阅者类
class Watcher { constructor (vm, expOrFn, cb, options) { this.cb = cb this.vm = vm /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/ Dep.target = this /*Github:https://github.com/answershuto*/ /*触发渲染操作进行依赖收集*/ this.cb.call(this.vm) } update () { this.cb.call(this.vm) } }
将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。
class Vue { constructor (options) { this._data = options.data observer(this._data, options.render) let watcher = new Watcher(this,) } } function defineReactive (obj, key, val, cb) { /*在闭包内存储一个Dep对象*/ const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: () => { if (Dep.target) { /*Watcher对象存在全局的Dep.target中*/ dep.addSub(Dep.target) } }, set: newVal => { /*只有之前addSub中的函数才会触发*/ dep.notify() } }) } Dep.target = null
源码解析:https://github.com/answershuto/learnVue/blob/master/docs/从源码角度再看数据绑定.MarkDown
转载本站文章《vue源码原理学习笔记—响应式之依赖收集与订阅发布更新》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue/8154.html