We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
要理解Vue,从observer开使是一个不错的选择。因为从本质上来讲,除去生命周期函数,虚拟DOM,组件系统等以外, Vue首先建立在数据监测之上,可以收集依赖,并在数据变化时自动通知到Vue的实例。
observer的相关代码在core/observer下,数据绑定的逻辑主要集中在dep/watcher/index/traverse4个文件中。
core/observer
dep/watcher/index/traverse
当有一个表达式,我们可以收集它的依赖,并在数据变化时,让依赖反过来去通知表达式这种变化。用一个例子来示意整个工作流程:
function expOrFn(vm) { return vm.user }
很显然,expOrFn依赖vm.user,当user变化时,expOrFn应该自动重新执行。但,怎么知道这种依赖关系呢?
vm.user
user
对数据vm来说,假设我们用getter来改写它的所有属性;那么当我们访问vm.user的时候,getter函数会执行, 所以,只要我们执行一次expOrFn,它的所有依赖就都知道了!bingo!
expOrFn
const vm = { user: 'creeper' } defineGetter(vm) let value = expOrFn(vm)
一切看起来很简单。然后很自然地,我们加上setter来感知数据的更新。
当vm变化时,我们必须能够感知这种变化,否则收集依赖是完全没有意义的。
const vm = { user: 'creeper' } defineGetterAndSetter(vm) let value = expOrFn(vm) // 然后我们更新数据 vm.user = 'who?' // 因为调用了setter,所以我们可以知道数据更新了!
看起来一切都搞定了。但上面的代码只是伪代码,实际开发中,我们必须要解决怎么定义依赖,怎么收集依赖,怎么通知更新的整个流程。
Dep
// 依赖,作为纽带来用,本身设计的很薄 class Dep { // 添加订阅者——即谁依赖这个依赖 addSub(sub) { this.subs.push(sub) } // 当有变化时,通知订阅者 notify() { this.subs.forEach(sub => sub.update()) } // 很有意思的方法,下一步重点说,或者直接看源代码的注释 depend() { Dep.target.addDep(this) } }
接下来看看Dep是怎么用在 getter/setter里的:
// 假设有数据 vm,我们对 vm 的每个属性调用 defineReactive 来设置 getter/setter // defineReactive(vm, 'user', 'creeper') function defineReactive(obj, key, val) { // 每个属性创建一个dep,这是一个一一对应的关系 const dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter() { if (Dep.target) { // 收集依赖 dep.depend() } return val }, set: function reactiveSetter(newVal) { val = newVal // 通知数据更新了 dep.notify() } }) }
如上,getter/setter 配合对应的dep,可以完成依赖收集和更新通知。下面描述整个流程是怎么工作的 (比如dep.depend()怎么收集依赖的):
dep.depend()
Watcher
class Watcher { // vm 是数据,expOrFn 是表达式,cb 是更新时的回调 constructor(vm, expOrFn, cb) { this.vm = vm // 用于收集依赖 this.deps = [] // 收集依赖 this.value = this.get() } get() { // 设置Dep.target,方便依赖收集时 dep.depend 可以正确调用 Dep.target = this // 调用 expOrFn 来收集依赖 const val = this.expOrFn.call(this.vm, this.vm) } // 联系上面的 dep.depend,是不是恍然大悟? addDep(dep) { this.deps.push(dep) dep.addSub(this) }, // 联系上面的 dep.notify,是不是懂了? update() { this.cb.call(this, this.get(), this.value) } } const vm = { user: 'creeper' } // 设置 getter/setter observe(vm) const exp = vm => vm.user // 让exp可以监测数据变化 new Watcher(vm, exp, function updateCb() {})
以上即整个observer流程,当然,里面简化了很多细节,详细的可看代码注释和下面的核心代码解读。
observe
Observer
observe(value, asRootData)方法用于为value创建getter/setter,从而实现对数据变化的监听;该方法会为value创建对应的Observer实例,而observer则是实际转化value的属性为getter/setter,收集依赖和转发更新的地方。
observe(value, asRootData)
observer相关的核心代码是defineReactive来创建getter/setter,下面是相关注释:
defineReactive
/** * 把 property 转化为 getter 和 setter * - 创建dep(dep是一个纽带,连接watcher和数据,getter时收集依赖,setter时通知更新); * - 在 getter 里面进行依赖收集,在非shallow时递归收集 * - 在 setter 里面进行更新通知,在非shallow时重新创建childOb */ export function defineReactive( obj, key, val, customSetter, shallow ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } // ⚠️ // 大部分情况shallow默认是false的,即默认递归observe。 // - 当val是数组时,childOb被用来向当前watcher收集依赖 // - 当val是普通对象时,set/del函数也会用childOb来通知val的属性添加/删除 let childOb = !shallow && observe(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, // watcher 初始化时调用自己的 watcher.get(),最终调用这个 getter, // 而 dep.depend 执行,把 watcher 放到了自己的 subs 里;所以当 // set 执行时,watcher 被通知更新。 get: function reactiveGetter() { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() // 现在问题是为什么要依赖 childOb 呢? // 考虑到如果 value 是数组,那么 value 的 push/shift 之类的操作, // 是触发不了下面的 setter 的,即 dep.depend 在这种情况不会被调用。 // 此时,childOb 即value这个数组对应的 ob,数组的操作会通知到childOb, // 所以可以替代 dep 来通知 watcher。 if (childOb) { childOb.dep.depend() // 同时,对数组元素的操作,需要通过 dependArray(value) 来建立依赖。 if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter(newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } // 对新val重新创建childOb childOb = !shallow && observe(newVal) // 通知更新 dep.notify() } }) }
observer核心入口是Watcher,创建一个watcher可以监测数据的变化,并在变化时执行回调。
下面是 traverse 的代码:
// 递归遍历 val,深度收集依赖 function _traverse(val, seen) { let i, keys const isA = Array.isArray(val) // 如果不是数组或对象,或者是 VNode,或者frozen,则不再处理。 if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { return } if (val.__ob__) { const depId = val.__ob__.dep.id // 如果已经收集过,则不再重复处理了。 if (seen.has(depId)) { return } seen.add(depId) } // 对数组的每个元素调用 _traverse if (isA) { i = val.length while (i--) _traverse(val[i], seen) } // 对子属性(val[keys[i]])访问,即调用 defineReactvie 定义的 getter,收集依赖 else { keys = Object.keys(val) i = keys.length while (i--) _traverse(val[keys[i]], seen) } }
下面是一些测试代码,帮助理解observer的运行。
const { observe } = require('./dist/core/observer') const Watcher = require('./dist/core/observer/watcher').default function createWather(data, expOrFn, cb, options) { const vm = { data: data || {}, _watchers: [] } observe(vm.data, true) return new Watcher(vm, expOrFn, cb, options) } const raw = { s: 'hi', n: 100, o: {x: 1, arr: [1, 2]}, arr: [8, 9] } const w = createWather(raw, function expOrFn() { // 1 return this.data.o }, (a, b) => { console.log('--->', a, b) }, { deep: false, sync: true }) // 2 raw.o.x = 2
我在设置getter的地方加了一些输出语句:
get: function reactiveGetter() { const value = getter ? getter.call(obj) : val; console.log('call getter --->', key, val, !!_dep.default.target) if (_dep.default.target) { dep.depend(); if (childOb) { childOb.dep.depend(); console.log('call childOb.dep.depend') if (Array.isArray(value)) { dependArray(value); } } } return value; },
并且测试代码中,序号1和2下面的一行代码会替换来测试不同的情况,测试结果如下:
1. retrun this.data.o 2. raw.o.x = 101 call getter ---> o { x: [Getter/Setter], arr: [Getter/Setter] } true call childOb.dep.depend call getter ---> o { x: [Getter/Setter], arr: [Getter/Setter] } false 1. retrun this.data.o 2. raw.o = 101 call getter ---> o { x: [Getter/Setter], arr: [Getter/Setter] } true call childOb.dep.depend call getter ---> o 101 true ---> 101 { x: [Getter/Setter], arr: [Getter/Setter] } 1. retrun this.data.arr 2. raw.arr.push(10) call getter ---> arr [ 8, 9 ] true call childOb.dep.depend // 因为child depend,数组的操作可以被监测。 call getter ---> arr [ 8, 9 ] false // push 产生的 getter call getter ---> arr [ 8, 9, 10 ] true // 调用回调时调用了 this.get() call childOb.dep.depend ---> [ 8, 9, 10 ] [ 8, 9, 10 ]
可下载本repo,在codes/vue下跑npm i && npm run build,然后如果是vscode,可以直接调试测试 vue-observer,了解 observer 的工作原理。
npm i && npm run build
测试 vue-observer
本篇的分析尽量不把Vue其它部分牵扯进来,所以遗留了 computed 型watcher和 scheduler 没有涉及。下一篇将解析 instance部分,会把遗留的补上。
computed
The text was updated successfully, but these errors were encountered:
No branches or pull requests
Vue源码解析一:observer
要理解Vue,从observer开使是一个不错的选择。因为从本质上来讲,除去生命周期函数,虚拟DOM,组件系统等以外,
Vue首先建立在数据监测之上,可以收集依赖,并在数据变化时自动通知到Vue的实例。
observer的相关代码在
core/observer
下,数据绑定的逻辑主要集中在dep/watcher/index/traverse
4个文件中。observer工作原理简述
当有一个表达式,我们可以收集它的依赖,并在数据变化时,让依赖反过来去通知表达式这种变化。用一个例子来示意整个工作流程:
很显然,expOrFn依赖
vm.user
,当user
变化时,expOrFn应该自动重新执行。但,怎么知道这种依赖关系呢?对数据vm来说,假设我们用getter来改写它的所有属性;那么当我们访问
vm.user
的时候,getter函数会执行,所以,只要我们执行一次
expOrFn
,它的所有依赖就都知道了!bingo!一切看起来很简单。然后很自然地,我们加上setter来感知数据的更新。
当vm变化时,我们必须能够感知这种变化,否则收集依赖是完全没有意义的。
看起来一切都搞定了。但上面的代码只是伪代码,实际开发中,我们必须要解决怎么定义依赖,怎么收集依赖,怎么通知更新的整个流程。
Dep
),并设计了巧妙的 getter/setter 来收集依赖和通知更新:接下来看看Dep是怎么用在 getter/setter里的:
如上,getter/setter 配合对应的dep,可以完成依赖收集和更新通知。下面描述整个流程是怎么工作的
(比如
dep.depend()
怎么收集依赖的):Watcher
来串联整个流程。以上即整个observer流程,当然,里面简化了很多细节,详细的可看代码注释和下面的核心代码解读。
核心代码解读
observe
函数和Observer
类observe(value, asRootData)
方法用于为value创建getter/setter,从而实现对数据变化的监听;该方法会为value创建对应的Observer实例,而observer则是实际转化value的属性为getter/setter,收集依赖和转发更新的地方。observer相关的核心代码是
defineReactive
来创建getter/setter,下面是相关注释:入口
Watcher
observer核心入口是
Watcher
,创建一个watcher可以监测数据的变化,并在变化时执行回调。下面是 traverse 的代码:
测试和代码执行过程简述
下面是一些测试代码,帮助理解observer的运行。
我在设置getter的地方加了一些输出语句:
并且测试代码中,序号1和2下面的一行代码会替换来测试不同的情况,测试结果如下:
可下载本repo,在codes/vue下跑
npm i && npm run build
,然后如果是vscode,可以直接调试测试 vue-observer
,了解 observer 的工作原理。更多
本篇的分析尽量不把Vue其它部分牵扯进来,所以遗留了
computed
型watcher和 scheduler 没有涉及。下一篇将解析instance部分,会把遗留的补上。
The text was updated successfully, but these errors were encountered: