Vue面试题
1.前端路由原理?两种实现方式有什么区别?
前端路由本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新页面。目前前端使用的路由就只有两种实现方式:Hash 模式和History 模式。
Hash 模式
Hash模式会在请求的URL后拼接#,当 # 后面的哈希值发生变化时,可以通过 hashchange 事件来监听到 URL 的变化,从而进行跳转页面,并且无论哈希值如何变化,服务端接收到的 URL 请求永远是不包含#的URL。使用window.location.hash可以读取#后的内容。
History 模式
History 模式是 HTML5 新推出的功能,主要使用 history.pushState 和 history.replaceState 来改变URL。通过 History 模式改变 URL 同样不会引起页面的刷新,只会更新浏览器的历史记录。
当用户做出浏览器动作时,比如history.back()、history.forward()、history.go(),会触发 popState 事件,在popState事件里可以拿到路由信息,从而进行页面跳转。
两种模式对比
Hash 模式只可以更改 # 后面的内容,History 模式可以通过 API 设置任意的同源 URL History 模式可以通过
API 添加任意类型的数据到历史记录中,Hash 模式只能更改哈希值,也就是字符串 Hash
Hash模式无需后端配置,并且兼容性好。History 模式在用户手动输入地址或者刷新页面的时候会发起 URL 请求,后端需要配置index.html 页面用于匹配不到静态资源的情况
2.keep-alive 组件有什么作用?
vue在切换页面的时候会将原来的组件注销掉然后渲染新的组件,看似一样实则每次打开的都是新页面。如果我们需要在页面切换的时候,不让他重新渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
对于 keep-alive 组件来说,它拥有两个独有的生命周期钩子函数,分别为 activated 和 deactivated 。
用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,根据缓存渲染后会执行 actived 钩子函数。
假设一个页面比较长,每次切换重新渲染的话都要在页面顶端开始浏览,这对用户来说不太友好。如果我们使用keep-alive每次切换页面前将状态存到内存中,然后返回时再从内存中读取,每次打开都是上次浏览的地方,相对体验比较好
3.对react中key值的理解
React利用key来识别组件,它是一种身份标识,相同的key,react会认为是同一个组件,这样后续相同的key对应的组件都不会创建。
有了key属性之后,就可以和组件建立一种对应关系,通过key来决定是重建组件还是更新组件;
如果key相同,看组件属性有没有变化来决定更不更新;
如果key不同,就先销毁该组件再重建。
4.说一下vue-router 守卫有哪些,如何实现路由懒加载?
vue的路由守卫:全局前置守卫(beforeEach)、全局解析守卫(beforeResolve)、全局后置守卫(afterEach)、路由独享守卫(beforeEnter)、组件内守卫(beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave)
路由懒加载就是把不同的路由对应的组件分割成不同的代码块,当路由被访问的时候才加载对应的组件,主要实现原理就是vue的异步组件和webpack的代码分割。
第一步 我们可以将异步组件定义为返回一个promise的工厂函数
第二 在webpack中使用动态import语法来定义代码分块点
在路由配置中什么都不需要改变
5.什么是Vue.js 动态组件与异步组件?
动态组件:如果我们打算在一个地方根据不同的状态引用不同的组件的话,vue给我们提供了动态组件。
实现就是通过给元素加一个特殊的is属性。
如果我们希望组件实例在它们第一次被创建的时候缓存下来,可以使用元素将动态组件包裹起来。
异步组件:在一些大型的应用中,我们可能需要将应用分割成一些小的代码块,并且只在需要的时候才从服务器加载。vue允许以一个工厂函数的方式定义组件,这个工厂函数会异步解析组件定义。vue只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
我们通常就是将异步组件和webpack的code-splitting功能配合使用,比如实现路由懒加载。
6.说一下vue单页面应用和多页面应用的区别
7.Vuex和redux有什么区别,他们共同的思想
8.vue项目常见优化点
9.前端路由原理
本质就是监听 URL 的变化,然后匹配路由规则,显示相应的页面,并且无须刷新 路由需要实现三个功能:
①浏览器地址变化,切换页面;
②点击浏览器【后退】、【前进】按钮,网页内容跟随变化;
③刷新浏览器,网页加载当前路由对应内容
目前前端使用的路由就只有两种实现方式
两种模式对比
10.vue中render函数与template对比。
介绍:
render --- js的方式做渲染。
tempalte --- html方式做渲染。
render里有一个函数h,这个h的作用是将单文件组件进行虚拟DOM的创建,然后再通过render进行解析。h就是createElement()方法:createElement( 标签名称, 属性配置, children )。
template是一种编译方式,但是template最终还是要通过render的方式再次进行编译。
区别:
11.vue-router路由权限认证如何做?
vue-router做路由权限认证,首先需求来源,根据不同的用户拥有不同的权限,例如:管理员,超级管理员,普通用户,他们登陆成功,所看到的东西是不一样的,所以也引发了不同角色登录成功会出现不同的路由这个话题了。来讲讲具体怎么做吧!!!首先在没有登录之前,可能除了登录页面和注册页面,其他的页面是没有办法进入,的这个时候我们需要使用到vue的路由前置守卫beforeEach了
router.beforeEach((to,from,next) => {
// 如果元信息的信息是需要登录或者前端埋点信息中是未登录状态比如没有登录token
if(!to.meta.isLogin && !token){
// 跳转登录页面
next("/login")
}else {
// 放行
next();
}
})
上面是第一步,如果登录成功之后后端一定会返回一系列的数据,数据里面包含了路由的信息,我们需要在其前端通过addRoutes的方式为前端进行健全
这样话,登录完成之后我们前端的路由就完整了,不同的角色拥有不同的权限路由。
12.vuex是有哪几个部分组成的(怎么去存储数据)
五大核心:
如何存储数据?
首先需要我们去dispatch方法给到action,action里面的函数会接受外部传来的payload的值,然后在action中我们再去commit找到mutation中的函数,在这个函数中我们吧在action中处理过的数据拿到存储在state中。
注意:这里面在修改值的时候我们需要注意 一个问题就是,关于vue2.0在修改对象或者修改数组值的时候会出现修改完之后vue无法监听到,我们需要使用深拷贝,扩展运算符或者$set这样的操作来重新让vue监听这个值
getter就是一个缓存属性,我们可以再计算一些比较复杂的数据的时候使用getter,也可以帮我们做到同步计算的效果
modules在分模块的时候是必须,例如:购物车数据和商品数据就是两个不同的模块,我们需要将他分开进行处理,但是在合并节点的时候,为了不发生冲突,我们需要使用命名空间的方式来区分他们。
13.简单的介绍一下vue和react的区别,他们当中都有key这个说法,它具体是做什么的
先说相同点:
Vue和React实现原理和流程基本一致,都是使用Virtual DOM + Diff算法。不管是Vue的template模板 + options api写法,还是React的Class或者Function(js 的class写法也是function函数的一种)写法,底层最终都是为了生成render函数,render函数执行返回VNode(虚拟DOM的数据结构,本质上是棵树)。当每一次UI更新时,总会根据render重新生成最新的VNode,然后跟以前缓存起来老的VNode进行比对,再使用Diff算法(框架核心)去真正更新真实DOM(虚拟DOM是JS对象结构,同样在JS引擎中,而真实DOM在浏览器渲染引擎中,所以操作虚拟DOM比操作真实DOM开销要小的多)。
Vue和React通用流程:vue template/react jsx -> render函数 -> 生成VNode -> 当有变化时,新老VNode diff -> diff算法对比,并真正去更新真实DOM。
核心还是Virtual DOM,为什么Vue和React都选择Virtual DOM(React首创VDOM,Vue2.0开始引入VDOM)?,个人认为主要有以下几点:
不同点:
1、核心思想不同
Vue早起定位是尽可能降低前端的开发门槛,Vue推崇灵活易用(渐进开发体验),数据可变,双向数据绑定(依赖收集)
React早期口号是Rethinking Best Practices。背靠大公司Facebook的React,从开始起就不缺关注和用户,而且React想要做的是用更好的方式去颠覆前端开发方式(事实上跟早期jquery称霸前端,的确是颠覆了)。所以React推崇函数式编程(纯组件),数据不可变以及单向数据流。函数式编程最大的好处是其稳定性(无副作用)和可测试性(输入相同,输出一定相同),所以通常大家说的React适合大型应用,根本原因还是在于其函数式编程。
1.1核心思想不同导致写法差异
Vue推崇template(简单易懂,从传统前端转过来易于理解)、单文件vue。而且虽然Vue2.0以后使用了Virtual DOM,使得Vue也可以使用JSX(bebel工具转换支持),但Vue官方依然首先推荐template,这跟Vue的核心思想和定位有一定关系。
React推崇JSX、HOC、all in js。
1.2 核心思想不同导致api差异
Vue定位简单易上手,基于template模板 + options API,所以不可避免的有较多的概念和api。比如template模板中需要理解slot、filter、指令等概念和api,options API中需要理解watch、computed(依赖收集)等概念和api。
React本质上核心只有一个Virtual DOM + Diff算法,所以API非常少,知道setState就能开始开发了。
1.3 核心思想不同导致社区差异
由于Vue定义简单易上手,能快速解决问题,所以很多常见的解决方案,是Vue官方主导开发和维护。比如状态管理库Vuex、路由库Vue-Router、脚手架Vue-CLI、Vutur工具等。属于那种大包大揽,遇到某类通用问题,只需要使用官方给出的解决方案即可。
React只关注底层,上层应用解决方案基本不插手,连最基础的状态管理早期也只是给出flow单向数据流思想,大部分都丢给社区去解决。比如状态管理库方面,有redux、mobx、redux-sage、dva等一大堆(选择困难症犯了),所以这也造就了React社区非常繁荣。同时由于有社区做上层应用解决方案,所以React团队有更多时间专注于底层升级,比如花了近2年时间把底层架构改为Fiber架构,以及创造出React Hooks来替换HOC,Suspense等。 更多框架设计思想可看 尤雨溪 - 在框架设计中寻求平衡。
1.4 核心思想不同导致未来升级方向不同
核心思想不同,决定了Vue和React未来不管怎么升级变化,Vue和React考虑的基本盘不变。
Vue依然会定位简单易上手(渐进式开发),依然是考虑通过依赖收集来实现数据可变。这点从Vue3核心更新内容可以看到:template语法基本不变、options api只增加了setup选项(composition api)、基于依赖收集(Proxy)的数据可变。更多Vue3具体更新内容可看笔者总结 Vue3设计思想 或者 尤雨溪 - 聊聊 Vue.js 3.0 Beta 官方直播。
React的函数式编程这个基本盘不会变。React核心思想,是把UI作为Basic Type,比如String、Array类型,然后经过render处理,转换为另外一个value(纯函数)。从React Hooks可以看出,React团队致力于组件函数式编程,(纯组件,无class组件),尽量减少副作用(减少this,this会引起副作用)。
2、组件实现不同
Vue源码实现是把options挂载到Vue核心类上,然后再new Vue({options})拿到实例(vue组件的script导出的是一个挂满options的纯对象而已)。所以options api中的this指向内部Vue实例,对用户是不透明的,所以需要文档去说明this.xxx这些api。另外Vue插件都是基于Vue原型类基础之上建立的,这也是Vue插件使用Vue.install的原因,因为要确保第三方库的Vue和当前应用的Vue对象是同一个。
React内部实现比较简单,直接定义render函数以生成VNode,而React内部使用了四大组件类包装VNode,不同类型的VNode使用相应的组件类处理,职责划分清晰明了(后面的Diff算法也非常清晰)。React类组件都是继承自React.Component类,其this指向用户自定义的类,对用户来说是透明的。
3、响应式原理不同
这个问题网上已经有许多优秀文章都详细讲解过,这里就不具体展开讲,对Vue3响应式原理有兴趣可以看笔者 Vue3响应式原理(Vue2和Vue3响应式原理基本一致,都是基于依赖收集,不同的是Vue3使用Proxy)。
Vue
React
4、diff算法不同
两者流程思维上是类似的,都是基于两个假设(使得算法复杂度降为O(n)):
但两者源码实现上有区别:
Vue基于snabbdom库,它有较好的速度以及模块机制。Vue Diff使用双向链表,边对比,边更新DOM。
React主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。
5#####、事件机制不同
Vue
React
Vue 和 React源码流程图
关于vue中的key:
在执行动态列表渲染的时候我们需要给每一个组件添加一个key的属性。
首先这个属性与diff算法是相关的。不管是vue还是react自己都有一套自己的虚拟dom算法,只操作数据就可以重新渲染页面,背后则是高校的Diff算法
1.两个相同的组件产生的类似的DOM结构,不同的组件产生不同的DOM结构
2.同一级的一组节点,他们可以通过唯一的id进行区分
使用Diff将之前的组件节点之间的比较算法的时间复杂度由O(n^3)变为了O(n)
当页面数据发生变化的时候,Diff算法只会比较同一层级的节点
如果节点类型不同,直接干掉前面的节点,在创建并插入新的节点,不会再比较节点以后的子节点了。
如果节点相同,则会重新设置该节点的属性,从而实现节点的更新
例如:
我们希望可以在B和C之间加一个F,Diff算法默认执行起来就是这样的:
即把C更新成F,D更新成C,E更新成D,最后在插入E,是不是更有效率呢?
所以我们需要使用key来给每一个节点做一个唯一标识,Diff算法可以正确的识别此节点,找到正确的位置区插入新的节点。
所以一句话。key的作用主要就是为了高效的更新虚拟DOM,另外vue中的使用相同标签名元素的 过度切换时, 也会使用key属性, 其目的也是为了让vue可以区分它们
14.template 模板是怎样通过 Compile 编译的
Compile 编译有三个阶段
parse函数:通过正则表达式解析模板中的指令,生成AST抽象语法树
optimize函数:优化AST,标记静态的(不需要修改的DOM)节点。diff算法更新时,会跳过静态节点, 8减少比较的过程,优化patch的性能
generate: 将AST生成render函数
https://www.jianshu.com/p/700b8e6347ff
15.MVVM框架设计理念
Model–View–ViewModel (MVVM) 是一个软件架构设计模式,由微软 WPF 和 Silverlight 的架构师 Ken Cooper 和 Ted Peters 开发,是一种简化用户界面的事件驱动编程方式。由 John Gossman(同样 也是 WPF 和 Silverlight 的架构师)于2005年在他的博客上发表
MVVM 源自于经典的 Model–View–Controller(MVC)模式 ,MVVM 的出现促进了前端开发与后端业 务逻辑的分离,极大地提高了前端开发效率,MVVM 的核心是 ViewModel 层,它就像是一个中转站 (value converter),负责转换 Model 中的数据对象来让数据变得更容易管理和使用,该层向上与视 图层进行双向数据绑定,向下与 Model 层通过接口请求进行数据交互,起呈上启下作用。
(1)View 层
View 是视图层,也就是用户界面。前端主要由 HTML 和 CSS 来构建 。
(2)Model 层
Model 是指数据模型,泛指后端进行的各种业务逻辑处理和数据操控,对于前端来说就是后端提供的 api 接口。
(3)ViewModel 层
ViewModel 是由前端开发人员组织生成和维护的视图数据层。在这一层,前端开发者对从后端获取的 Model 数据进行转换处理,做二次封装,以生成符合 View 层使用预期的视图数据模型。需要注意的是 ViewModel 所封装出来的数据模型包括视图的状态和行为两部分,而 Model 层的数据模型是只包含状 态的,比如页面的这一块展示什么,而页面加载进来时发生什么,点击这一块发生什么,这一块滚动时 发生什么这些都属于视图行为(交互),视图状态和行为都封装在了 ViewModel 里。这样的封装使得 ViewModel 可以完整地去描述 View 层。
MVVM 框架实现了双向绑定,这样 ViewModel 的内容会实时展现在 View 层,前端开发者再也不必低 效又麻烦地通过操纵 DOM 去更新视图,MVVM 框架已经把最脏最累的一块做好了,我们开发者只需要 处理和维护 ViewModel,更新数据视图就会自动得到相应更新。这样 View 层展现的不是 Model 层的 数据,而是 ViewModel 的数据,由 ViewModel 负责与 Model 层交互,这就完全解耦了 View 层和 Model 层,这个解耦是至关重要的,它是前后端分离方案实施的重要一环。
16.对于即将到来的vue3.0特性你有什么了解的吗
Vue 3.0 正走在发布的路上,Vue 3.0 的目标是让 Vue 核心变得更小、更快、更强大,因此 Vue 3.0 增加以下这些新特性:
(1)监测机制的改变
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言覆盖的反应性跟踪。这消除了 Vue 2 当中基 于 Object.defineProperty 的实现所存在的很多限制:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map }
新的 observer 还提供了以下特性:
(2)模板
模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会重新渲染,而3.0 把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。 同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来方便习惯直接使用 api 来生成 vdom 。
(3)对象式的组件声明方式
vue2.x 中的组件是通过声明的方式传入一系列 option,和 TypeScript 的结合需要通过一些装饰器的方 式来做,虽然能实现功能,但是比较麻烦。3.0 修改了组件的声明方式,改成了类式的写法,这样使得 和 TypeScript 的结合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的功能复杂之后,必须有一个静态类型系统 来做一些辅助管理。现在 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外暴露的 api 更容易结 合 TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
(4)其它方面的更改
vue3.0 的改变是全面的,上面只涉及到主要的 3 个方面,还有一些其他的更改:
17.如果线上出现bug git怎么操作
方法1:在当前主分支修改bug,暂存当前的改动的代码,目的是让工作空间和远程代码一致:
Git stash
修改完bug后提交修改:
git add.
git commit --m “fix bug 1”
git push
从暂存区把之前的修改恢复,这样就和之前改动一样了
git stash pop
这时可能会出现冲突,因为你之前修改的文件,可能和bug是同一个文件,如果有冲突会提示:
Auto–merging xxx.Java
conflice(content):Merge conflict in xxx.java
前往xxx.java解决冲突
注意stash pop意思是从暂存区恢复到工作空间,同时删除此条暂存记录
方法2:拉一个新分支,老司机都推荐这样做,充分利用了git特性,先暂存一下工作空间改动:
git stash
新建一个分支,并切换到这个新分支
git branch fix-bug//新建分支
git checkbox fix-bug//切换分支
这时候就可以安心在这个分之下修改bug了,改完之后:
git add.
git commit --m “fix a bug”
切换到master主分支
git checkout master
从fix-bug合并到master分支
git merge fix-bug
提交代码
git push
然后从暂存区恢复代码
git stash pop
此时有冲突需要解决冲突
会!!!
因为vuex里的 数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里 面的数据就会被重新赋值。
用vuex-persistedstate插件可以解决
在vuex配置项中配置 plugins: [createPersistedState()],默认存储在localStorage
传参方式:createPersistedState({ storage: window.sessionStorage})
将vuex中的数据直接保存到浏览器缓存中(sessionStorage、localStorage、cookie)
缺点是不安全,不适用大数据量的存储;
19.vue.nextTick()
定义:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
放在Vue.nextTick()回调函数中的执行的应该是会对DOM进行操作的 js代码;
理解:nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:当数据更新了,在dom中渲染后,自动执行该函数,
注意:Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 nextTick,则可以在回调中获取更新后的 DOM,
20.V-if和v-show的区别,应用场景
v-show 只是在 display: none 和 display: block 之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 v-show 在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。
v-if 的话就得说到 Vue 底层的编译了。当属性初始为 false 时,组件就不会被渲染,直到条件为 true,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。
并且基于 v-if 的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。
vuex和redux的区别
表面区别就是vuex是通过将store注入到组件实例中,通过dispatch和commit来维护state的状态,并可以通过mapstate和this.$store来读取state数据。而redux则是需要通过connect将state和dispatch链接来映射state并操作state。redux没有commit,直接通过dispatch派发一个action来维护state的数据。并且只能通过reducer一个函数来操作state
rudex使用的是不可变数据;vuex使用的是可变数据
rudex每次都是返回一个新的state;而vuex是直接改变state。
vue路由懒加载方式
resolve
官网方法
require.ensure
Vue中的key值、React中的key值
Vue
Raect (Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识)
Vue中的单向数据流
Vue组件中的data为什么是一个函数,根组件却是对象
为什么根实例的data是一个对象?
为什么组件中的data必须是一个函数
总结
Vue的性能优化
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
第三方模块按需导入
服务端渲染SSR (更利于SEO,更利于首屏渲染)
压缩代码
Tree Shaking
source-map
服务端开启gzip压缩
什么是nextTick
应用场景:
在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中
原因:是created()钩子函数执行时DOM其实并未进行渲染
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在Vue.nextTick()的回调函数中
原因:Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次
介绍一下单页面和多页面的区别
Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信
props / $emit 适用 父子组件通信
ref 与 parent / children 适用 父子组件通信
EventBus (emit / on)适用于父子、隔代、兄弟组件通信
attrs / listeners 适用于 隔代组件通信
provide / inject 适用于 隔代组件通信
Vuex 适用于 父子、隔代、兄弟组件通信
Vue中响应式的原理
Vue的响应式原理也就是数据的双向绑定原理,数据双向绑定主要是指:数据变化更新视图,视图变化更新数据
步骤
实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化
实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新
实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数
实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理
虚拟DOM为什么会提高性能
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了
vue中 computed 的原理
当组件初始化的时候,computed 和 data 会分别建立各自的响应系统,Observer遍历 data 中每个属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持
初始化 computed 会调用 initComputed 函数
调用 computed 时会触发其 Object.defineProperty 的 get 访问器函数
当某个属性(依赖项)发生变化,触发 set 拦截函数,然后调用自身消息订阅器dep, dep 通过 notify 遍历当前 dep.subs 通知每个 watcher 更新(watcher.update())
34.为什么在 Vue3.0 采用了 Proxy,抛弃了Object.defineProperty
Object.defineProperty 本身有一定的监控到数组下标变化的能力,但是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性(Vue 为什么不能检测数组变动 )。为了解决这个问题,经过 vue 内部处理后可以使用以下几种方法来监听数组 ( push() pop() shift() unshift() splice() sort() reverse() )
Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
Proxy 可以劫持整个对象,并返回一个新的对象。 Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。
实现原理
缓存策略(LRU缓存淘汰算法)
LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
五个属性
State
Getter
Mutation
Action
Module
Vue的性能优化技巧
使用函数式组件
函数式组件和普通的对象类型的组件不同,它不会被看作成一个真正的组件,我们知道在 patch 过程中,如果遇到一个节点是组件 vnode,会递归执行子组件的初始化过程;而函数式组件的 render 生成的是普通的 vnode,不会有递归子组件的过程,因此渲染开销会低很多。
因此,函数式组件也不会有状态,不会有响应式数据,生命周期钩子函数这些东西。你可以把它当成把普通组件模板中的一部分 DOM 剥离出来,通过函数的方式渲染出来,是一种在 DOM 层面的复用
子组件拆分
优化后的方式是把这个耗时任务 heavy 函数的执行逻辑用子组件 ChildComp 封装了,由于 Vue 的更新是组件粒度的,虽然每一帧都通过数据修改导致了父组件的重新渲染,但是 ChildComp 却不会重新渲染,因为它的内部也没有任何响应式数据的变化。所以优化后的组件不会在每次渲染都执行耗时任务,自然执行的 JavaScript 时间就变少了。
使用 v-show 复用 DOM
v-show 相比于 v-if 的性能优势是在组件的更新阶段,如果仅仅是在初始化阶段,v-if 性能还要高于 v-show,原因是在于它仅仅会渲染一个分支,而 v-show 把两个分支都渲染了,通过 style.display 来控制对应 DOM 的显隐
38.vue中使用过ssr吗?说说ssr
Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,也可以将同一个组件渲染为服务端的 HTML 字符串,将它们直接发送 到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步 完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由 服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好 的页面;
更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才 开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由 服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到 达时间;
(2) 服务端渲染的缺点:
更多的开发条件限制: 例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致 一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文 件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
computed:
如果不使用计算属性,那么 message.split('').reverse().join('') 就会直接写到 template 里,那么在模版中放入太多声明式的逻辑会让模板本身过重,尤其当在页面中使用大量复杂的逻辑表达式处理数据时,会对页面的可维护性造成很大的影响,而且计算属性如果依赖不变的话,它就会变成缓存,computed 的值就不会重新计算。而且他返回的是一个值,同时也不能进行异步操作。所以就有了watch。
watch:
也是用来监听数据的变化的,watch与computed的区别就是它支持异步,不支持缓存。而且watch在第一次监听的时候不会触发,想要触发必须得在第二个参数上面配置(immeiate)。此外还有另外一个参数是deep,可以用来深度监听一个数组或者是对象。
第一个生命周期函数,表示实例完全被创建之前,会执行这个函数
在beforeCreate生命周期函数执行的时候,data 和 methods 中的数据还没有被初始化
第二个生命周期函数
在 created 中,data 和 methods 都已经被初始化好了!
如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作
第三个生命周期函数,表示模板已经在内存中编译完成,但是尚未把模板渲染到页面中
在beforeMount执行的时候,页面中的元素没有被真正替换过来,知识之前写的一些模板字符串
第四个生命周期函数,表示内存中的模板已经真实的挂载到页面中,用户已经可以看到渲染好的页面
这个mounted是实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了
第五个生命周期函数,表示界面还没有被更新,但是数据肯定被更新了
得出结论:
当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时data 数据是最新的,页面尚未和最新的数据保持同步
第六个生命周期函数
updated事件执行的时候,页面和 data 数据已经保持同步了,都是最新的
第七个生命周期函数
当执行beforeDestroy的时候,实例身上的data及所有的methods都处于可用状态,还没有真正执行销毁过程
第八个生命周期函数
当执行到destroyed时,组件已被完全销毁
hash、chunkhash、contenthash三者区别
hash
chunkhash
contenthash