🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[项目地址](https://github.com/fujiazhang/vue) [TOC] 在开始之前需要有一些基本的认识 # 数据驱动 Vue.js 一个核心思想是数据驱动。所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。 在 Vue.js 中我们可以采用简洁的模板语法来声明式的将数据渲染为 DOM: ~~~ <div id="app"> {{ message }} </div> ~~~ ~~~ var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }) ~~~ 最终它会在页面上渲染出`Hello Vue`。 # vue2响应式核心原理 直接上代码了,vue2 defineProperty api的实现基于如下代码: ![](https://img.kancloud.cn/f5/c1/f5c162fa9a0f785f906942012f893950_1322x1130.png) # vue3响应式核心原理 直接上代码了,proxy: ![](https://img.kancloud.cn/31/31/3131c693d31e1aec433ffc75afd15810_1462x1152.png) 可以看到我们proxy的实现要简洁一些,另外性能有浏览器优化,性能要比defineProperty好。 # 发布订阅模式 ## 发布订阅和观察者模式 这两种模式在vue中有各自的运用场景,两种模式本质是相同的,但是是有区别的,他们经常被混为一谈。 在vue中的自定义事件 就是一个典型的发布订阅模式,比如下面截图的代码, ![](https://img.kancloud.cn/fe/c3/fec3fe74148a949d5c00b615e6a6698a_792x420.png) 又比如我们兄弟组件传值可以用eventBus: ![](https://img.kancloud.cn/c4/1e/c41eb77e8eb93497c5989dcafdccad78_1420x798.png) ### 发布者订阅者模式 ![](https://img.kancloud.cn/cb/ee/cbeec9f3f0846f23d96d87495a0ba7e8_1512x1104.png) ### 观察者模式 ![](https://img.kancloud.cn/be/93/be937280d434f6f26fbc2d17a1f86187_1286x1048.png) ## 关于两种模式的总结 **观察者模式**:观察者模式是有具体目标调度,比如事件触发,Dep就会去调用观察者的方法,所以观察者和发布者之间存在依赖(强耦合) **发布订阅模式**:发布订阅模式由调度中心调用,因此发布者和订阅者不需要知道对方存在。 ![](https://img.kancloud.cn/7e/ef/7eef45585bc20349cae4172bb2f207ae_1002x706.png) (图片源自互联网) # 正餐开始 乞丐最小版本vue整体分析如下 ![](https://img.kancloud.cn/87/33/8733cea9fa519eadd9de5cb0808dad43_756x460.png) vue: 将data中的成员注入到vue实例,并把data中的成员专程getter/setter observer: 能够将数据对象所有属性监听,变化通知Dep。 ## Vue模块 * 负责接收初始化参数 * 负责把data中的属性转换成getter/setter * 负责调用observer监听data中所有属性的变化 * 负责调用compiler解析指令/差值表达式 代码实现: ![](https://img.kancloud.cn/20/d5/20d59091baa63921a8f597889992296a_1532x1180.png) ## Observer模块 * 负责把data选项中的属性转换成响应式数据 * data中的某个属性也是对象,把该属性转换成响应式数据 * 数据变化发送通知 ![](https://img.kancloud.cn/f3/c3/f3c394cf2aa1218ffc5dded7f9308909_1280x1090.png) ![](https://img.kancloud.cn/cf/76/cf766a9b25e0ae10141270147a014f0f_1278x980.png) ## Compiler * 负责编译模版,解析指令/差值表达式 * 负责页面的首次渲染 * 当数据变化后 重新渲染视图 代码如下: ``` /** * @description: * 1.负责编译模版,解析指令/差值表达式 * 2.负责页面的首次渲染 * 3.当数据变化后重新渲染视图 * 未引入虚拟dom直接操作的实dom */ class Compiler { constructor(vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } /** * @description: 编译模版,处理文本和元素节点 */ compile(el) { let childNodes = el.childNodes Array.from(childNodes).forEach(node = >{ if (this.isTextNode(node)) { this.compileText(node) } else if (this.isElement(node)) { this.compileElement(node) } //判断下是否还有字节点 if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } /** * @description: 变异元素节点 处理指令 */ compileElement(node) { // console.log(node.attributes) Array.from(node.attributes).forEach(attr = >{ let attrName = attr.name if (this.isDirective(attrName)) { attrName = attrName.substr(2) let key = attr.value this.update(node, key, attrName) } }) } update(node, key, attrName) { let updateFn = this[(attrName + 'Update')] updateFn && updateFn.call(this, node, this.vm[key], key) } /** * @description: 处理v-text */ textUpdate(node, value, key) { node.textContent = value //创建watcher对象 数据改变更新试图 new Watcher(this.vm, key, (newValue) = >{ node.textContent = newValue }) } /** * @description: 处理v-model */ modelUpdate(node, value, key) { node.value = value //创建watcher对象 数据改变更新试图 new Watcher(this.vm, key, (newValue) = >{ node.value = newValue }) // 双向绑定 node.addEventListener('input', () = >{ this.vm[key] = node.value }) } /** * @description: 编译文本节点,处理差值表达式 */ compileText(node) { let reg = /\{\{(.+?)\}\}/let value = node.textContent if (reg.test(value)) { let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) //创建watcher对象 数据改变更新试图 new Watcher(this.vm, key, (newValue) = >{ node.textContent = newValue }) } } /** * @description: 判断元素属性是否指令 */ isDirective(attrName) { return attrName.startsWith('v-') } /** * @description: 判断节点是否是文本节点 */ isTextNode(node) { return node.nodeType === 3 //node接点属性3为本文 1为元素 } /** * @description: 判断节点是否是元素节点 */ isElement(node) { return node.nodeType === 1 //node接点属性3为本文 1为元素 } } ``` ## Dep * 收集依赖、添加观察者(watcher * 通知所有的观察者 ![](https://img.kancloud.cn/db/ca/dbca6b1d95b97d4abef6054d7cd7a432_982x316.png) 代码如下: ![](https://img.kancloud.cn/79/e7/79e7d47926cbfec15c5963dc221f69f3_1096x920.png) ## watcher * 当数据变化出发依赖,dep通知所有的watcher实例更新视图 * 自身实例化的时候往dep中添加自己 ![](https://img.kancloud.cn/1a/ad/1aadc46c9140ba79c17e240ed23d71b1_1040x326.png) ![](https://img.kancloud.cn/2f/ca/2fca88eb1adad4af83b394a8ba639ee7_996x1054.png) ## 双向绑定 * 数据变化 改变视图 * 视图变化 改变数据 ![](https://img.kancloud.cn/78/f9/78f94cdf62c003a88ac96810c519935c_1212x634.png)