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 和 Directive,并且自己实现了一个 v-on 的指令,那么现在我们的代码既可以监听数据变动,又可以监听DOM事件。如何把这两部分链接起来呢?那么再实现 Dep 和 Watcher 就完整了。
v-on
Dep
Watcher
这里的 dep.js 其实就是一个记录依赖关系的,他有一个内部的数组 subs 会把所有依赖的 watcher 记录在里面,然后 observer 在观察到数据改变的时候,就告诉dep,它会负责遍历 subs 并调用他们的 update 也就是通知所有相关的 watcher
dep.js
subs
watcher
observer
update
所以代码这里就不贴了,大家可以直接去看源码。这也是唯一一个我没有自己实现而是从 vuejs 中复制过来的类。
vuejs
那么现在我们自己实现一个 Watcher 类
首先定义我们的scope,Watcher 到底要做什么? 还记得上一篇我们的 Directive 类么,它会拿到一个 descriptor 作为参数,而他依赖watcher来知道什么时候需要执行update。那么什么时候需要执行 update 呢?显然是指令的表达式中的值更新了就需要执行 update。
Directive
descriptor
举个栗子:
Hello <span v-text=“name”></span>
这里通过 v-text 指令绑定了 this.name ,那么当name更新的时候显然需要更新DOM,如何更新DOM我们这里不关心,这是 v-text 指令中实现的,我们关心的只是调用他的 update 方法。
v-text
this.name
所以,我们的watcher需要知道这些:
vm.name
name
export default function Watcher (vm, expOrFn, cb) { vm._watchers.push(this) this.vm = vm this.expOrFn = expOrFn this.expression = expOrFn this.cb = cb this.id = ++uid // uid for batching this.deps = [] this.depIds = new Set() // TODO: support expression, like: "'Hello' + user.name" this.getter = () => { return vm[expOrFn] } this.setter = (vm, value) => { return vm[expOrFn] = value } this.value = this.get() } Watcher.prototype.update = function () { this.run() } Watcher.prototype.run = function () { const value = this.get() const oldValue = this.value if (value !== oldValue) { this.cb.call(this.vm, value, oldValue) } } Watcher.prototype.get = function () { Dep.target = this const value = this.getter.call(this.vm, this.vm) Dep.target = null return value } Watcher.prototype.set = function (value) { return this.setter.call(this.vm, this.vm, value) } Watcher.prototype.addDep = function (dep) { if (!this.depIds.has(dep.id)) { this.deps.push(dep) this.depIds.add(dep.id) dep.addSub(this) } }
有几点需要注意的:
1, getter 和 setter
为了方便起见,我们做了一个非常非常非常简单的 getter 和 setter,所以我们不支持 v-text=“‘hello’ + name” 这样的表达式。对表达式的支持,在vuejs中做了非常详尽的处理,其中如何处理 people.name 这样路径,就是勾三股四那篇文章的图示讲的内容,有兴趣可以看一下,其实是一个自动状态机 http://jiongks.name/blog/vue-code-review/
v-text=“‘hello’ + name”
people.name
2, addDep 是干嘛的?
这里是强调了很多遍的地方,addDep 是把自己加到 deps 的依赖里的。这涉及到vuejs解析依赖的机制: 在vuejs中,对一个表达式比如 name + age , 他的依赖并不是通过解析这个表达式的时候获取的,而是在计算他们的值得时候记录的。也就是在 计算这个表达式的过程中,有哪些 watcher 正在执行 get,就会把他们记录了为对当前observer的依赖。
name + age
observer中下面代码就是干这个的:
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val if (Dep.target) { //.. 当前有一个watcher正在执行 get 方法,那么他肯定依赖这个observe dep.depend() } return value },
所以 watcher 的get函数开始的时候就会执行:
Dep.target = this
get结束的时候会设置为 null。
请务必理解并牢记,这是一个非常巧妙的设计。
3,关于 batcher
这里我们省略了batcher相关的内容,其实细心的读者应该会发现 update 里面直接调用了 run 那么为什么不直接写成一个函数呢,其实这是因为我们省略了一个非常重要的性能优化:batcher。他会把一个 tick 的变动全部合并执行,而不是每次改动都执行一次DOM的更新。 vuejs中是这么实现的:
run
Watcher.prototype.update = function (shallow) { if (this.lazy) { this.dirty = true } else if (this.sync || !config.async) { this.run() } else { //…. pushWatcher(this) } }
只有 sync 模式的时候会执行 run,也就是更新DOM,默认的 async 模式下只是把 watcher 加入一个队列,在 nextTick 的时候会统一把队列中的watcher都执行。 也就是默认模式下(async) ,vuejs会在nextTick的时候才会更新DOM,所以我们有数据更新之后立刻获取DOM的值很可能是旧的哦。
sync
async
nextTick
Vue中异步更新的原理,就是在 nextTick 的时候统一执行所有的更新, 这一点也很重要,请务必牢记,因为这是面试的时候的一个常见的考点。
到这里为止,我们不仅可以创建 directive,而且当 directive 表达式的值改变的时候,还会执行他的 update 函数,所以我们就能实现更多的指令了。
directive
下一篇我们实现两个常用的指令: v-text 和 v-on,这两个指令实现了之后,我们的 tiny-vue 就基本可用了,恭喜~
tiny-vue
The text was updated successfully, but these errors were encountered:
No branches or pull requests
讲了这么多,希望大家没忘记第二篇中我们讲Vue的架构的时候贴的一个数据流程图:
我们已经实现了 Observer 和 Directive,并且自己实现了一个
v-on
的指令,那么现在我们的代码既可以监听数据变动,又可以监听DOM事件。如何把这两部分链接起来呢?那么再实现Dep
和Watcher
就完整了。这里的
dep.js
其实就是一个记录依赖关系的,他有一个内部的数组subs
会把所有依赖的watcher
记录在里面,然后observer
在观察到数据改变的时候,就告诉dep,它会负责遍历subs
并调用他们的update
也就是通知所有相关的watcher
所以代码这里就不贴了,大家可以直接去看源码。这也是唯一一个我没有自己实现而是从
vuejs
中复制过来的类。那么现在我们自己实现一个 Watcher 类
首先定义我们的scope,Watcher 到底要做什么?
还记得上一篇我们的
Directive
类么,它会拿到一个descriptor
作为参数,而他依赖watcher来知道什么时候需要执行update。那么什么时候需要执行update
呢?显然是指令的表达式中的值更新了就需要执行update
。举个栗子:
这里通过
v-text
指令绑定了this.name
,那么当name更新的时候显然需要更新DOM,如何更新DOM我们这里不关心,这是v-text
指令中实现的,我们关心的只是调用他的update
方法。所以,我们的watcher需要知道这些:
vm.name
取值name
这个字符串,这样我们才知道要取的是name
,也能知道要观察他的变动。有几点需要注意的:
1, getter 和 setter
为了方便起见,我们做了一个非常非常非常简单的 getter 和 setter,所以我们不支持
v-text=“‘hello’ + name”
这样的表达式。对表达式的支持,在vuejs中做了非常详尽的处理,其中如何处理people.name
这样路径,就是勾三股四那篇文章的图示讲的内容,有兴趣可以看一下,其实是一个自动状态机 http://jiongks.name/blog/vue-code-review/2, addDep 是干嘛的?
这里是强调了很多遍的地方,addDep 是把自己加到 deps 的依赖里的。这涉及到vuejs解析依赖的机制:
在vuejs中,对一个表达式比如
name + age
, 他的依赖并不是通过解析这个表达式的时候获取的,而是在计算他们的值得时候记录的。也就是在 计算这个表达式的过程中,有哪些watcher
正在执行 get,就会把他们记录了为对当前observer的依赖。observer中下面代码就是干这个的:
所以 watcher 的get函数开始的时候就会执行:
get结束的时候会设置为 null。
请务必理解并牢记,这是一个非常巧妙的设计。
3,关于 batcher
这里我们省略了batcher相关的内容,其实细心的读者应该会发现
update
里面直接调用了run
那么为什么不直接写成一个函数呢,其实这是因为我们省略了一个非常重要的性能优化:batcher。他会把一个 tick 的变动全部合并执行,而不是每次改动都执行一次DOM的更新。vuejs中是这么实现的:
只有
sync
模式的时候会执行run
,也就是更新DOM,默认的async
模式下只是把watcher
加入一个队列,在nextTick
的时候会统一把队列中的watcher都执行。也就是默认模式下(async) ,vuejs会在nextTick的时候才会更新DOM,所以我们有数据更新之后立刻获取DOM的值很可能是旧的哦。
Vue中异步更新的原理,就是在
nextTick
的时候统一执行所有的更新, 这一点也很重要,请务必牢记,因为这是面试的时候的一个常见的考点。到这里为止,我们不仅可以创建 directive,而且当
directive
表达式的值改变的时候,还会执行他的update
函数,所以我们就能实现更多的指令了。下一篇我们实现两个常用的指令: v-text 和 v-on,这两个指令实现了之后,我们的
tiny-vue
就基本可用了,恭喜~The text was updated successfully, but these errors were encountered: