1、监听数据变化的实现原理不同
Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
React 默认是通过比较引用的方式(diff)进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的VDOM的重新渲染
2、数据流的不同
Vue中默认是支持双向绑定的
React 不支持双向绑定,一直提倡的是单向数据流,他称之为 onChange/setState()模式。
3、模板渲染方式的不同
React 是通过JSX渲染模板
而Vue是通过一种拓展的HTML语法进行渲染
Model 代表数数据层,在Vue中指的是 data属性
View 代表视图层,在Vue中指的是 html
ViewModel 代表视图模型层,ViewModel 通过双向数据绑定把 View 层和 Model层连接了起来,在Vue中指的是vue的实例
1.View传送指令到Controller。
2.Controller完成业务逻辑后改变Model状态。
3.Model将新的数据发送至View,用户得到反馈。
MVVM通过数据来显示视图层而不是节点操作
MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验
无需显式调用。vue利用发布者订阅者模式+数据劫持,可以直接通知变化并更新视图。比如data.name = '渣渣辉‘;后直接触发变更,而比如Angular的脏值检测则需要显式调用markForCheck
可精确得知变化数据:劫持了属性setter之后,当属性值改变,我们可以精确知道变化的内容newVal,因此在这一部分不需要多余的diff操作。否则我们只知道数据变化了,但是不知道具体哪些数据发生变化,所以需要大量的diff来找出变化值。这是额外的性能损耗。
1、初始化显示
beforeCreate(): 不能通过this读取data中的数据
在beforeCreate阶段,在实例初始化之后立即同步调用,此时还没有数据代理和数据观察,vue实例的挂载元素el和数据对象data都为undefined
实现数据代理与数据监视
created(): 在此开始可以通过this读取data中的数据
在created阶段,vue实例的数据对象data有了,el还没有
实现在内存中去编译/解析模板
beforeMount(): 不能通过ref来读取页面中的标签
vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。
将编译的DOM挂载到页面上显示
mounted(): 在此开始可以通过ref来读取页面中的标签——初始化渲染页面,只会调用一次
在mounted阶段,vue实例挂载完成,$el 可以访问( $ el 是虚拟DOM编译之后的真实DOM),data.message成功渲染。
2、更新状态:更新了data中的数据
3、销毁vue实例: vm.$destory()
常用的生命周期方法
* mounted() / created(): 绑定自定义事件监听、发送ajax请求, 启动定时器等异步任务
* beforeDestory(): 做收尾工作, 如: 清除定时器
什么是vue生命周期?
答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
vue生命周期的作用是什么?
答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑
vue生命周期总共有几个阶段?
答:它可以总共分为8个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。
第一次页面加载会触发哪几个钩子?
答:会触发下面这几个beforeCreat、created、beforeMount、mounted 。
DOM 渲染在哪个周期中就已经完成?
答:DOM 渲染在 mounted 中就已经完成
组件会复用
当我们使用组件的时候,虽然data是在原型链上被创建的,所有的实例化的组件都共享一份data;而函数是返回对象的独立拷贝
vuex是一个专为vue.js应用程序开发的状态管理器,它采用集中式存储管理应用的所有组件的状态,并且以相应的规则保证状态以一种可以预测的方式发生变化。
state: vuex使用单一状态树,用一个对象就包含来全部的应用层级状态
mutation: 更改vuex中state的状态的唯一方法就是提交mutation
action: action提交的是mutation,而不是直接变更状态,action可以包含任意异步操作
getter: 相当于vue中的computed计算属性
引入 vueRouter ,声明使用vueRouter插件,new vueRouter并配置路由
在main.js中注册路由
1、url 展示上,hash 模式有“#”,history 模式没有
2、刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 因为没有 # 号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求,没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由。
3、兼容性。hash 可以支持低版本浏览器和 IE
1、hash 模式:
这种 #。后面 hash 值的变化,并不会导致浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。另外每次 hash 值的变化,还会触发hashchange
这个事件,通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听hashchange来实现更新页面部分内容的操作
2、history 模式:
history 模式的实现,主要是 HTML5 标准发布的两个 API,pushState
和 replaceState
,这两个 API 可以改变 url 地址且不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。
hash模式:
路径中带#: http://localhost:8080/#/home/news
发请求的路径: http://localhost:8080 项目根路径 = = > 返回index.html
响应: 返回的总是index页面 = = > path部分(/home/news)被解析为前台路由路径
history模式:
路径中不带#: http://localhost:8080/home/news
发请求的路径: http://localhost:8080/home/news ==> 后台处理不了,返回404
响应: 404错误
解决思路:
需要在服务器配置,如果URL匹配不到任何静态资源,就跳转到默认的index.html。, path部分(/home/news)被解析为前台路由路径
解决: 添加配置
devServer: historyApiFallback: true, // 任意的 404 响应都被替代为 index.html
output: publicPath: '/', // 引入打包的文件时路径以/开头
当v-if 条件为false时,会将元素从DOM中删除,
当v-if 条件为true时,会在DOM中新键一个元素;
当v-show条件为false时,会display : none
当v-show条件为true时,会display : block
.prevent : 阻止事件的默认行为 event.preventDefault()
.stop : 停止事件冒泡 event.stopPropagation()
可以。
v-cloak指令
[v-cloak] {
display: none!important;
}
隐藏未编译的双大括号标签
element-ul vant
$ route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
而$ router是路由器对象包括了路由的跳转方法(push , replace , back ,go),钩子函数等
< keep-alive> 可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲染。有 include (包含组件)、exclude (排除组件)这两个属性
< keep-alive include="About,Home">
<router-view></router-view>
</ keep-alive>
比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用< keep-alive>< /keep-alive>进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染。
其拥有两个独立的生命周期钩子函数 actived
和deactived
,使用 keep-alive 包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactived 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
Vue 的组件都是可复用的,一个组件创建好后,可以在多个地方复用,组件每复用一次,data 就应该复用一次,每一处复用组件的 data 改变应该对其他复用组件的数据不影响。
如果data值为对象,因为对象是引用类型,组件可能会被多个实例同时引用。如果data值为对象,将导致多个实例共享一个对象,其中一个组件改变data属性值,其它实例也会受到影响。
data为函数,每个实例都有自己独立的对象,实例之间可以互不影响的改变data属性值。
当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费。
这种场景建议使用 computed,先对数据进行过滤。
computed 计算属性,是依赖其他属性的计算值,并且有缓存,只有当依赖的值变化时才会更新。
watch 是在监听的属性发生变化时,在回调中执行一些逻辑。
所以,computed 适合在模板渲染中,某个值是依赖了其他的响应式对象甚至是计算属性计算而来,而 watch 适合监听某个值的变化去完成一段复杂的业务逻辑。
计算属性是基于他们的响应式依赖进行缓存的,只有在依赖发生变化时,才会计算求值,而使用 methods,每次都会执行相应的方法。
computed会拥有自己的watcher
,它内部有个属性dirty
开关来决定 computed 的值是需要重新计算还是直接复用之前的值。,假设计算属性sum依赖响应式属性count
1、在sum第一次进行求值的时候会读取响应式属性count,收集到这个响应式数据作为依赖。并且计算出一个值来保存在自身的 value上,把dirty设为false,接下来在模板里再访问sum就直接返回这个求好的值value,并不进行重新的求值。
2、而count发生变化了以后会通知sum所对应的watcher把自身的dirty属性设置成 true,这也就相当于把重新求值的开关打开来了。这个很好理解,只有count变化了,sum才需要重新去求值。
3、那么下次模板中再访问到this.sum的时候,才会真正的去重新调用sum函数求值,并且再次把dirty设置为 false。
v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景 参考
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化:数组冻结Object.freeze
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化
开启 gzip 压缩
浏览器缓存
CDN 的使用
使用 Chrome Performance 查找性能瓶颈
首先对于data中的属性是对象时候,vue会遍历对象的所有属性进行依赖收集,如果对象属性还是对象,那么会继续递归遍历这个对象的所有属性进行依赖收集。
对于数组,会遍历数组的每一项进行依赖收集,如果数组的单项是数组或者对象,会继续进行递归依赖收集。所以我们修改数组的任何一项或者任何一项的任意属性,都会触发对应的setter方法,触发更新操作。
同时,对于数组,vue重写了数组原型链的方法,在调用原型链方法的时候,会自动触发更新操作。其中,如果调用的是会增加数组项的push、unshift和splice这三个方法,那么在触发更新前,会对新增的项进行依赖收集。
当然也可以采用同步渲染,只是同步渲染的话,对数据的每次修改,都会立刻引发dom的修改,而对dom的操作是比较费时的,从性能的角度考虑,vue选择了异步更新。在数据变化下,无论是引起的重绘渲染还是重排渲染,都有可能会在性能消耗之下造成低效的页面性能,甚至造成加载卡顿问题。
实际上,Vue在默认情况下,每次触发某个数据的setter方法后,对应的Watcher对象其实会被push进一个队列queue 中,在下一个tick的时候将这个队列queue全部拿出来run(Watcher对象的一个方法,用来触发patch操作) 一遍。
官方实例的异步请求是在mounted生命周期中调用的,而实际上也可以在created生命周期中调用。
当需要在组件销毁前做一些善后收尾工作的时候,比如清除计时器,可以在这个回调里写。
全局前置/钩子:beforeEach、beforeResolve、afterEach 完整的导航解析流程如下: Mutation中是同步函数和Action中是异步函数 更改state的唯一方法是通过提交 mutation,它能被devtools 追踪状态变化 mutation的触发通过store.commit来进行 Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。vuex中的数据是多个组件共享的,如果mutation支持异步操作,多个组件同时异步修改,导致数据错乱,没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。 模板编译的作用是生成渲染函数,通过执行渲染函数生成最新的vnode,最后根据vnode进行渲染。 模板———>模板编译——>渲染函数——>vnode——>用于界面 此过程可以分成两个步骤:先将模板解析成AST(abstract syntax tree,抽象语法树),然后使用AST生成渲染函数。 1、将模板解析成AST 这三部分内容在模板编译中分别抽象出三个模块实现各自的功能:解析器、优化器和代码生成器。 标记静态节点有两个好处: 优化器的实现原理主要分两步: 用于安装 Vue.js 插件。 第一,判断这个插件是否被注册过,如果已经注册了,不允许重复注册。 deep,默认值是 false,代表是否深度监听。 immediate:true代表如果在 wacth 里声明了之后,就会立即先去执行里面的handler方法,如果为 false就跟我们以前的效果一样,最初绑定的时候是不会执行的,要等到 监听的属性 改变时才执行监听计算。 虚拟DOM不会进行排版与重绘操作 观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。 在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。 1、内置指令 2、自定义指令 a、注册全局指令 b、注册局部指令 3)使用指令 1、watch(handler、deep、immediate) 4、样式穿透——深度选择器 有些像 Sass 之类的预处理器无法正确解析 5、路由传参,props解耦 6、异步加载组件 7、keep-alive 组件缓存 https://www.jianshu.com/p/1618cb183d28 项目中引用的图片都直接引用静态目录下的图片,如果图片放到src\assets目录下,项目中引用图片的相对路径,在vue打包的时候会改变图片的引用名称,这样就无法实现图片预加载的效果 preload()方法中先将所有要加载的图片路径放到一个数组中,然后遍历数组中的每个图片路径,创建Image对象,将图片的路径绑定到image.src,实现图片加载,然后在图片加载的onload回调中记录图片加载的数量count,用作实现图片资源加载百分比的计算。 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。 表单元素依赖于状态,表单元素需要默认值实时映射到状态的时候,就是受控组件,这个和双向绑定相似. 非受控组件即不受状态的控制,获取数据就是相当于操作DOM(ref)。 参考链接: 前端基础之vue篇 2020年大厂面试指南 - Vue篇v-html
指令最终调用的是innerHTML
方法将指令的value
插入到对应的元素里,这很容易导致XSS跨站脚本攻击,一般情况下我们只对可信内容使用HTML插值,绝不要对用户提供的内容插值。
如果一定要用可以用标签代替
的一个属性:被包围在
元素中的文本通常会保留空格和换行符,并且文本也会呈现为等宽字体。
站在项目全局的角度,如果不放心可以使用xss的npm包,在webpack里配置对所有innerHTML方法进行覆盖,对innerHTML方法的值外面包上一层xss方法。
vue-router中导航守卫有哪些?
路由独享的守卫:beforeEnter
组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
action和mutation的区别?
Action中不进行状态的直接更改,而是通过commit触发mutation
action的触发通过store.dispatch进行为什么 Vuex 的 mutation 中不能做异步操作?
vue中模板编译原理?
由于静态节点不需要总是重新渲染,所以生成AST之后,生成渲染函数之前这个阶段,需要做一个优化操作:遍历一遍AST,给所有静态节点做一个标记,这样在虚拟DOM中更新节点时,如果发现这个节点有这个标记,就不会重新渲染它。
所以,在大体逻辑上,模板编译分三部分内容:
2、遍历AST标记静态节点
3、使用AST生成渲染函数
1、每次重新渲染的时候不需要为静态节点创建新节点,也就是静态节点的解析器不需要重新创建
2、在Virtual DOM中patching的过程可以被跳过
1、用递归的方式将静态节点添加static属性,用来标识是不是静态节点
2、标记所有静态根节点(子节点全是静态节点就是静态根节点)vue.use()
如果插件是一个对象,自动调用 install 方法。
如果插件是一个函数,该函数就是 install 方法。
install 方法调用时,会将 Vue 作为参数传入。当 install 方法被同一个插件多次调用,插件将只会被安装一次。
第二,如果传入的插件是一个对象,且对象中有install属性,且此属性对应的值是一个函数… … 如果传入的插件是一个函数 … …nextTick实现原理?
Watch中的deep:true是如何实现的?
Watch中的deep、immediate作用和区别
虚拟DOM和真实DOM的区别,虚拟DOM的优缺点
虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分
用js模拟一颗dom树,放在浏览器内存中.当你要变更时,虚拟dom使用diff算法进行新旧虚拟dom的比较,将变更放到变更队列中,反应到实际的dom树,减少了dom操作.
虚拟DOM将DOM树转换成一个JS对象树,diff算法逐层比较,删除,添加操作,但是,如果有多个相同的元素,可能会浪费性能,所以,react和vue-for引入key值进行区分.vue过渡动画怎么实现
观察者和发布者订阅有什么区别
简述vue中diff算法原理?
v-for中为什么要用key?
组件间通信
插槽
内置指令与自定义指令
v-text、v-html、v-if、v-else、v-show、v-for、v-on、v-bind、v-model、ref、v-cloak Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
directives : {
'my-directive' : function (el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
v-my-directive='xxx'
vue3.0你知道有哪些改进?
vue高阶特性
2、Vue 在更新 DOM 时是异步执行的。
3、父组件监听子组件的生命周期// Parent.vue
<Child @hook:mounted="doSth" />
methods:{
doSth(){
//some codes here
}
}
<style scoped>
.a >>> .b { /* ... */ }
</style>
>>>
。这种情况下你可以使用 /deep/
或 ::v-deep
操作符,这两者都是 >>>
的别名,实现同样的功能。routes: [{
path: '/user/:id',
component: User,
props: (route) => ({
id: route.query.id
})
}]
const Home = () => import('../pages/Home/Home.vue');
图片预加载
所以,需要创建loading.vue文件,编写preload()方法<script>
export default {
data () {
return {
count: 0,
}
},
mounted: function() {
this.preload()
},
methods: {
preload: function() {
let imgs = [
"static/img/back.gif",
"static/img/correct.png",
"static/img/cover.gif",
"static/img/errExpress.png",
"static/img/error.png",
"static/img/ply.png",
"static/img/q1.png",
"static/img/q1a.png",
"static/img/q1b.png",
"static/img/q1c.png",
"static/img/q1d.png",
"static/img/share.png",
"static/img/start.png",
"static/img/stop.png"
]
for (let img of imgs) {
let image = new Image()
image.src = img
image.onload = () => {
this.count++
}
}
},
},
}
</script>
高阶组件
受控组件
受控组件,表单元素的修改会实时映射到状态值上,此时就可以对输入的内容进行校验.
受控组件只有继承React.Component才会有状态.
受控组件必须要在表单上使用onChange事件来绑定对应的事件.非受控组件