Vue源码-各实现原理简单总结

1,双向数据绑定的实现

简单总结为 :通过js的层层封装,互相调用,实际就是利用js的Object.defineProperty()方法,然后实现了一个发布订阅模式。

整体逻辑是在vue初始化的时候,通过Object.defineProperty()重写数据的set、get方法。

在每个调用到变量的地方(例如vue的模板字符串{{ }}),会触发重写的get方法,该方法增加一个观察者;

在每次修改变量值的时候,会触发重写的set方法,该方法会通知所有的观察者更新视图。

详细源码解读可查看vue源码解析-响应式原理

2,$mount的实现

vue项目的main.js文件中最后一段代码总是为:

new Vue({ el: '#app', router, store, ... ... render: (h) => h(App)});

也可以这样写:new Vue({ router, store, ... ... render: (h) => h(App).$mount('#app') });

而$mount函数是Vue.property原型上定义的函数。

这个函数中主要代码简单总结为

首先会判断options.render,如果render为空则再获取options.template,如果template也为空则获取传入的el参数(即el),获取到模板内容之后通过处理将模板内容转换成Element类型,并返回render函数(获取render函数的过程见3,complier过程);

然后根据render函数生成虚拟DOM(具体过程见4,render函数生成虚拟DOM过程);

然后进行patch过程(新增节点、更新节点、删除节点),vue执行diff算法渲染页面;

最后调用mounted钩子。

详细源码解读可查看[vue源码解析-mount](https://juejin.cn/post/6932726705521950733 "vue源码解析-mount")

3,complier过程

简单总结:将template转换成render函数。

complier分为构建时complier和运行时complier,二者的区别是:

构建时complier是在本地开发中通过webpack + vue-loader来处理.vue文件,然后在打包的时候转换成render函数;

运行时complier是不使用vue-loader这样的插件,。直接编写template这样的模板代码,然后在浏览器运行的时候将template转换成render函数。

因此本质都是转换成render函数,对比看来构建时complier的性能更好一些。

然而vue的源码对于运行时complier进行了封装实现:在mount挂载时,如果没有render函数,则会先进行模板编译,转换成AST对象(Abstract Syntax Tree抽象语法树,实际是一种自定义的数据结构),然后通过AST转换成render函数并返回,挂载到vm.$options。

4,render函数生成虚拟DOM过程

上述complier过程,只是返回了render函数,但并没有执行,而执行了render函数之后,会生成一个虚拟DOM,也就是一个js对象。虚拟dom存在的意义就是提升性能和跨平台。

生成虚拟dom的过程简单总结为 :通过执行_render函数,来调用createElement函数,生成VNode虚拟DOM并返回。

虚拟DOM与真实DOM的区别在于:虚拟DOM只需要一些重要的属性(tag, data, children, text, elm, context, componentOptions)即可,因此虚拟DOM其实就是一个js对象。

详细源码解读可查看vue源码解析-组件化&虚拟DOM

5,patch过程和diff算法

上述执行render函数并返回虚拟DOM之后,vue会调用update方法去更新视图。而patch函数就是在update方法中进行调用(vm.$el = vm.patch(prevVnode, vnode))。

简单总结 :整个patch函数的执行过程就是以新的虚拟dom为基准,改造旧的虚拟dom(创建节点、更新节点、删除节点)。其中新老vnode对比的过程就是diff算法。

首次渲染页面时,不需要使用diff算法对比:

首次渲染时旧节点oldNode是真实节点(根节点),此时要将其转换成虚拟节点(因为后面节点的remove、invoke和diff对比都是基于虚拟DOM)并保存;然后调用createElm方法(最内层调用封装的原生的document.createElement)创建节点;若有多层组件嵌套,接着调用createChildren(实际是递归调用createElm方法)方法完成多层嵌套的子组件的节点创建。

非首次渲染页面,页面数据发生改变时,diff算法介入:

会触发reactiveSetter方法对比新老数据是否相等,如果相等则直接return;不相等则需要使用diff算法进行更新过程。

触发reactiveSetter方法时,实际是将每个观察者放入一个队列中(一次性更新,提升性能)循环调用了update方法,在最后调用nextTick进行一次性更新。

diff算法的过程,主要是对新老vnode标签、文本(updateProperties)、子节点(updateChildren)等依次进行判断和对比,若二者不一致,通常将新的vnode节点或内容替换到oldVnode节点或者内容,然后对二者都有子节点的情况,再递归对子节点进行diff的过程。整个过程可能有增删改查等操作。因此diff算法是为了可以合理的复用节点,提升性能。

详细源码解读可查看Vue源码解析-patch&diff算法

6,$nextTick的实现

简单总结:$nextTick是通过事件循环的机制,将所有的回调函数放到一个队列中(callbacks)存储,然后在下一次dom同步更新完成页面渲染之后,再执行队列中存储的回调函数,执行完成后再清空队列,便于下次使用。

这样实现的是因为同步更新dom会执行diff对比,将会非常损耗性能。

vue的$nextTick有效使用降解来实现兼容性问题(promise>MutaionObserver>SetImmediate>

setTimeout)

7,watch监听的实现

vue中watch的实现方式有4种

1,变量名: 函数名(字符串形式)

eg:watch: { message: "getMessage" } methods: { getMessage(val) { ... } }

2,变量名: 函数定义

eg:watch: { message: function (old, new) { ... ... } }

3,对象.属性(字符串形式): 函数定义

eg:watch: { "message.read": function (old, new) { ... ... } }

4,对象.属性(字符串形式): 回调函数数组

eg:watch: { "message.read": ["getMessage", function (old, new) { ... ... }] }

methods: { getMessage(val) { ... } }

简单总结

在vue初始化调用initState()的方法中调用了initWatch方法,对watch进行一系列的初始化操作;

该过程主要通过遍历的方式循环获取属性,根据不同watch的实现方式来分别判断并获取回调函数handler;

然后进入new Watcher()收集依赖和更新;收集的依赖之后调用get方法,并挂载到Dep.target上(其中字符串类型的对象属性键值如果是多层嵌套,例如"obj.a.b.c",每一层都会触发回调);而更新也会触发数据劫持set方法,执行dep.notify()方法进行后续的更新操作(基于双向绑定响应式的实现过程)。

8,computed计算属性的实现

简单总结:与watch监听属性类似,也是在Vue初始化的时候进行初始化,通过遍历的方式获取属性;

但不同点是计算属性会为每个属性创建计算属性watcher实例和和渲染属性watcher实例,且将值缓存到vm._computedWatchers中;同时计算属性在对数据set劫持的时候会先进行判断是否有dirty标记属性,如果有则需要通过他依赖的计算观察者watcher.evaluate()方法重新计算;没有则

computed要依赖data属性的数据变化返回一个值;而watch是观察数据变化执行回调函数

相关推荐
下雪天的夏风8 分钟前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript
青稞儿14 分钟前
面试题高频之token无感刷新(vue3+node.js)
vue.js·node.js
diygwcom20 分钟前
electron-updater实现electron全量版本更新
前端·javascript·electron
volodyan23 分钟前
electron react离线使用monaco-editor
javascript·react.js·electron
^^为欢几何^^32 分钟前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
Hello-Mr.Wang36 分钟前
vue3中开发引导页的方法
开发语言·前端·javascript
程序员凡尘1 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七4 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫5 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy5 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript