1. vue双向绑定原理
mvvm双向绑定,采用数据劫持结合发布者订阅者模式的方式,通过Object.defineProperty()函数来劫持各个属性的setter和getter函数,在数据变化时发布消息给订阅者,触发相应的监听回调。
几个要点:
- 实现一个数据监听器observe, 能够对数据对象的每个属性进行监听,在数据更新时可拿到最新值并通知订阅者。
- 实现一个指令解析器compile, 对每个元素节点进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
- 实现一个watcher,作为连接observe和compile的桥梁,添加订阅并收集每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。
- mvvm入口函数,整合以上三者。
具体步骤:
- 需要observe对数据对象进行递归遍历,包括属性对象的属性,都加上setter和getter函数,这样给对象的某个属性赋值时,就会触发setter,那么就能监听到了数据变化了。
- compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
- watcher订阅者是observe和compile之间通信的桥梁,主要做的事情是:
- 在自身实例化时往订阅器中(Dep)中添加自己
- 自身有一个update方法
- 待属性变动dep.notice()时,能够调用自身的update方法,并处罚compile中绑定的回调,则功成身退
- MVVM作为数据绑定的入口,整合observe、compile和watcher三者,通过observe来监听自己的model数据变化,通过compile来解析编译模板指令,最终利用watcher搭起observe和compile之间的通信桥梁,达到数据变化->更新视图; 视图交互变化(input)- 数据model变更的双向绑定效果。
2. 描述下vue从初始化页面-修改数据-刷新页面UI的过程?
当Vue进入初始化阶段时,一方面Vue会遍历data中的属性,并用Object.defineProperty将其转化成getter/setter的形式,实现数据劫持(暂不谈Vue3.0的proxy); 另一方面,Vue的指令编译器compiler对元素节点的各个指令进行解析,初始化视图,并订阅watcher来更新视图,此时watcher会将自己添加到消息订阅器Dep中国,此时初始化完毕。 当数据变化时,触发Observer中setter方法,立即调用Dep.notify(), dep这个数组开始遍历所有的订阅器,并调用其update方法,Vue内部再通过diff算法,patch响应的更新完成对订阅者视图的改变。
3. 虚拟DOM实现原理
- 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
- 状态变更时,记录新树和旧树中的差异
- 你对虚拟DOM原理的理解?
4. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行Diff检测差异?
考点: Vue的变化侦原理 前置知识: 依赖收集、虚拟DOM、响应式系统 现代前端框架有两种方式侦测变化,一种pull,一种是push
pull: 其代表是React,我们可以回忆一下React是如何侦测到变化的,我们通常会用setState显示更新,然后React会进行一层层的Virtual Diff操作找出差异,然后Patch到DOM上,React从一开始就不知道到底是哪发生了变化,只知道[有变化了], 然后再进行暴力的Diff操作查找[哪发生了变化了]。 另外一个代表就是Angular的脏检查操作。
push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一旦数据发生了变化,响应式系统就会立刻得到通知。 因此Vue是一开始就知道是[在哪发生变化了]。 但是这又会产生一个问题,如果你熟悉Vue的响应式就会知道,通常一个绑定数据就需要一个Watcher,一旦我们绑定的细粒度过高就会产生大量的Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低则会导致无法精准侦测变化,因此Vue的设计是选择中等细粒度的方案,在组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件, 然后在组件内部进行Virtual Dom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的。
5. Vue中key值的作用
当Vue用v-for正在更新已渲染过的元素列表时,它默认用就地复用策略。 如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是简单的复用此处每个元素,并且确保它在特定所印象显示已被渲染过的每个元素。 key的作用主要就是为了高效的更新虚拟DOM。结合源码分析具体就是: 在patch过程中会执行patch vnode, vnode会执行updateChildren方法,它会更新所有的两个新旧子元素,在这个过程中会通过key精准判断当前循环中的两个节点是否是同一个节点,如果没有加key就会认为永远是相同节点,就会强制去更新,没有办法避免频繁更新元素,性能就会很差。 结论:
- key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,减少DOM操作量,提高性能。
- 另外,若不设置key,还可以在列表更新时引发一些隐蔽的bug。 (比方在系列排行榜中,切换tab时,替换v-for中list项,当不设置key时,会出现诸如后一个tab的list中的前三项渲染的是上一个tablist中的三项)
- vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
6. 组件的生命周期
-
- beforeCreate 和 created
-
- beforeMount 和 mounted
-
- beforeUpdate 和 updated
-
- beforeDestory 和 destoryed
-
- activated 和 deactivated 生命周期详解
7. Vue组件间的通信方式有哪些
-
- props/$emit
-
- on
-
- vuex
-
- listeners
-
- provide/inject
-
- [children 与ref vue组件的六大通信方式详细
8. watch、methods和computed的区别
- watch为了监听某个响应数据的变化。computed是自动监听依赖值的变化,从而动态返回内容。主要目的是简化模板内复杂运算。 所有区别来源用法,稚死需要动态值,就用computed; 需要知道值后执行业务逻辑采用watch
- methosd是一个方法,它可以接受参数,而computed不能,computed是可以缓存的,methods不会。 computed可以一来其他computed,甚至是其它组件的data。
9. vue中怎么重置data
使用Object.assign(), vm.options.data.call(this)可以获取组件初始状态下的data
export default {
props: {
P: Object
},
data () {
return {
A: {
a: this.methodA
},
B: this.P
};
},
methods: {
resetData () { // 更新时调用
Object.assign(this.$data, this.$options.data.call(this)); // 有问题!!!
},
methodA () {
// do sth.
},
methodB () { // 通过用户操作调用
this.A.a && this.A.a();
}
}
}
data()中若使用了this来访问props或methods,在重置options.data()的this指向,最好使用this.options.data()重置组件data时,data()里用this获取的props或method都为undefined](blog.csdn.net/mocoe/artic…)
10. 组件中写name选项有什么作用
-
- 项目使用keep-alive时,可搭配组件name进行缓存过滤
-
- DOM做递归组件时需要调用自身的nane
-
- vue-devtools调试工具里显示的组件名称是由vue中组件的name决定的
11. vue-router有哪些钩子函数
-
- 全局前置守卫 router.beforeEach
-
- 全局解析守卫 router.beforeResolve
-
- 全局后置钩子 router.afterEach
- 路由独享的守卫 beforeEnter
- 组件内守卫 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 官网vue Touter 前端路由简介以及vue-router实现原理
12. route和router的区别是什么
route是“路由信息对象”,包括path,params, hash, query,fullpath,mathced, name等路由信息参数 router是“路由实例对象”,包括了路由的跳转方法(push,replace),钩子函数等
13. 说一下Vue和React的认识,做一个简单的对比
1. 监听数据变化的实现原理不同 参考【4. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行Diff检测差异?】
- 组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件, 然后在组件内部进行Virtual Dom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的。
- React默认是通过比较引用的方式进行,如果不优化,每当应用的状态被改变时,全部子组件都会重新进行渲染,可能会导致大量不必要的VDOM的重新渲染。
- Vue不需要特别的优化就能达到很好的性能,而对于React而言,需要通过PureComponent/ShouldComponentUpdate这个生命周期方法来进行控制。 如果你的应用中,交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果你更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操作DOM。
为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变的数据,而React更强调数据的不可变。
2. 数据流的不同
- Vue中默认支持双向绑定,组件和DOM之间可以通过v-model双向绑定。但是父子组件之间,props在2.x版本是单项数据流。
- React一直提倡单向数据流,他称之为onChange/setState()模式
不过由于我们一般都会用vuex以及Redux等单项数据流的状态管理框架,因此很多时候我们感受不到这一点区别了。
3. 模板渲染方式不同 在表层上,模板的语法不同
- React是通过JSX渲染模板
- 而Vue是通过一种扩展的HTML语法进行渲染 在深层上,模板的原理不同,这才是他们本质区别:
- React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的
- Vue是在和组件JS代码分离的单独模块中,通过指令来实现的,比如条件语句就需要v-if来实现。
对于这一点,我个人比较喜欢React的做法,因为他更加纯粹更加原生,而Vue的做法显得有些独特,会把HTML弄的很乱。举个栗子,说明React的好处,react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用,但是在Vue中,由于模板中使用的数据都必须挂载this上进行一次中转,所以我们import一个组件玩了之后,还需要在components中再声明一下,这样显然是很奇怪但是又不得不这样的做法。
14 Vuex有哪几种属性
有五种,分别是state, getter, mutation, action, module
15. vue首屏加载优化
1. 把不常改变的库放到index.html中,通过CDN引入
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css" rel="stylesheet"/>
<link href="https://cdn.bootcss.com/element-ui/2.4.0/theme-chalk/index.css" rel="stylesheet"/>
<body>
<div id="APP"></div>
<script src="https"//cdn.bootcss.com/vue/2.5.16/vue.mim.js"></script>
<script src="https"//cdn.bootcss.com/vue-router/3.0.1/vue-router.mim.js"></script>
<script src="https"//cdn.bootcss.com/element-ui/2.4.0/index.js"></script>
</body>
然后找到build/webapck.base.conf.js文件,在module.export={}中添加以下代码
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
},
这样webpack就不会把vue.js, vue-router,element-ui库打包了 ,声明一下,我把main.js中对element的引入删掉了,不然我会发现打包后的app.css还是会把element的css打包进去,删掉后就没有了。 然后你打包就会发现vendor文件小了很多~
2. vue 路由的懒加载 import 或者 require 懒加载。你打包就会发现,多了很多 1.xxxxx.js;2.xxxxx.js 等等,而 vendor.xxx.js 没了,剩下 app.js 和 manifest.js,而且 app.js 还很小,我这里是 100k 多一点。
3. 不生成 map 文件 找到 config/index.js,修改为 productionSourceMap: false
4. vue 组件尽量不要全局引入 5. 使用更轻量级的工具库 6. 开启gzip压缩 这个优化是两方面的,前端将文件打包成.gz文件,然后通过nginx的配置,让浏览器直接解析.gz文件 7. 首页单独做服务端渲染
16 Vue3.0了解
17 vue-cli做了哪些事
首先需要知道vue-cli是什么?它是基于vue.js进行快速开发的完整系统,也可以理解成很多npm包的集合。 其次,vue-cli完成的功能有哪些?
- vue文件 --> js文件
- ES6t语法 --> ES5语法
- Sass,Less, Stylus --> css
- 对jpg,png, font等静态资源的处理
- 热更新
- 定义环境变量,区分dev和production模式
- …… 如果开发者需要补充或默认设置,需要在package.json同级下新建一个vue.config.js文件
18.Proxy与Object.defineProperty的优劣对比
Proxy的优势如下
- 可以直接监听对象,而非属性
- 可以直接监听数组的变化
- proxy有多达13种拦截方法,不限于apply, ownKeys,deleteProperty, has等等是Object.dedfineProperty不具备的
- Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
- Proxy作为新标准将受到浏览器厂商终点持续的性能优化,也就是传说中的新标准的性能红利
Object.defineProperty的优势如下
- 兼容性好,支持IE9 Vue3为什么选择Proxy做双向绑定?
19. 理解Vue的响应式系统
上述图片解读
- 任何一个Vue Component都有一个与之对应的Watcher实例。
- Vue的data上的属性会被添加getter和setter属性
- 当Vue Component render函数被执行的时候,data上会被触碰(touch),即被读,getter方法会被调用,此时Vue会记录此Vue Component所依赖的所有data(这一过程被称为依赖收集)
- data被改动时(主要时用户操作),即被写,setter方法会被调用,此时Vue会去通知所有依赖于此的data的组件去调用他们的render函数进行更新。
20 虚拟DOM的优劣如何
优点:
- 保证性能下限:虚拟DOM可以经过diff找出最小差异,然后批量进行patch,这种操作虽然比不上手动优化,但是比起粗暴的DOM操作性能能要好很多,因此虚拟DOM可以保证性能下限。
- 无需手动操作DOM: 虚拟DOM的diff和patch都是在一次更新中自动进行的,我们无需手动操作DOM,极大提高了开发效率。
- 跨平台: 虚拟DOM本质上时javaScript对象,而DOM与平台强相关,相比之下虚拟DOM可以进行更方便地跨平台操作,例如服务器渲染,移动端开发等等
缺点:
- 无法进行极致优化: 在一些性能要求极高地应用中,虚拟DOM无法进行针对性地极致优化,比如VScode采用直接手动操作DOM地方式进行极端地性能优化
21 虚拟DOM实现原理
- 虚拟DOM本质上时JavaScript对象,是对真实DOM地抽象
- 状态变更时,记录新树和旧树地差异
- 最后把差异更新到真正地DOM中 详情见
22. 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?
考点: Vue的变化侦测原理
前置知识: 依赖收集、虚拟DOM、响应式系统
现代前端框架尤两种方式侦测变化,一种是pull,一种是push
pull: 其代表是React,我们可以回忆一下React是如何侦测到变化的,我们通常会用到setState API显示更新, 然后React会进行一层层的Virtual Dom Diff操作找出差异,然后Patch到DOM上,React从一开始旧不知道到底哪发生了变化,只知道【有变化了】,然后再进行比较暴力的Diff操作查找【哪发生变化了】,另一个代表就是Angular的脏查操作。
push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一旦数据发生变化,响应式系统就会立刻得知,因此Vue是一开始旧知道是【哪发生变化了】,但是这又会产生一个问题,如果你熟悉Vue的响应式旧知道,同池一个绑定一个数据旧需要一个Watcher, 一旦我们的绑定的细粒度过高就会产生大量的Watcher, 这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化。 因此Vue的设计选择中等细粒度方案,在组件级别进行push侦测方式,也就是那套响应式系统; 通常我们会第一时间侦测到发生变化的组件, 然后在组件内部进行Virtual Dom Diff获取更加具体的差异, 而Virtual Dom Diff则是pull操作, Vue是push+ pull结合的方式进行变化侦测的
组件级别和组件内部的理解:Vue 对于响应式属性的更新,只会精确更新依赖收集的当前组件,而不会递归的去更新子组件,这也是它性能强大的原因之一。
<template>
<div>
{{ msg }}
<ChildComponent />
</div>
</template
我们在触发 this.msg = 'Hello, Changed~'的时候,会触发组件的更新,视图的重新渲染。
但是 这个组件其实是不会重新渲染的,这是 Vue 刻意而为之的。 为什么说 Vue 的响应式更新精确到组件级别?(原理深度解析)
23 Vue为什么没有类似于React中shouldComponentUpdate的生命周期?
考点: Vue的变化侦测原理
前置知识: 依赖收集、虚拟DOM、响应式系统
根本原因是Vue与React的变化侦测方式有所不同
React是pull的方式侦测变化,当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组 件实际上是肯定不会发生变化的,这个时候需要用shouldComponentUpdate进行手动操作来减少diff,从 而提高程序整体的性能.
Vue是pull+push的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在push的阶段并不需要手 动控制diff,而组件内部采用的diff方式实际上是可以引入类似于shouldComponentUpdate相关生命周期 的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入 shouldComponentUpdate这种手动优化的生命周期.
24. Vue中的key到底有什么用?
key 是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的 key 与旧节点进行 比对,然后超出差异
diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比 较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较 的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完 了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.
-
准确: 如果不加 key ,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下 来,会产生一系列的bug
-
快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复 杂度仅仅为O(1)