关于Vue的一次自我面试
- 关于Vue的一次自我面试
- 一、介绍一下Vue的响应式系统
- 二、computed与watch的区别
- 三、介绍一下Vue的生命周期函数
- 四、为什么组建的data必须是一个函数
- 五、组件之间是如何通信的
- 六、Vue事件绑定的原理
- 七、slot是什么?有什么作用?原理是什么?
- 八、Vue模板渲染的原理是什么?
- 九、template预编译是什么?
- 十、template与jsx有什么区别?
- 十一、说一下什么是Virtual DOM?
- 十二、介绍一下Vue中的Diff算法
- 十三、key属性的作用是什么?
- 十四、Vue2.0与3.0的区别
- 十五、新增加的Composition API ,它可以解决什么问题?
- 十六、Composition API与React Hook很像,说说区别
- 十七、SSR有了解吗?原理是什么?
关于Vue的一次自我面试
一、介绍一下Vue的响应式系统
-
Vue为MVVM框架,当数据模型data发生变化时,页面的视图会得到响应更新,其原理是对data的getter/setter方法进行拦截(Object.defineProperty或者Proxy),利用发布订阅的设计模式,在getter方法中发布订阅,在setter方法中发布通知,让所有订阅者完成响应。
-
在响应式系统中,Vue会为数据模型data的每一个属性新建一个订阅中心作为发布者,而监听器watch、计算属性computed、视图渲染template/render三个角色同时作为订阅者,对于watch,会直接订阅观察监听的属性,对于computed和template/render如果内部执行获取了data的某个属性,就会执行该属性的getter方法,然后自动完成该属性的订阅,当属性被修改时,就会执行该属性的setter放啊,从而完成该属性的发布通知,通知所有订阅者进行更新。
二、computed与watch的区别
计算属性computed和监听器watch都可以观察属性的变化从而做出响应,不同的是:
- 计算属性更多的是作为缓存功能的观察者,它可以将一个或多个data属性进行复杂的计算,生成一个新的值,提供给渲染函数使用,当依赖属性变化时,computed不会立即重新计算生成新的值,而是先标记成脏数据,当下次computed被获取的时候,才会进行重新计算并返回。
- 而监听器watch并不具备缓存性,它提供了一个监听函数,当监听的属性发生变化时,会立即执行该函数。
三、介绍一下Vue的生命周期函数
beforeCreate
:是new Vue()
之后出发的第一个钩子函数,在这个阶段,data、methods、computed以及watch上的数据和方法都不可以访问。created
:在实例创建完成之后发生,当前阶段已经完成了数据的观测,也就是可以使用数据,更改数据,在这里更改数据不会出发updated函数,可以做一些初始数据的获取,在该阶段无法与Dom就行交互,如果非要交互,可以通过vmm.$nextTick
来访问dom。beforeMount
:发生在模板挂载之前,在这之前template模板已导入渲染函数编译,而当前阶段虚拟Dom已经创建完成,即将开始渲染,在此时可以进行数据的更改,不会出发updatedmounted
:在模板挂在完成后发生,在这个阶段,真实的Dom已经挂载完毕,数据完成双向绑定,可以访问到Dom的节点,使用$refs
属性对Dom进行操作。beforeUpdate
:发生在更新之前,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重新渲染。updated
:发生在数据更新完成之后,在当前阶段组件dom已完成更新,要注意避免的是 在此期间更改数据,因为这可能会导致无限循环更新。beforeDestroy
:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行收尾工作,比如清除定时器等。destroyed
:发生在实例销毁之后,这个时候只剩下了dom空壳,组件已经被拆解,数据绑定被卸除,监听被移除,子实例也统统被销毁。
四、为什么组建的data必须是一个函数
一个组件可能会在很多地方使用,也就是会创建很多个实例,如果data是一个对象的话,对象是引用类型,一个实例修改了data,就会影响到其他实例,所以data必须是一个函数,为每一个实例创建自己的data,使其同一个组件的不同实例互不影响。
五、组件之间是如何通信的
-
父子组件通信
父传子:子组件需要用到props,来声明出对应的属性用来接收父组件传递过来的数据
子传父:通过发射
$emit
事件、$on
获取组件实例:使用
$parent / $children / $refs.xxx
,获取到实例后直接获取属性数据或者调用组件方法 -
兄弟组件通信
Event Bus:每一个Vue实例都是一个Event Bus,都支持
$on,$emit
,可以在兄弟组件的实例之间new一个Vue实例,作为Event Bus进行通信.vuex:将状态和方法提取到vuex中,完成共享
-
跨级组件通信
使用provide/inject
Event Bus:同兄弟组件Event Bus通信
Vuex:将状态和方法提取到vuex中,完成共享
六、Vue事件绑定的原理
每一个实例都是一个Event Bus,当子组件被创建的时候,父组件将事件传递给子组件,子组件初始化的时候是有$on
方法将事件注册到内部,在需要的时候使用$emit
触发函数。而对与原生的native事件,使用addEventListener绑定到真实的DOM元素上。
七、slot是什么?有什么作用?原理是什么?
slot又名为插槽,是Vue内容的分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及如何显示是由父组件决定的。
slot又分为3类:默认插槽、具名插槽、作用域插槽
- 默认插槽:当slot没有指定name属性值的时候是一个默认插槽,一个组件内只有一个匿名插槽
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是 在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件传递过来的数据决定如何渲染该插槽
实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot
中,默认插槽为vm.$slot.default
,具名插槽为vm.$slot.xxx
,xxx为插槽名 ,当组件执行渲染函数时候,遇到slot标签,使用$slot
中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
八、Vue模板渲染的原理是什么?
Vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的html语法,所以需要将template转换成一个JavaScript函数,这样浏览器就可以执行这一个函数,并渲染出对应的HTML元素,就可以让试图跑起来,这一个转化的过程就称之为模板编译。
模板编译又分为三个阶段:解析parse,优化optimize,生成generate,最终生成可以执行的函数render。
- parse阶段:使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化成抽象语法树AST
- optimize阶段:遍历AST,找到其中的一些静态节点进行标记,方便在页面重新渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能。
- generate阶段:将最终的AST转换为render函数字符串。
九、template预编译是什么?
-
对于Vue组件来说,模板编译只会在组件实例化的时候编译一次,生成渲染函数之后再不会进行编译。因此,编译对组件的runtime是一种性能耗损。
-
而模板编译的目的仅仅是将template转化为
render funtion
,这个过程,正好可以在项目构建的过程中完成,这样可以让实际组件在runtime时直接跳过模板渲染,进而提升性能,这个在项目构建编译template的过程就是预编译.
十、template与jsx有什么区别?
-
对于runtime来说,只需要保证组件存在render函数即可,而我们有了预编译后,只需要保证在构建函数的过程中生成render函数即可。
-
在webpack中,我们可以使用
vue-loader
编译.vue
文件,内部依赖的vue-template-compiler
模块,在webpack构建过程中,将template预编译成render函数。 -
与react类似,在添加了jsx的语法糖解析器
babel-plugin-transform-vue-jsx
之后,就可以直接手写render函数了 -
所以,template与jsx都是render的一种表现形式,不同的是:
- JSX相当于template而言,具有更高的灵活性,在复杂的组件中,更具有优势,而template虽然显得有些呆滞,但是template在代码结构上更符合视图与逻辑分离的习惯,更简单、直观、更好维护。
十一、说一下什么是Virtual DOM?
Virtual Dom 是DOM节点在JavaScript中的的一种抽象数据结构,之所以需要虚拟DOM,是因为浏览器中操作DOM的代价比较昂贵,频繁操作DOM会产生性能问题。虚拟DOM的作用是在每一次响应式数据发生变化引起页面重新渲染时,Vue对比更新前后的虚拟DOM,匹配找出尽可能少的需要重新更新的真实DOM,从而达到提升性能的目的。
十二、介绍一下Vue中的Diff算法
在新老虚拟DOM对比时:
- 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
- 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
- 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。
- 匹配时,找到相同的子节点,递归比较子节点
在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n^3)
降低值O(n)
,也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
十三、key属性的作用是什么?
在对节点进行diff的过程中,判断是否为相同节点的一个很重要的条件是key是否相等,如果是相同节点,则会尽可能的复用原有的DOM节点,所以key属性是提供给框架在diff的时候使用的,而非开发者。
十四、Vue2.0与3.0的区别
- 重构响应式系统,使用
Proxy
替换Object.defineProperty
,其优势在于:- 可以直接监听数组类型的数据变换
- 监听的目标为对象本身,不需要像
Object.defineProperty
一样遍历每个属性,有一定的性能提升 - 可以拦截
apply
、ownKeys
、has
等13种方法,而Object.defineProperty
不行 - 直接实现对象属性的增加、删除
- 新增
Composition API
,更好的逻辑复用和代码组织 - 重构Virtual Dom
- 模板编译时的优化,将一些静态节点编译成常量
- slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给了子组件
- 模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)
- 代码结构调整,便于
Tree shaking
,使得体积更小 - 使用TS替换Flow
十五、新增加的Composition API ,它可以解决什么问题?
- Vue2.0中,随着功能的增加,组件变得越来越复杂,越来越难维护,而难以维护的根本原因是Vue的API设计迫使开发者使用watch,computed,methods选项组织代码,而不是实际的业务逻辑。
- 另外Vue2.0缺少一种较为简洁的低成本的机制来完成逻辑复用,虽然可以minxis完成逻辑复用,但是当mixin变多的时候,会使得难以找到对应的data、computed或者method来源于哪个mixin,使得类型推断难以进行。
- 所以Composition API的出现,主要是也是为了解决Option API带来的问题,第一个是代码组织问题,Compostion API可以让开发者根据业务逻辑组织自己的代码,让代码具备更好的可读性和可扩展性,也就是说当下一个开发者接触这一段不是他自己写的代码时,他可以更好的利用代码的组织反推出实际的业务逻辑,或者根据业务逻辑更好的理解代码。
- 第二个是实现代码的逻辑提取与复用,当然mixin也可以实现逻辑提取与复用,但是像前面所说的,多个mixin作用在同一个组件时,很难看出property是来源于哪个mixin,来源不清楚,另外,多个mixin的property存在变量命名冲突的风险。而Composition API刚好解决了这两个问题。
十六、Composition API与React Hook很像,说说区别
从React Hook的实现角度看,React Hook是根据useState调用的顺序来确定下一次重渲染时的state是来源于哪个useState,所以出现了以下限制
- 不能在循环、条件、嵌套函数中调用Hook
- 必须确保总是在你的React函数的顶层调用Hook
- useEffect、useMemo等函数必须手动确定依赖关系
而Composition API是基于Vue的响应式系统实现的,与React Hook的相比
- 声明在setup函数内,一次组件实例化只调用一次setup,而React Hook每次重渲染都需要调用Hook,使得React的GC比Vue更有压力,性能也相对于Vue来说也较慢
- Compositon API的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用
- 响应式系统自动实现了依赖收集,进而组件的部分的性能优化由Vue内部自己完成,而React Hook需要手动传入依赖,而且必须必须保证依赖的顺序,让useEffect、useMemo等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。
虽然Compositon API看起来比React Hook好用,但是其设计思想也是借鉴React Hook的。
十七、SSR有了解吗?原理是什么?
在客户端请求服务器的时候,服务器到数据库中获取到相关的数据,并且在服务器内部将Vue组件渲染成HTML,并且将数据、HTML一并返回给客户端,这个在服务器将数据和组件转化为HTML的过程,叫做服务端渲染SSR。
而当客户端拿到服务器渲染的HTML和数据之后,由于数据已经有了,客户端不需要再一次请求数据,而只需要将数据同步到组件或者Vuex内部即可。除了数据意外,HTML也结构已经有了,客户端在渲染组件的时候,也只需要将HTML的DOM节点映射到Virtual DOM即可,不需要重新创建DOM节点,这个将数据和HTML同步的过程,又叫做客户端激活。
使用SSR的好处:
- 有利于SEO:其实就是有利于爬虫来爬你的页面,因为部分页面爬虫是不支持执行JavaScript的,这种不支持执行JavaScript的爬虫抓取到的非SSR的页面会是一个空的HTML页面,而有了SSR以后,这些爬虫就可以获取到完整的HTML结构的数据,进而收录到搜索引擎中。
- 白屏时间更短:相对于客户端渲染,服务端渲染在浏览器请求URL之后已经得到了一个带有数据的HTML文本,浏览器只需要解析HTML,直接构建DOM树就可以。而客户端渲染,需要先得到一个空的HTML页面,这个时候页面已经进入白屏,之后还需要经过加载并执行 JavaScript、请求后端服务器获取数据、JavaScript 渲染页面几个过程才可以看到最后的页面。特别是在复杂应用中,由于需要加载 JavaScript 脚本,越是复杂的应用,需要加载的 JavaScript 脚本就越多、越大,这会导致应用的首屏加载时间非常长,进而降低了体验感。