整体结构
image-20210303083609219.png
-
Vue
- 记录传入的选项,设置 el
- 把 data 的成员注入到 Vue 实例
- 负责调用 Observer 实现数据响应式处理(数据劫持)
- 负责调用 Compiler 编译指令/插值表达式等
-
Observer
-
数据劫持
负责把 data 中的成员转换成 getter/setter
负责把多层属性转换成 getter/setter
如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
添加 Dep 和 Watcher 的依赖关系
数据变化发送通知
-
-
Compiler
- 负责编译模板,解析指令/插值表达式
- 负责页面的首次渲染过程
- 当数据变化后重新渲染
-
Dep
- 收集依赖,添加订阅者(watcher)
- 通知所有订阅者
-
Watcher
- 自身实例化的时候往dep对象中添加自己
- 当数据变化dep通知所有的 Watcher 实例更新视图
Vue
-
功能
- 负责接收初始化的参数
- 负责把data中的属性注入到Vue实例,转换为getter/setter
- data中的成员记录在data中的setter是真正接收数据变化的地方;接着把构造函数的参数记录在data指向同一个对象,__开头的都是私有属性或方法 el对应选项中的el
- 负责调用observer监听data中所有属性变化
- 负责调用compiler解析指令/插值表达式
结构
image-20210301085130273.png
-
代码
class Vue { constructor(options) { // 1.保存所有选项 this.$options = options this.$data = options.data this.$methods = options.methods this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el // 2.将data中的数据转换为getter/setter,存入到$data中 this._proxyData(this.$data) // 3.将methods注入到vue实例 this._proxyMethods(this.$methods) // 4.负责调用Observer实现数据劫持 new Observer(this.$data) // 5.负责调用Compiler解析指令/插值表达式等 new Compiler(this) } // 设置响应式 _proxyData(data) { Object.keys(data).forEach((key) => { Object.defineProperty(this, key, { enumrable: true, configurable: true, get() { return data[key] }, set(newValue) { if (newValue === data[key]) return data[key] = newValue }, }) }) } _proxyMethods(methods) { Object.keys(methods).forEach((key) => { this[key] = this.$methods[key] }) } } // 测试 let vm = new Vue({ el: '#app', data: { msg: 'Hello Vue', count: 100, }, })
测试,并在控制台打印vm实例
image-20210301090341826.png
Observer
-
功能
- 负责把data选项中的属性转换为响应式对象
- data中的某个属性也是对象,把该属性转换为响应式对象
- 数据变化发送通知
结构
image-20210302081600702.png
-
代码
class Observer { constructor(data) { this.walk(data) } // 如果不是对象则返回;如果是对象,遍历对象所有属性,设置getter/setter walk(data) { if (!data || typeof data !== 'object') { return } Object.keys(data).forEach((key) => { this.defineReactive(data, key, data[key]) }) } // 定义响应式成员 defineReactive(data, key, val) { const self = this // 如果val是对象,继续设置其下面的响应式 this.walk(val) // 添加观察者 get负责添加依赖,set触发更新 let dep = new Dep() Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { // Dep.target存储的是watcher对象 Dep.target && dep.addSub(Dep.target) return val }, set(newValue) { if (newValue !== val) { // 如果newValue是对象,设置newValkue的响应式成员 self.walk(newValue) val = newValue // 发送通知 dep.notify() } }, }) } } // 测试 let vm = new Vue({ el: '#app', data: { msg: 'Hello Vue', count: 100, }, }) vm.msg = { a: 123 }
image-20210302082359401.png
Compiler
-
功能
- 负责编译模板,解析指令/插值表达式
- 负责页面首次渲染
- 当数据变化后重新渲染视图
-
结构
image-20210302082740514.png
-
代码
class Compiler { constructor(vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } // 编译模板,处理文本节点和元素节点 compile(el) { let childNodes = el.childNodes Array.from(childNodes).forEach((node) => { // 处理文本节点 if (this.isTextNode(node)) { this.compileText(node) } else if (this.isElementNode(node)) { // 处理元素节点 this.compileElement(node) } // node有子节点时,递归调用 if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } // 编译元素节点,处理指令 compileElement(node) { // 获取所有属性节点 // console.log(node.attributes) // 遍历所有属性节点 Array.from(node.attributes).forEach((attr) => { // 判断是否是指令 let attrName = attr.name if (this.isDirective(attrName)) { // v-text --> text attrName = attrName.substr(2) let key = attr.value this.update(node, key, attrName) } }) } update(node, key, attrName) { // 删除on:前缀 if (attrName.startsWith('on:')) { attrName = attrName.replace('on:', '') } let updateFn = this[`${attrName}Updater`] // 因为在 textUpdater等中要使用 this updateFn && updateFn.call(this, node, this.vm[key], key) } // 处理v-text指令 textUpdater(node, value, key) { node.textContent = value new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // 处理v-model modelUpdater(node, value, key) { node.value = value // 每一个指令中创建一个 watcher,观察数据的变化 new Watcher(this.vm, key, (newValue) => { node.value = newValue }) window.addEventListener('input', () => { this.vm[key] = node.value }) } // 只需将textContent替换为innerHTML htmlUpdater(node, value, key) { node.innerHTML = value new Watcher(this.vm, key, (newValue) => { node.innerHTML = newValue }) } clickUpdater(node, value, key) { node.onclick = value } // 编译文本节点,处理插值表达式 compileText(node) { // console.dir(node) // assignedSlot: null // baseURI: "file:///F:/%E5%A4%A7%E5%89%8D%E7%AB%AF/%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86/03-01-study- materials/03-01-codes/03-01-03-06-vue-reactivity/vue/index.html" // childNodes: NodeList [] // data: "{{ msg }}" // firstChild: null // isConnected: true // lastChild: null // length: 9 // nextElementSibling: null // nextSibling: null // nodeName: "#text" // nodeType: 3 // nodeValue: "{{ msg }}" // ownerDocument: document // parentElement: h3 // parentNode: h3 // previousElementSibling: null // previousSibling: null // textContent: "{{ msg }}" // wholeText: "{{ msg }}" // 正则获取{{ msg }} let reg = /\{\{(.+?)\}\}/ // 获取文本节点内容 let value = node.textContent if (reg.test(value)) { // 获取第一个分组内容 let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) // 数据改变,更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } // 是否是指令 isDirective(attrName) { return attrName.startsWith('v-') } // 文本节点 isTextNode(node) { // nodeType节点类型 return node.nodeType === 3 } // 元素节点 isElementNode(node) { return node.nodeType === 1 } }
Dep(Dependency)
image-20210303082520641.png
-
功能
收集依赖,添加观察者(watcher)
通知所有观察者
-
结构
image-20210303082608791.png -
代码
class Dep { constructor() { // 存储所有观察者 this.subs = [] } // 添加观察者 addSub(sub) { if (sub && sub.update) { this.subs.push(sub) } } // 发送通知 notify() { this.subs.forEach((sub) => { sub.update() }) } } // 在 compiler.js 中收集依赖,发送通知 // defineReactive 中 // 创建 dep 对象收集依赖 const dep = new Dep() // getter 中 // get 的过程中收集依赖 Dep.target && dep.addSub(Dep.target) // setter 中 // 当数据变化之后,发送通知 dep.notify()
Watcher
image-20210303082708782.png
-
功能
当数据变化触发依赖,dep通知所有的Watcher实例更新视图
自身实例化的时候往dep对象上添加自己
结构
image-20210303082801222.png
-
代码
class Watcher { constructor(vm, key, cb) { this.vm = vm // 保存data中的属性名 this.key = key // 回调函数 this.cb = cb // 在 Dep 的静态属性上记录当前 watcher 对象,当访问数据的时候把 watcher 添加到dep 的 subs 中 Dep.target = this // 触发一次 getter,让 dep 为当前 key 记录 watcher this.oldValue = vm[key] // 清空 target Dep.target = null } // 当数据变化时更新视图 update() { let newValue = this.vm[this.key] if (newValue === this.oldValue) { return } this.cb(newValue) } } // 在 compiler.js 中为每一个指令/插值表达式创建 watcher 对象,监视数据的变化 // 因为在 textUpdater等中要使用 this updaterFn && updaterFn.call(this, node, this.vm[key], key) // v-text 指令的更新方法 textUpdater (node, value, key) { node.textContent = value // 每一个指令中创建一个 watcher,观察数据的变化 new Watcher(this.vm, key, value => { node.textContent = value }) }
总结
-
问题
-
给属性重新赋值成对象,是否是响应式?
是,属性本身是响应式,重新赋值时会触发observer中定义的响应式数据的set方法,调用walk方法遍历对象设置为响应式数据
-
给Vue实例新增一个成员是否是响应式的?
不是,因为data中的响应式对象是在创建vue实例的时候定义的,所以直接新增成员不是响应式的
-