从设计模式理解Vue响应式(多图警告)

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"序言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"近日公司开发一个拖拽表单项目,用到了 Vue,部门老大便开始研读 Vue 源码,并且传授给我们,老大说,读源码不能仅仅只看懂源码,还得读懂他的设计思想,他为什么要这么设计,把自己当做设计者来读,这样才能真正理解,本文中,我会按照老大的指引方向,和自己的理解,来谈一谈 Vue 响应式原理,及其设计思想","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"什么是Vue响应式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"官方解释: Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新 ,简单说就是数据发生改变视图会做出相应的更新,视图发生变化,例如 input 输入,数据也会做出对应的变化。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们来看一个实例图:图片来源于阿宝哥的响应式原理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是一个Excel表格的加法表达式,我们会发现当加数发生改变,他的和就会会自动发生改变,不需要人为操作让他重新运算结果。在Vue中数据和视图的关系,就像这里的加数与和的关系。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/90/900fe7a55ad574fb412ad8b1ec09563a.gif","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么Vue响应式是如何实现的呢,想要知道的话我们就得了解一下 他的设计模式了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"响应式的设计模式","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Vue 响应式所使用的设计模式,是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"观察者模式","attrs":{}},{"type":"text","text":",观察者模式通俗的说就是。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如:郭老师每天上班带一个橙子,我想在他吃橙子的时候蹭一口,但是我又不想一直盯着郭老师看他什么时候吃,于是我和郭老师约定,你吃橙子的时候通知我。然后小郭老师看到了,也想要吃,就也和郭老师约定,郭老师吃橙子的时候也通知她,然后等郭老师吃橙子的时候,想起和他做好约定的我和小郭老师,于是发送通知,我和小郭老师收到消息,立马就做出我想要做的事情(凑过去和他一起吃)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在编程中,这样做可以省去了反复检索的资源消耗,也得到更高的反馈速度。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/37/377c2541198ea0255b586aa4cbcb0469.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"观察对象 (Subject):拥有两个必要标识,通知当前实例所拥有的观察者的方法。给当前实例添加观察者的方法,这样你才知道到时候要通知谁。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"观察者(Observer):拥有一个必要标识,通知实例更新状态的方法。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"联系方式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"观察对象 (Subject) 通过自己内部的通知函数,调用所有观察者列表中所有观察者对应的回调函数,达到通知观察者的目的。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"观察者(Observer)通过调用观察者对象(Subject)中的添加方法,把自己回调函数传入他的观察者列表中。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"响应式和观察者模式的融合","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们先来看一下实现一个基础的Vue需要哪些文件,我们以尤大的","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/yzycool/miniVue","title":null},"content":[{"type":"text","text":"miniVue","attrs":{}}]},{"type":"text","text":"作为探讨的demo。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f9bf6daa63381cf5a0b48c2d9ff2ceb1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们先来一一介绍一下这些文件分别做了什么功能;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"Vue 是事件总线文件,他会将对象描述文本中的 data 数据拿出来,然后通过 _proxyData 函数,将 data 所有数据进行劫持代理,方便后续属性的访问 。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"observer 文件,这里的 walk 方法,通过递归遍历 data 中的每一个属性。然后在 defineReactive 中给每个属性创建一个新的 Dep ,用于存储自身的依赖(观察者),Object.defineProperty 响应式的根本,如果是触发的get,就把他的 Dep.target 添加到 Dep 列表,这一步也就是收集依赖,你取了我的值说明你对我感兴趣,所以我把你添加进我的观察者俩表,如果触发set,说明数据发生改变,触发dep中的notify,通知所有观察者,有数据更新了,快行动。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"dep 文件是观察者模式中的观察对象(Subject),里面拥有一个储存观察中的容器,和添加观察者,通知观察者的方法。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"compiler 文件是一个处理文件,专门用来处理解析指令,差值表达式,等等的集合。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"watcher 文件是观察者模式中的观察者,里面有一个更新视图的方法,和调用dep中添加观察者的方法。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们看到,除了事件总线文件,和处理文件,就剩下3个文件了,这三个文件就是Vue响应式的根本了,我们看个关系图","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/16aec4ab02ebf516c6b058ad143bc4e2.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"小结","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以发现dep文件和watcher文件,就是一个观察者模式的实现,然后observe文件是他们之间的桥梁,通过劫持get和set操作,告诉dep什么时候应该添加观察者,和通知观察者,形成了自动化,当数据被读取,创造观察者和被观察者的联系,当数据改变,通知被观察者发送通知消息,如此一来就实现了响应式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Vue实例初始化","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"响应式三步走源码","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"首先是进入口文件 vue.js 。通过 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"defineProperty","attrs":{}}],"attrs":{}},{"type":"text","text":" 完成了 Data 中所有数据的代理。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Vue {\n constructor (options) {\n \n this.$options = options || {} // save options\n this.$el = typeof options.el === 'string' ? document.querySelector(options.el)\n :options.el // get dom\n this.$data = options.data // get data\n this.$methods = options.methods\n // 1.data 所有数据进行劫持代理\n this._proxyData(this.$data)\n // 2.调用observe对象,监听数据变化\n new Observer(this.$data)\n // 3.调用compiler对象,解析指令和差值表达式\n new Compiler(this)\n }\n _proxyData (data) {\n // 遍历所有data\n Object.keys(data).forEach(key => {\n // 将每一个data通过defineProperty进行劫持\n Object.defineProperty(this, key, {\n enumerable: true,\n configurable: true,\n get () {\n return data[key]\n },\n set (newValue) {\n if (data[key] === newValue) {\n return\n }\n data[key] = newValue\n }\n })\n })\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"2","normalizeStart":"2"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"然后进入 Observe.js ,遍历所有数据,如果是get则收集依赖,如果是set则发送通知","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\nclass Observer {\n constructor(data) {\n this.walk(data)\n }\n walk (data) { // 循环执行data\n if (!data || typeof data !== 'object') {\n return\n }\n Object.keys(data).forEach(key => {\n this.defineReactive(data, key, data[key])\n })\n }\n defineReactive (obj, key, val) { \n let that = this\n this.walk(val) // 如果val是对象,则给他绑定get和set时触发的方法\n let dep = new Dep() // 负责收集依赖,并发送通知\n Object.defineProperty(obj, key, {\n configurable: true,\n enumerable: true,\n get() {\n Dep.target && dep.addSub(Dep.target) // 收集依赖\n return val // 如果使用obj[key],会变成死循环\n },\n set(newValue) {\n if (newValue === val) {\n return\n }\n val = newValue\n that.walk(newValue) // 修改后可能是对象,set函数内部调用,修改了this指向\n dep.notify() // 发送通知\n }\n })\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小结:这个文件是响应式自动化的实现,使用了一个 walk 方法,通过递归遍历 data 中的每一个属性,然后在放进 defineReactive 中给每个对象创建一个新的 Dep ,用于存储自身的依赖(观察者),然后将对象的可枚举和可写属性打开,并且定义一个 set 和 get 触发时的方法。 如果是触发的 get ,就把他的 Dep.target 添加到 Dep 列表,这一步也就是收集依赖,你取了我的说明你对我感兴趣,所以我把你添加进我的观察者列表。如果触发 set ,说明数据发生改变,触发 dep 中的 notify ,通知所有观察者,有数据更新了,快行动,以此达成响应式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"3","normalizeStart":"3"},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"然后进入 watcher.js 。这里通过一个 Dep 类的静态属性 target ,用来记录当前 watcher 对象,通过使用其中的属性,触发 get ,使自身依赖添加进观察者列表,形成了观察者模式,然后复原Dep.target,方便下一个Watcher使用。在接收到观察对象的通知后,调用自身的更新方法,完成视图更新。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class Watcher {\n constructor (vm, key, cb) {\n this.vm = vm\n // data中的属性名称\n this.key = key\n // 回调函数负责更新视图\n this.cb = cb\n // 把watcher对象记录到Dep类的静态属性target\n Dep.target = this\n // 触发get方法,在get方法中会调用addSub\n this.oldValue = vm[key]\n Dep.target = null\n }\n // 当数据发生变化的时候通知视图更新\n update () {\n let newValue = this.vm[this.key]\n if (this.oldValue === newValue) {\n return\n }\n this.cb(newValue)\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们最后来看一下初始化流程图","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f4/f40c2858f73c8335d4b32b6e4951af79.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"按照箭头得知:首先初始化阶段做了三个步骤:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"调用 vue.js 中的 _proxyData 进行数据劫持。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"进入 Observe.js ,创建 Dep(观察目标),所有他拥有一个储存观察者列表的容器,和添加观察者,通知观察者的方法。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"创建 Watcher (观察者) ,他是观察者模式中的观察者,所以他拥有一个通知更新的响应方法。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小结:这三个步骤是 Vue 响应式的核心,也是观察者模式的实现,Watcher(观察者)通过触发 get ,将自身添加进观察目标的观察者列表。Dep 通过遍历自身观察者列表实现通知所有观察者,从而实现响应式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"视图渲染和视图更新","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们来看看 compiler.js 文件的主要内容","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"compile :编辑模板","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"compileElement :编译元素节点","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"compileText :编译文本节点,处理差值表达式","attrs":{}}]}],"attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// 解析 v-model\n modelUpdater (node, value, key) {\n node.value = value\n new Watcher(this.vm, key, (newValue) => { // 创建watcher对象,当数据改变更新视图\n node.value = newValue\n })\n // 双向绑定\n node.addEventListener('input', () => {\n this.vm[key] = node.value\n })\n } \n// 编译模板\n compile (el) {\n let childNodes = el.childNodes\n Array.from(childNodes).forEach(node => {\n if (this.isTextNode(node)) { // 处理文本节点\n this.compileText(node)\n } else if(this.isElementNode(node)) { // 处理元素节点\n this.compileElement(node)\n }\n // 如果还有子节点,递归调用\n if (node.childNodes && node.childNodes.length > 0) {\n this.compile(node)\n }\n })\n }\n // 编译元素节点,处理指令\n compileElement (node) {\n // console.log(node.attributes)\n if (node.attributes.length) {\n Array.from(node.attributes).forEach(attr => { // 遍历所有元素节点\n let attrName = attr.name\n if (this.isDirective(attrName)) { // 判断是否是指令\n attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2) \n // 获取 v- 后面的值\n let key = attr.value // 获取data名称\n this.update(node, key, attrName)\n }\n })\n }\n }\n // 编译文本节点,处理差值表达式\n compileText (node) {\n // 获取 {{ }} 中的值\n // console.dir(node) // console.dir => 转成对象形式\n let reg = /\\{\\{(.+?)\\}\\}/\n let value = node.textContent\n if (reg.test(value)) {\n let key = RegExp.$1.trim() // 返回匹配到的第一个字符串,去掉空格\n node.textContent = value.replace(reg, this.vm[key])\n new Watcher(this.vm, key, (newValue) => { // 创建watcher对象,当数据改变更新视图\n node.textContent = newValue\n })\n }\n }","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们再看一幅流程图","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/e7/e7a45dcecc43b606f8c58bf88e9b69e5.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小结:compile 把元素转换为数据模型,他是普通的 JavaScript 对象,我们这叫做 vnode 对象,然后遍历 vnode 对象,根据标识分为元素节点,文本节点,数据三个分类,分别进入不同的处理函数,并且创建一个 Watcher 对象,然后在 Watcher 对象中触发 get 实现响应式,同步会进行 updata 更新数据,转换成真实 dom ,完成页面渲染,更新就是如此反复。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"整体响应式运行流程图","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/10/10210c6f88f1474cf503106c0851c169.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,我们根据这张流程图进行一下知识回顾。首先是初始化三步走:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"通过 _proxyData 进行数据劫持,对 Data 进行代理,","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"创建 Dep(观察目标)对象,","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"然后创建 Watcher (观察者)对象,","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后开始渲染阶段","attrs":{}}]},{"type":"numberedlist","attrs":{"start":"","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"vm.render()触发后进行模板编译","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"complie(template)将元素编译成 vnode 对象,遍历该对象,创建 Watcher ,添加依赖完成响应式。","attrs":{}}]}],"attrs":{}},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"updata 更新数据,然后转化成真实 dom,完成渲染。","attrs":{}}]}],"attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此, Vue响应式原理及其设计模式应该很清楚啦,如有疑问欢迎留言提出。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"欢迎想要一起学习进步的朋友,加入我的学习群,大家可以在里面讨论一下进阶技巧,分享自己最新学习的内容!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"如果觉得文章不错,可以点个赞,给作者一点小小的鼓励,谢谢,可以选择加我微信好友,我来拉你们进群。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b8/b8ddcf1220eb2dacf4ec4b3a839bce84.png","alt":"微信二维码.png","title":"微信二维码.png","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章