new Vue({ data() { return { name: 'yjh', }; }, router, store, render: h => h(App), }).$mount('#app');
vm._data = vm.$options.data()
proxy(vm,
_data, key) 代理到vm上访问
html
function proxy(vm, _data, key)() { Object.defineProperty(target, key, { get() { return vm._data.key }, set(val) { vm._data.key = val } }) }
const updateComponent = () => { // 建立虚拟dom const vnode = vm._render() // 建立虚拟dom的过程等同于以下代码行 // const vnode = vm.$options.render.call(vm, vm.$createElement) // 更新$el vm._update(vnode) }
在以上发生的行为当中,第3步与第7步二者相辅相成;也是咱们最须要关心的,弄清楚这二者,vue响应式原理就基本掌握了vue
咱们都知道 数据发生变化视图也随之更新,那么首先咱们得知道如何监听数据的变化node
class Observer { constructor(value) { this.value = value this.walk(value) } walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } } function defineReactive(obj, key) { Object.defineProperty(obj, key, { get() { // 数据被访问 return obj.key }, set(val) { if (val === obj.key) { return } // 数据更新了 obj.key = val } }) }
当咱们在建立虚拟dom的过程当中,也就是执行vm.$createElement方法,可能会在多个地方使用到同一个数据字段(如:vm.name),即多个订阅者订阅了name的更新,所以在Vue中定义了一个发布订阅的Dep类react
class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } depend() { if (Dep.target) { this.addSub(Dep.target) } } notify() { this.subs.forEach(sub => sub.update()) } removeSub(sub) { const i = this.subs.findIndex(sub) if (i > -1) { this.subs.splice(i, 1) } } }
订阅数据更新的究竟是谁,咱们先看看以下场景git
<!-- 场景1 --> <div>名字:{{ userInfo.name }},全名:{{ fullName }}</div>
export default { data() { return { userInfo: { name: 'junhua', }, } }, mounted() { // 场景2 this.$watch('name', (newVal, val) => { // ... }) }, // 场景2 watch: { name(newVal, val) { // ... } }, computed() { // 场景3 fullName() { return `yang${this.userInfo.name}` } } }
从上面示例代码看,订阅数据更新的场景有:github
new Watcher(vm, updateComponent)
数据发生变化,更新组件所以数据订阅者包含一个参数expOrFn([Function|String]
),数据更新后须要执行的callback,以下:app
class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) } this.cb = cb || () => {} this.value = this.get() } get() { Dep.target = this const value = this.getter.call(this.vm, this.vm) Dep.target = undefined return value } update() { const val = this.value const newVal = this.get() this.cb.call(this.vm, newVal, val) } }
class Observer { constructor(value) { this.value = value this.walk(value) } walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i],) } } } function defineReactive(obj, key) { const dep = new Dep() Object.defineProperty(obj, key, { get() { // 依赖收集,收集订阅者Watcher实例 dep.depend() // 数据被访问 return obj.key }, set(val) { if (val === obj.key) { return } // 数据更新了 obj.key = val // 通知订阅者Watcher实例更新 dep.notify() } }) }
咱们再来回顾下实例化Vue应用的最重要的两点dom
observe(vm._data) // vm.$mount() const componentUpdateWatcher = new Watcher(vm, updateComponent)
updateComponent在更新渲染组件时,会访问1或多个数据模版插值,当访问数据时,将经过getter拦截器把componentUpdateWatcher做为订阅者添加到多个依赖中,每当其中一个数据有更新,将执行setter函数,对应的依赖将会通知订阅者componentUpdateWatcher执行update,即执行updateComponent;至此Vue数据响应式目的已达到,再来看官网的这张图片就很好理解了函数