本文章转自:乐字节 文章主要讲解:Vue实现原理 **获取更多VUE相关资料及项目可以关注公众号《乐字节》 发送:999** ## 1、Vue简介 现在的大前端时代,是一个动荡纷争的时代,江湖中已经分成了很多门派,主要以Vue,React还有Angular为首,形成前端框架三足鼎立的局势。Vue在前端框架中的地位就像曾经的jQuery,由于其简单易懂、开发效率高,已经成为了前端工程师必不可少的技能之一。 Vue是一种渐进式JavaScript框架,完美融合了第三方插件和UI组件库,它和jQuery最大的区别在于,Vue无需开发人员直接操作DOM节点,就可以改变页面渲染内容,在应用开发者具有一定的HTML、CSS、JavaScript的基础上,能够快速上手,开发出优雅、简洁的应用程序模块。 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4e0094ff7d8746c8ba21ea11bfb3fc19~tplv-k3u1fbpfcp-watermark.image) 但是我们提及Vue的时候,更多的是关注它的用法,而不是学习它是如何解决前端问题的,这多少有点亚健康。有前端开发经验的人,一定在开发过程中遇到过奇奇怪怪的问题,然后稀里糊涂地解决,倘若再次遇到相似的问题,便再次手足无措,作为一名前端工程师,在遇到问题的时候我们是否能准确定位产生问题的原因并及时解决,主要取决于我们对前端框架的理解是否足够深入。 ## 2、Vue实现原理 ### 2.1 虚拟DOM(Virtual DOM) 随着时代的发展,Web应用的页面交互效果越来越复杂,页面功能越来越丰富,需要维护的状态越来越多,DOM操作也越来越频繁。DOM操作虽然简单易用,但是会产生不好维护的问题。 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/731c4e3af7104bd7addac00fd7c571b6~tplv-k3u1fbpfcp-watermark.image) 在程序执行的过程中,Watcher初始化时会将每一个节点和状态进行一一关联和映射,setter监听到Data的状态发生改变后,就会通知Watcher,Watcher会将这些变化通知曾经记录过的DOM以及跟这些状态相关的节点,从而触发页面的渲染过程。组件接收到状态变化后,会通过编译将模板转换成渲染函数Render,执行渲染函数就会得到一个虚拟DOM树,通过对比旧的虚拟DOM和新生成的虚拟DOM树,来更新对应的实际DOM节点,执行页面渲染。 ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e085218bb4cf4c4cad6f1af90c20aa34~tplv-k3u1fbpfcp-watermark.image) 主流前端框架几乎都在使用虚拟DOM,但是在使用虚拟DOM的时候,Angular和React都无法确定具体是哪个状态发生了变化,因此需要在旧的虚拟DOM和新的虚拟DOM之间进行暴力对比,但Vue从1.0版本开始,就通过细粒度的绑定来更新视图,也就是说,当状态发生变化的时候Vue可以知道具体是哪个状态哪些节点需要发生改变,从而对这个节点执行更新,然而这种细粒度的变化侦测会有一些内存开销影响性能,一个项目越复杂,开销就越大。 Vue从2.0版本后,为了优化性能,引入了虚拟DOM,选择了一个折中的方案,既不需要暴力对比整个新旧虚拟DOM,也不需要通过细粒度的绑定来实现视图的更新,即以组件为单位进行Watcher监听,也就是说即便一个组件内有多个节点使用了某个状态,也只需一个Watcher来监听这个状态的变化,当这个状态发生变化时,Watcher通知组件,组件内部通过虚拟DOM的方式去进行节点的对比和重新渲染。 ### 2.2 常用指令实现原理 指令是指Vue提供的以“v-”前缀的特性,当指令中表达式的内容发生变化时,会连带影响DOM内容发生变化。Vue.directive全局API可以创建自定义指令,并获取全局指令,除了自定义指令,Vue还内置了一些开发过程中常用的指令,如v-if、v-for等。在Vue模板解析时,会将指令解析到AST,使用AST生成字符串的过程中实现指令的功能。 ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c99aa7aa9ba14f04aaf2ef6a3a62df0f~tplv-k3u1fbpfcp-watermark.image) 在解析模板时,会将节点上的指令解析出来并添加到AST的directives属性中,directives将数据发送到VNode中,在虚拟DOM进行页面渲染时,会触发某些钩子函数,当钩子函数被触发后,就说明指令已生效。 ### 2.2.1 v-if指令原理 在应用程序中使用v-if指令: ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/909809f6be354a5e806323eb4f3bdc1a~tplv-k3u1fbpfcp-watermark.image) 在编译阶段生成: ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1488c41da4034f08a842e1b80941dc5c~tplv-k3u1fbpfcp-watermark.image) 在代码执行时,会根据create的值来选择创建哪个节点。 ### 2.2.2 v-for指令原理 在应用程序中使用v-for指令: ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/18c5fe1a587d46bd8c2ed142dd33d5c1~tplv-k3u1fbpfcp-watermark.image) 在编译阶段生成: ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/dc50f23c56dd4e81a249d21353c71a47~tplv-k3u1fbpfcp-watermark.image) _l是renderList的别名,执行代码时,_l函数会循环list变量,调用第二个参数中传递的函数,传递两个参数:item和index,当_c函数被调用时,会执行_v函数,创建一个节点。 ### 2.2.3 自定义指令原理 在应用程序中,指令的处理逻辑分别监听了create函数、update函数以及destory函数,具体实现如下: ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f5ea92bf32bb4c8882ed861e089decd8~tplv-k3u1fbpfcp-watermark.image) 钩子函数被触发后,会执行updateDirectives函数,代码如下: ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9b3906e485f843e296e90632c63b9dfc~tplv-k3u1fbpfcp-watermark.image) 在该函数中,不论是否存在旧虚拟节点,只要其中存在directives,就会执行_update函数,_update函数代码如下: ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4e4e17592296483fa29b9029651153bc~tplv-k3u1fbpfcp-watermark.image) ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/966ef82ed00348b3889cd29666b9c1ff~tplv-k3u1fbpfcp-watermark.image) isCreate:判断该虚拟节点是否是一个新建的节点。 isDistory:判断是否删除一个旧虚拟节点。 oldDirs:旧的指令集合,oldVnode中保存的指令。 newDirs:新的指令集合,vnode中保存的指令。 dirsWithInsert:触发inserted指令钩子函数的指令列表。 dirsWithPostpatch:触发componentUpdated钩子函数的指令列表。 通过normalizeDirectives函数将模板中使用的指令从用户注册的自定义指令集合中取出来的结果如下: ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a009543e4eb1498ca33a6854f6831dd4~tplv-k3u1fbpfcp-watermark.image) 自定义指令的代码为: ![image.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/930f9b6a8f764c14a68ffcc77a830620~tplv-k3u1fbpfcp-watermark.image) 虚拟DOM在对比和渲染时,会根据不同情景触发不同的钩子函数,当使用虚拟节点创建一个新的实际节点时,会触发create钩子函数,当一个DOM节点插入到父节点时,会触发insert钩子函数。 callHook函数执行钩子函数的方式如下: ![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4f5d91a989624fa9bc2052d1869859d3~tplv-k3u1fbpfcp-watermark.image) callHook函数的参数意义分别为: dir:指令对象。 hook:将要触发的钩子函数名。 vnode:新的虚拟节点。 oldVnode:旧的虚拟节点。 isDestory:判断是否删除一个旧虚拟节点。 虚拟DOM在渲染时会触发的所有钩子函数及其触发机制如下: ![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2ac960c6f9654de2af61057c84a7f30d~tplv-k3u1fbpfcp-watermark.image) 需要注意的是,remove函数是只有一个元素从其父元素中移除时才会触发,如果该元素是被移除元素的子元素,则不会触发remove函数。 **感谢大家的认同与支持,小编会持续转发《乐字节》优质文章**