FinClip 前端之 VUE 核心原理总结
小程序框架有很多,都是支持前端JavaScript语言的,也是支持 vue.js 框架的。FinClip 小程序是兼容各家平台的。所以在学习了框架使用之后的进阶就要熟悉框架的底层原理。
1、数据响应式
- 首先判断数据的类型,如果是基础数据类型,直接返回,如果已经有 ob 属性,表示已经是响应式的数据了,直接返回该数据。如果是对象就走第2步,如果是数组就走第3步
- 对象是通过 Object.defineProperty,在 getter 里收集依赖,在 setter 里触发更新
- 数组是首先拷贝数组的原型,然后基于拷贝的原型改写(push,pop,unshift,shift,sort,reverse,splice)七个可以改变数组长度的方法,然后将改写后的原型赋给数组的隐式原型
- 对数组的隐式原型赋值后,还要观测数组的每一项,重复第一步
- 如果 Object.defineProperty 的 setter 里赋值,如果新赋的值是对象,也要进行观测
- 如果对数组的操作是有数据新增(push,unshift,splice),还需要观测数组新增的每一项,同第4步(这里Vue源码的实现是给每个响应式数据[对象和数组]新增了一个不可枚举的属性 ob,它的作用有三,其一是用来判断数据是否已经是响应式的数据,如果是就不需再次观测,其二是属性 ob 是 Observer 类的一个实例,实例上有对数组每一项进行响应式处理的方法),其三是 $set 方法中,ob 用来判断要设置属性的对象是不是响应式的对象,如果它本身就不是响应式对象,则该属性无需定义为响应式的属性
- 如果 data 的层级过深会影响性能
- 对象有新增和删除属性没办法做数据的响应式处理(通过$set解决)
- 如果给对象的属性赋值为对象,也会对赋值后的对象进行响应式处理
- 在重写数组原型之前,Vue 给每个响应式数据新增了一个不可枚举的 ob 属性,这个属性指向了 Observer 实例,可以用来防止已经被响应式处理的数据反复被响应式处理,其次,响应式的数据可以通过 ob 获取到 Observer 实例的相关方法
- 对于数组的新增操作(push/unshift/splice),会对新增的数据也做响应式处理
- 通过索引修改数组内容和直接修改数组长度是观测不到的
2、Vue如何进行依赖收集的?
- 每个属性都有 dep 实例,dep 实例用来收集它所依赖的 watcher
- 在模板编译的时候,会取值触发依赖的收集
- 当属性发生变化时会触发 watcher 更新
3、Vue的更新粒度是组件级?
- 首先渲染 watcher 是组件级的。在初始化的时候,会调用 _init 方法,_init 内部会调用 $mount 方法,$mount 方法会调用 mountComponent 方法,mountComponent 方法内部定义了 updateComponent 方法,updateComponent 方法内部就是调用 _update 方法将 vnode 渲染成真实 DOM,mountComponent 方法会 new 一个渲染 watcher,并把 updateComponent 传给渲染 watcher ,所以渲染 watcher 可以重新渲染DOM(试想一下,如果我们没有把更新DOM渲染的方法传递给 watcher ,更改数据后,我们需要手动去调用DOM渲染的方法;传递给 watcher 后,数据变化后,可以让 watcher 自动的去调用更新DOM渲染的方法)
- 在 render 函数生成 vnode 时,会判断是否是原生的HTML标签,如果不是原生HEML标签即是 组件,会创建组件的 vnode,子组件本质是 VueComponent 函数,VueComponent 内部会调用 _init 方法,所以创建子组件 vnode 的时候,也会 new 一个渲染 watcher,所以说渲染 watcher 是组件级的,也就是说 Vue 的更新粒度是组件级的
4、模板编译原理
- 注意一:我们平时开发中使用的是不带编译的 Vue 版本(runtime-only),所以在传入选项的时候是不能使用 template 的
- 注意二:我们 .vue 文件中的 template 是经过 vue-loader 处理的,vue-loader 其实也是使用 vue-template-compiler 处理的
- 如果选项 options 里有 render 直接使用 render,如果没有 render 看选项里有没有 tempalte,如果有就用 template,如果没有就看选项里有没有 el,如果有 template = document.querySelector(el),最后用 compileToFunctions(tempalte) 生成render
- 最终都是生成 render 函数,优先级是 render > tempalte > el
- 模板编译的整体逻辑主要分为三个部分: 第一步:将模板字符串转换成 element ASTs (解析器) 第二步:对 AST 进行静态节点标记,主要用来做虚拟 DOM 的渲染优化 (优化器)(进行新旧vnode对比的时候可以跳过静态节点) 第三步:使用 elements ASTs 生成 render 函数代码字符串 (代码生成器)
- 其实就是 while 循环里不断的通过正则匹配字符串,如果是匹配到是开始标签,就触发 start 钩子处理开始标签和属性,如果匹配到文本,就触发 chars 钩子处理文本,如果匹配到结束标签,就调用 end 钩子处理结束标签。处理完后就把模板中已经匹配到子串截取出来,一直这样循环操作,直到模板的字符串被截取成空串跳出 while 循环。
- 在匹配到开始标签后,就把开始标签压入栈中,匹配到结束标签就把栈顶元素出栈。第一个进栈的元素就是根节点,除了第一根元素外,其他元素在进栈之前,栈顶的元素就是该元素的父亲节点,所以可以维护元素之间的父子关系(入栈元素的parent是栈顶元素,该入栈元素是栈顶元素的儿子),当栈被清空之后,根节点就是生成的 AST 匹配到文本内容是没有子节点的,所以它直接作为栈顶元素的儿子即可。
- AST 是用 JS 中的对象来描述节点,一个对象代表一个节点,对象的属性用来保存节点所需的各种数据。
- 解析器内部分了好几个子解析器,比如 HTML解析器,文本解析器,过滤器解析器。其中最主要的是 HTML解析器,HTML解析器的作用就是解析HTML,它在解析的过程中会不断的触发各种钩子函数。这些钩子函数包括,开始标签钩子函数(start)、结束标签钩子函数(end),文本钩子函数(chars)和注释钩子函数(comment)。
- 实际上,模板解析的过程就是不断的调用钩子函数的过程,读取 template,使用不同的正则表达式匹配到不同的内容,然后触发对应的钩子函数处理匹配到的字符串截取片段。比如比配到开始标签,触发 start 钩子函数,start 钩子函数处理匹配到开始标签片段,生成一个标签节点添加到抽象语法树上。
- HTML解析器解析HTML的过程就是循环(while循环)的过程,简单来说就是利用 HTML 模板字符串来循环,每轮循环都从 HTML字符串中截取一小段字符串,重复以上过程,一直到 HTML字符串被截取成一个空串结束循环,解析完毕。
- 在解析开始标签和结束标签是用栈来维护的,解析到开始标签就压入栈中,解析到结束标签,就从栈顶取出对应的开始标签的AST,栈顶的前一个开始标签就是该标签的父元素,然后就可以建立父子元素之间的关系。
- 文本解析器是对 HTML 解析器解析出来的文本进行二次加工。文本分为两种类型,一种是纯文本,一种是带变量的文本。HTML解析器在解析文本的时候,并不会区分是纯文本还是带变量的文本,如果是纯文本,不需要进行任何处理,带变量的文本需要文本解析器的进一步解析,因为带变量的文本在使用虚拟DOM进行渲染时,需要将变量替换成变量中的值。
- 文本解析器通过正则匹配出变量,把变量改写成 _s(x)的形式添加到数组中
- 首先是生成 render 函数
- vm._render 函数生成虚拟DOM render 函数主要返回了这样的代码 _c('div'{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world")))),所以需要定义 _c, _v, _s 这样的函数才能真正转换成虚拟DOM
- vm._update 方法将生成的虚拟DOM进行实例挂载 update 方法的核心是利用 patch 方法来渲染和更新视图,这里是初次渲染,patch 方法的第一个参数是真实DOM,更新阶段第一个参数是 oldVnode
5、Vue.mixin 的使用场景和原理
- Vue.mixin的作用就是抽离公共的业务逻辑,原理类似 “对象的继承”,当组件初始化的时候会调用 mergeOptions 方法进行合并,对于不同的 key(data,hooks,components...)有不同的合并策略。如果混入的数据和组件本身的数据有冲突,会采用“就近原则”,以组件本身的为准。
- mixin有很多的缺陷:命名冲突,来源不清晰,依赖问题
6、nextTick 在哪里使用?原理是什么?
- nextTick 可用于获取更新后的 DOM
- Vue的数据更新是异步的,会把所有的数据更新操作都放入任务队列中,然后在 nextTick 中去依次执行这些任务,nextTick 是一个异步任务,采用的是优雅降级(Promise -> MutationObserver -> setImmediate -> setTimeout)
7、watch 原理
- watch 的使用方式,可以是对象,可以是函数,也可以是数组
- 不论是哪种使用方式,watch 的每一个属性对应的函数(数组的使用方式,数组中的每一项(函数))都是一个 用户watcher,其实现都是调用的 $watch(vm, handler)
- $watch 方法的实现都是 new Watcher(),只不过是 options 参数里标记了是用户自定义的 watcher(options.user = true)
- watch 的属性对应的函数里有新值和旧值,我们是如何返回新值和旧值的呢?
- new Watcher() 的时候传递的是属性的 key,我们要把它包装成一个函数(函数内部就是根据 key 取值),赋值给 Watcher类的 getter 属性,在 Watcher 类实例化的时候,会调用一次 get 方法,我们就可以拿到它的值(取值同时会进行依赖收集)
- 在值更新后,会再次调用 Watcher 类的 get 方法获得新值
- 然后判断 watcher 的类型,如果是用户 watcher ,执行 callback,把新值旧值传递给 callback
- watch api 不管是哪种使用方式,最终都是一个 key, 一个函数,对应一个 user watcher ,每一个 watcher 都有一个 getter 方法,watch api 对应的 getter 方法是根据 key 来封装的,getter 方法就是取 key 对应的数据,因为 watcher 在初始化的时候默认会调用一次 getter ,所以就拿到 key 对应的旧值了,取值也就进行了依赖收集,当 key 对应的数据改变了,watcher 的 getter 方法会再次执行,这时就拿到了新值,然后调用 key 对应的回调函数,将新值和旧值传给它
8、computed 原理
- 每个计算属性本质上也是一个用户 watcher,在它取值的时候进行依赖收集,computed 依赖的值改变后触发更新
- 计算属性的 watcher 在初始化的时候会有两个属性 lazy 和 dirty
- watcher 在初始化的时候,会默认调用一次 get 方法,但是 computed 默认是不执行的,所以用 lazy 属性来标记是 computed watcher
- computed 是有缓存的,即依赖的值没有发生改变,多次获取,是不会多次调用 watcher 的 get 方法获取值的,所以用 dirty 属性来标记是否需要重新计算值,如果不需要计算,直接返回 watcher 的 value,如果需要计算,再来调用 get 方法获取新的值,再返回 watcher 的 value 补充:什么时候 dirty 的值是 true 呢?
- computed watcher 初始化的时候
- computed watcher 依赖的值改变时(调用了 computed watcher 的 update 方法,即可表示依赖的值改变了)
9、diff算法
- 首先比对是否是相同的节点,如果不是删除旧的DOM,生成新的DOM插入
- 如果是相同的节点,比对更新属性
- 判断是否是文本节点,如果是,判断文本内容是否相同,不同更新文本内容
- 比对新旧子节点,如果只有新的有子节点,新增子节点插入;如果只有旧的有子节点,将元素的 innerHTML 置为空
- 如果新旧都有子节点,比对新旧子节点(采用双指针)
- 依次是头头、尾尾、头尾、尾头比较,没有匹配到,就乱序比对
- 乱序比对:建立旧的节点的映射表(key->index)
- 新的起始节点是否能在旧的映射表中找到,不能找到直接在旧的前面插入,如果找到,将映射表找到的旧的节点,移动到前面,并将该位置置为null
- 因为在乱序比对中,有将旧节点置为 null 的情况,所以在进行子节点比对前,先判断该节点是否为null,为null顺移
- 比对完之后如果新的节点还有,插入新的节点(插入的位置要判断是否在哪里插入),如果旧的节点还有,删除旧的节点(null的位置跳过)
FinClip 前端之 VUE 核心原理总结的更多相关文章
- myvue 模拟vue核心原理
// js部分index.js class Myvue{ constructor(options){ this.data = options.data; this.dep = new Dep(); v ...
- 「进阶篇」Vue Router 核心原理解析
前言 此篇为进阶篇,希望读者有 Vue.js,Vue Router 的使用经验,并对 Vue.js 核心原理有简单了解: 不会大篇幅手撕源码,会贴最核心的源码,对应的官方仓库源码地址会放到超上,可以配 ...
- SPA 路由三部曲之核心原理
为了配合单页面 Web 应用快速发展的节奏,近几年,各类前端组件化技术栈层出不穷.通过不断的版本迭代 React.Vue 脱颖而出,成为当下最受欢迎的两大技术栈. 仅 7 个月的时间,两个技术栈的下载 ...
- 《大型网站技术架构:核心原理与案例分析》【PDF】下载
<大型网站技术架构:核心原理与案例分析>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062557 内容简介 本书通过梳理大型网站 ...
- 0基础菜鸟学前端之Vue.js
简介:0基础前端菜鸟,啃了将近半月前端VUE框架,对前端知识有了初步的了解.下面总结一下这段时间的学习心得. 文章结构 前端基础 Vue.js简介 Vue.js常用指令 Vue.js组件 Vue.js ...
- vue路由原理剖析
单页面应用(SPA)的核心之一是: 更新视图而不重新请求页面, 实现这一点主要是两种方式: 1.Hash: 通过改变hash值 2.History: 利用history对象新特性(详情可出门左拐见: ...
- 可能是目前最完整的前端框架 Vue.js 全面介绍
Vue.js 是一个JavaScriptMVVM库,是一套构建用户界面的渐进式框架. 摘要 2016年最火的前端框架当属Vue.js了,很多使用过vue的程序员这样评价它,“vue.js兼具angul ...
- 【前端】Vue.js经典开源项目汇总
Vue.js经典开源项目汇总 原文链接:http://www.cnblogs.com/huyong/p/6517949.html Vue是什么? Vue.js(读音 /vjuː/, 类似于 view) ...
- vue运行原理
Vue工作原理小结 本文能帮你做什么? 1.了解vue的双向数据绑定原理以及核心代码模块 2.缓解好奇心的同时了解如何实现双向绑定 为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简 ...
随机推荐
- Spark学习摘记 —— RDD转化操作API归纳
本文参考 在阅读了<Spark快速大数据分析>动物书后,大概了解到了spark常用的api,不过书中并没有给予所有api具体的示例,而且现在spark的最新版本已经上升到了2.4.5,动物 ...
- A Beginner’s Introduction to CSS Animation中文版
现在越来越多的网站正在使用动画,无论是以GIF,SVG,WebGL,背景视频等形式. 当正确使用时,网络上的动画带来生机和交互性,为用户增添了额外的反馈和体验. 在本教程中,我将向您介绍CSS动画; ...
- 利用AudioContext来实现网易云音乐的鲸鱼音效
一直觉得网易云音乐的用户体验是很不错的,很早就注意到了里面的鲸鱼音效,如下图,就是一个环形的跟着音乐节拍跳动的特效. gif动图可能效果不太理想,可以直接在手机上体验 身为前端凭着本能的好奇心和探索心 ...
- Java/C++实现模板方法模式---数据库操作
对数据库的操作一般包括连接.打开.使用.关闭等步骤,在数据库操作模板类中我们定义了connDB().openDB().useDB().closeDB()四个方法分别对应这四个步骤.对于不同类型的数据库 ...
- leetcode1753. 移除石子的最大得分
题目描述: 你正在玩一个单人游戏,面前放置着大小分别为 a.b 和 c 的 三堆 石子. 每回合你都要从两个 不同的非空堆 中取出一颗石子,并在得分上加 1 分.当存在 两个或 ...
- win10设置开机自启动程序
问题情境:前两天刚刚给自己的win10系统美化了一下,但发现一个问题,每次开机都需要双击启动一个程序,才能达到一个我想要的效果,所以就在思考能不能将这个程序设为开机自启动项呢? 1.首先,找到启动文件 ...
- nginx负载均衡的五种方式
文章目录 前言 :负载均衡是什么 一.方式1:轮询 二.方式2:权重 方式3:iphash 方式4:最小连接 方式5:fair 总结:根据这几种方式可以猜测处nginx的底层使用了计数器,从而可以将海 ...
- SpringMVC快速使用——基于XML配置和Servlet3.0
SpringMVC快速使用--基于XML配置和Servlet3.0 1.官方文档 https://docs.spring.io/spring-framework/docs/5.2.8.RELEASE/ ...
- Java学习day11
如果程序出现了问题,我们没有做任何处理,JVM会做默认处理,即:把异常的名称,原因和位置等信息输出在控制台,程序停止执行 一个简单的检测集合对象是否含有某元素,有就再添加一个某元素 public cl ...
- Nuxt.js(二、解决首屏速度与SEO)
Nuxt.js(二.解决首屏速度与SEO) 1.普通的Vue SPA初始化加载速度很慢 在传统的web应用中,当用户请求一个页面时,服务器直接返回一个html文件,浏览器直接渲染出来.但是,在vue应 ...