Watcher和Dep建立关联
首先在 defineReactive
中创建 Dep
实例,与 data.key
是一一对应的关系,然后再 get
中 调用dep.addDep
进行依赖的收集,Dep.target
就是一个 Watcher
。在 set
中 调用 dep.notify()
通知所有的 Watchers
更新视图。
function defineReactive(obj, key, val) { ... ... // 创建 Dep 实例 , 与 key 一一对应 const dep = new Dep() // 通过该方法拦截数据 Object.defineProperty(obj, key, { // 读取数据的时候会走这里 get() { console.log('????????~ get:', key); // 依赖收集 Dep.target 就是 一个Watcher Dep.target && dep.addDep(Dep.target) return val }, // 更新数据的时候会走这里 set(newVal) { // 只有当新值和旧值不同的时候 才会触发重新赋值操作 if (newVal !== val) { console.log('????????~ set:', key); // 如果 newVal 是个对象类型,再次做响应式处理。 if (typeof obj === 'object' && obj !== null) { observe(newVal) } val = newVal // 通知更新 dep.notify() } } })}复制代码
代码实现 - 第四回合 事件和双向绑定
事件绑定
事件绑定也很好理解,首先判断节点的属性是不是以 @
开头,然后拿到事件的类型,也就是例子中的 click
, 再根据函数名找到 methods
中定义的函数体,最后添加事件监听就行了。
class Compile { ... ... // 省略号的地方都没有发生改变 compile(el) { // 判断节点属性是不是一个事件 if (this.isEvent(attrName)) { // @click="onClick" const dir = attrName.substring(1) // click // 事件监听 this.eventHandler(node, exp, dir) } } ... ... // 判断节点是不是一个事件 也就是以@开头 isEvent(dir) { return dir.indexOf("@") === 0 } eventHandler(node, exp, dir) { // 根据函数名字在配置项中获取函数体 const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp] // 添加事件监听 node.addEventListener(dir, fn.bind(this.$vm)) } ... ...}复制代码
双向绑定
my-model
其实也是一个指令,走的也是指令相关的处理逻辑,所以我们只需要添加一个 model
指令和对应的 modelUpdater
处理函数就行了。
my-model
双向绑定其实就是 事件绑定 和修改 value
的一个语法糖,本文以 input
为例,其它的表单元素绑定的事件会有不同,但是道理是一样的。
class Compile { // my-model指令 my-model='xxx' model(node, exp) { // update 方法只完成赋值和更新 this.update(node, exp, 'model') // 事件监听 node.addEventListener('input', e => { // 将新的值赋值给 data.key 即可 this.$vm[exp] = e.target.value }) } modelUpdater(node, value) { // 给表单元素赋值 node.value = value }}复制代码
现在也可以更新一下模板编译的流程图啦~
后语
到这里一个简易版的 Vue
数据响应式就完成了,整套流程从头到尾都是自己手写的,还怕不懂原理么?