第一阶段
Vue的优点和缺点
优点:轻量级、组件化、虚拟DOM、渐进式、响应式、数据与视图分离
缺点:不利于SEO,首屏加载时间长、不支持IE8以下的
为什么说Vue是一个渐进式框架
通俗来说,就是想用什么就用什么,比如component、vuex、vue-router
Vue和React的异同
相同点:
- 都使用了虚拟DOM
- 都是组件化开发
- 都是单向数据流(父子组件之间,不建议子修改父传下来的数据)
- 都支持服务端渲染
不同点:
- 编码方面:Vue的是
template,React的是JSX - 数据变化方面:Vue是响应式
通过Object.defineproperty,React是手动setState - 数据绑定方面:Vue是双向绑定,React是单向绑定
- 状态管理方面:Vue的是
Vuex,React的是Redux
说一下MVVM模型?和MVC模型有何区别
MVVM模型
M——模型 Model——对应data中的数据——存放数据
V——视图 View——对应页面——展示数据
VM——视图模型 viewModel——对应Vue实例对象——操作数据,实现数据双向绑定
VM主要通过DOM Listeners 和 Data Bindings实现数据双向绑定
- 页面监听
DOM Listeners:监听页面中的数据、事件的变化,然后反馈到Model- 数据绑定
Data Bindings:将Model中的数据绑定到view
MVC模型
M——模型 Model——负责从数据库取数据的地方
V——视图 View——负责展示数据的地方
C——控制器 Controller——用户交互的地方、例如点击事件等
思想:controller 将 Model 中的数据展示到 View 上
Vue和JQuery的区别在哪?为什么放弃使用JQuery?
区别: JQuery是直接操作DOM的;在Vue中,数据和视图是分开的,Vue是直接操作数据,再通过虚拟DOM来操作真实DOM,从而影响视图
放弃原因
- 在频繁操作DOM的场景下,Vue通过虚拟DOM,可以大大提高性能
- Vue集成的一下库,vuex、vue-router可以大大提高开发效率
第二阶段
this 指向
- 在组件配置中:
data 函数、methods 中的函数、watch 中的函数、computed 中的函数;它们的 this 均是指向 【VueComponent 实例对象】
- 在 new Vue({}) 配置中
data 函数、methods 中的函数、watch 中的函数、computed 中的函数;它们的 this 均是指向 【Vue 实例对象】
使用过哪些Vue的修饰符呢?
可以看这篇文章「百毒不侵」面试官最喜欢问的13种Vue修饰符
使用过哪些Vue的内部指令呢?
为什么data要写成函数形式并且返回一个对象
为了防止数据污染,保护数据的独立性
因为一个组件可能会被多处调用比如:在浏览器同时播放两部电视剧,都是调用同一个播放组件,但是传入的数据是不一样的,那么将data写成一个函数并且返回一个对象,那就能保证每一次调用组件的时候,该组件的实例对象都有一个独立的data对象
如果写成对象形式,会怎么样
写成对象形式,如果该组件被多处调用,就意味着该组件的所有实例对象共用同一个data对象,会造成数据污染
说一下ref
通过ref="xxx"绑定DOM元素或者组件,通过this.$refs.xxx调用
- 在DOM元素上使用,引用指向的是DOM元素,可以调用DOM元素上的属性
- 在组件上使用,引用指向的是组件实例对象,可以调用其身上的属性和方法
Vue怎么实现组件通信
简述
- 父传子:父组件通过
v-bind:或简写:绑定传的数据,子组件使用props接受 - 子传父:子组件通过
$emit将数据以事件的形式发送给父组件,父组件通过v-on:或简写@接受 - 使用
ref:在父组件中,使用ref="xxx"标记子组件,然后通过this.$refs.xxx直接调用子组件中的数据和方法 - 使用
vuex
使用场景(基于我会的这4种)
- 父子通信:使用 props、$emit、ref
- 兄弟、跨级通信:使用 vuex
怎么设置动态class、动态style
<div :class="className"></div>
<div :style="styles"></div>
v-if 和 v-show 的区别
v-if 通过创建和删除DOM节点来实现DOM元素的显示和隐藏,适合不频繁切换的场景
v-show 通过控制DOM元素的样式来实现显示和隐藏,适合频繁切换的场景
介绍一下计算属性和监视属性
计算属性
-
原理:底层借助了
object.defineproperty()方法的getter()和setter(),计算已有属性得出新属性 -
优势:与 methods相比,它内部具有缓存机制,可以实现代码复用,效率更高
-
调用:初始化默认调用一次,当所依赖的属性发生变化,也会被调用
// 完整写法
computed: {
fullName: {
// 获取 fullName 属性
get() {
return this.firstName + '-' + this.lastName
},
// 修改 fullName 属性
set(value) {
}
}
}
// 简写:当计算属性只读不写才能简写,实际开发,大多数计算属性都不能修改
computed: {
fullName() {
return this.firstName + '-' + this.lastName
}
}
监视属性
- watch 监听的数据必须是
data 或 props中的 - 支持异步监听,不支持缓存
- watch 监听的函数会接收两个参数,第一个参数是最新的值,第二个参数是变化之前的值
// 完整写法
watch: {
isHot: {
immediate: true, // 是否初始执行handler函数
deep: true, // 开启深度监视
handler(newValue, oldValue) {
// 编写相关操作
}
}
}
// 简写:前提是不需要写 immediate deep 配置项
watch: {
isHot(newValue, oldValue) {
// 编写相关操作
}
}
computed 和 watch 的区别
computed 是依赖已有属性计算得出一个新属性,内部有缓存机制,可以实现复用,不能进行异步操作
watch 是监听某个属性的变化,从而执行对应的函数,无缓存机制,可以进行异步操作
使用场景
computed是多对一,如计算购物车总价:多个商品价格影响一个总价格
watch是一对多,如搜索数据:一条搜索信息,会引出多条数据
谈谈生命周期
父子组件生命周期顺序
-
渲染过程
父 beforeCreate --> 父 created --> 父 beforeMount --> 子 beforeCreate --> 子 created --> 子 beforeMount --> 子 mounted --> 父 mounted
-
子组件更新过程
父 beforeUpdate --> 子 beforeUpdate --> 子 updated --> 父 updated
-
父组件更新过程
父 beforeUpdate --> 父 updated
-
销毁过程
父 beforeDestroy --> 子 beforeDestroy --> 子 destroyed --> 父 destroyed
第三阶段
不需要响应式的数据怎么处理
在 Vue 中,会对死数据进行去响应式处理
死数据就是一些从始至终都不会发生变化的数据,比如:写死的下拉框信息、表头等。既然这些数据不会发生变化,那就不需要实现响应式,免得消耗性能
去响应式处理
// 方法一:将数据定义在 data() 的 return{} 外
data () {
this.list = {xxx}
return {
name: 'jie'
}
}
// 方法二:使用 Object.freeze()
data () {
return {
list: Object.freeze({xxx})
}
}
对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?
对象新属性无法更新视图
原因:object.defineproperty()方法没有对对象的新属性进行劫持
// 方法一:在浏览器终端
Vue.$set(obj, key, value)
// 方法二:在组件中
this.$set(this.obj, key, value)
// 方法三:创建新对象
this.obj = Object.assign({}, {
...this.obj, // 浅拷贝
new: 123
})
删除属性无法更新视图
原因:若在methods中使用 delete this.obj.xxx,是不会引起视图更新的,这是因为原生的delete不会被Vue检测到
// 方法一:在浏览器终端
Vue.$delete(obj, key)
// 方法二:在组件中
this.$delete(this.obj, key)
通过数组下标修改数组中的元素或者手动修改数组的长度,无法更新视图
原因:Vue中操作数组只能通过指定的 API,不能通过下标
Vue 采用数据劫持的方式重写了数组的7种方法:push、pop、shift、unshift、sort、reverse、splice
为什么不建议使用index作为key
-
若对数据进行:逆序添加、逆序删除等破坏顺序的操作
- 会产生没有必要的【真实 DOM】更新:界面效果没问题,但是效率低
-
如果结构中还包含输入类的 DOM:
- 会产生错误 DOM 更新:界面会出问题
举个例子:
<div v-for="(item, index) in list" :key="index">{{item.name}}</div>
list: [
{ name: '小明', id: '123' },
{ name: '小红', id: '124' },
{ name: '小花', id: '125' }
]
渲染为
<div key="0">小明</div>
<div key="1">小红</div>
<div key="2">小花</div>
现在我执行 list.unshift({ name: '小林', id: '122' })
渲染为
<div key="0">小林</div>
<div key="1">小明</div>
<div key="2">小红</div>
<div key="3">小花</div>
新旧对比
<div key="0">小明</div> <div key="0">小林</div>
<div key="1">小红</div> <div key="1">小明</div>
<div key="2">小花</div> <div key="2">小红</div>
<div key="3">小花</div>
可以看出,如果用index做key的话,其实是更新了原有的三项,并新增了小花,虽然达到了渲染目的,但是损耗性能
谈谈 nextTick
作用:nextTick 指定的回调函数会在下一次DOM节点更新之后,再执行
原因:因为 nextTick 指定的回调函数会被放到异步队列的最后面,而异步队列是依次链式调用,所以指定的回调函数才会在下一次DOM节点更新之后,再执行
使用场景:当某方法中的某些操作,需要在下一次DOM节点更新之后才执行的时候
某方法 () {
this.$nextTick(function () {
// 需要DOM节点更新之后再执行的操作
})
}
Vue的SSR是什么?有什么好处?
SSR就是服务端渲染- 基于
nodejs serve服务环境开发,所有html代码在服务端渲染 - 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
SSR首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好
修改props值,为什么控制台有时飘红,有时不飘
控制台飘红是分情况的
-
如果传递的props值是基本类型,子组件直接修改,控制台肯定报错
-
如果传递的是引用类型,子组件修改里面的属性值或某数组的元素值,控制台不会报错。
- 因为引用类型改的不是值,而是引用地址
不过,不管传递的是什么类型的数据,我们都不建议在子组件中直接修改props的值,因为这会破坏单一数据流,可能会导致数据的变化无法追踪
最终阶段
Vue的响应式是怎么实现的
整体思路是数据劫持+观察者模式
一开始通过Object.defineProperty劫持所有属性的getter()和setter(),当数据变化的时候,通知Dep依赖收集器,调用notify,通知Watcher观察者,执行对应的回调函数,从而更新视图
MVVM作为绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析编译模板指令,最终利用Watcher连接Observer,Compile,达到双向数据绑定的效果,实现响应式。
Vue的数据代理是怎么实现的
- 通过
Object.defineProperty()将data对象的所有属性都添加到Vue实例对象(vm)上 - 为每一个添加到 vm 上的属性,都指定一个专属的 getter / setter
- 通过 Vue 实例对象(vm)来代理 data 对象中对属性的操作(读 / 写)
数据代理就是:通过一个对象来代理,另一个对象中对属性的操作
我们在new一个Vue实例时,Vue做了什么
- 创建入口函数,再创建一个
数据观察者Observer和一个指令解析器Compile - Compile解析所有
DOM节点上的vue指令,提交到更新器Updater(实际上是一个对象) - Updater把数据(如
{{}},msg,@click)替换,完成页面初始化渲染; - Observer使用
Object.defineProperty劫持数据,通知依赖器Dep; - Dep中加入观察者Watcher,当数据发生变化时,通知Watcher更新;
- Watcher取到旧值和新值,在回调函数中通知Updater更新视图;
- Compile中每个指令都new了一个Watcher,用于触发Watcher的回调函数进行更新。
Vue为什么要采用异步更新
因为 Vue 本身是组件级别的更新,如果更新的时候,数据特别多、操作特别频繁,如果不采用异步更新,会导致每次更新之后都要重新渲染,极其消耗性能
Vue异步更新的原理
- 修改数据的时候,会触发所有和这个数据有关的 Watcher 进行更新
- 然后就会将相关的 Watcher 都放入异步队列中(Queue)
- 调用 nextTick 方法,将所有回调函数添加到数组 callbacks 中,执行异步任务
- 在异步队列中,根据进入先后,执行对应的函数,然后更新相应的 DOM
key 有什么用?
key 主要用于虚拟 DOM 算法中, 每个虚拟节点都有一个唯一标识Key,Vue 通过对比新旧节点的key来判断节点是否改变,用key就可以大大提高渲染效率
key 的内部原理
虚拟 DOM 中 key 的作用
key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后,Vue 进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较,比较规则如下:
对比规则
-
旧虚拟 DOM 中找到与新虚拟 DOM 相同的 key
- 若虚拟 DOM 中内容没变,直接使用之前的【真实 DOM】
- 若虚拟 DOM 中内容变了,则生成【新的虚拟 DOM】,随后替换页面之中的【真实 DOM】
-
旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key
- 创建【新的真实 DOM】,随后渲染到页面