Q、 简述一下对vue的mvvm模型的理解
mvvm:model-view-viewmodel,而vue实现了一套响应式系统实现了viewmodel层监听model(data)数据,通知view层patch dom。
响应式系统:说响应式系统是因为vue的实现很像观察者/发布订阅模式,但不完全是,这点尤其在vue3.0+的源码上提现更明显
Q、 双向绑定的实现
vue2.x: 通过Object.defineProperty实现对数据的监听;
vue3.x: 通过ES6的扩展对象Proxy实现对数据的监听,原生支持,并且对数组有天然的支持,不用像vue2.x那样要重写数组原生方法来触发数据更新,速度更快,但对浏览器兼容有要求,不支持ie11浏览器;
具体流程: 在Observer对数据的劫持,添加getter setter,在setter添加上notify,数据改变,通知到Dep订阅器,并给到Watcher订阅者重新计算,然后Compile解析器更新视图;
Q、 简述一下vue生命周期
生命周期名称 | 触发条件 |
---|---|
beforeCreate | 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。 |
created | 在实例创建完成后被立即调用。但此时虚拟 DOM 还没生成,property不可用。 |
beforeMount | 虚拟 DOM 生成并且渲染前被调用。 |
mounted | 虚拟 DOM 生成并且渲染完毕后被调用 |
beforeUpdate | 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。 |
updated | 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。 |
activated | 被 keep-alive 缓存的组件激活时调用。 |
deactivated | 被 keep-alive 缓存的组件停用时调用。 |
beforeDestroy | 实例销毁之前调用。在这一步,实例仍然完全可用。 |
destroyed | 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。 |
errorCaptured | 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。 |
vue3.x: 舍弃了beforeCreate和created这两个生命周期,使用setup方法去代替
Q、组件间通讯方法有哪些
- props、v-model向下传递
- emit广播事件 on监听事件
- provide提供和inject接收注入,用于跨层级组件.
- eventBus总线事件处理
- Vuex全局状态管理
Q、用过自定义指令吗?怎么实现一个指令
使用directive API去注册指令
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
Q、 实现一个全局toast方法
// ./ToastComponent.vue里面的代码就是普通的组件代码
import ToastComponent from "./ToastComponent.vue";
const globalToast = {
install: function (Vue, conf_options = {}) {
if (!conf_options) {
conf_options = {}
}
const ToastConstructor = Vue.extend(ToastComponent);
let loadedToasts = [];
// 生成并在body子级挂载
function toast(msg, options = {}) {
//接受全局和局部options
let propsData = Object.assign(conf_options, options, { msg: msg });
// 创建组件实例
const instance = new ToastConstructor({ propsData });
// 挂载到body下面
if (!instance.$el) {
let vm = instance.$mount();
document.body.appendChild(vm.$el);
}
loadedToasts.push(instance);
return instance;
}
// 显示与隐藏
function showLoading(msg) {
loadedToasts.push(toast(msg, { type: "loading" }));
}
function hideLoading() {
loadedToasts.forEach(ins => {
ins.closeToast();
})
}
// 全局注册方法
Vue.prototype.$toast = toast;
Vue.prototype.$showLoading = showLoading;
Vue.prototype.$hideLoading = hideLoading;
}
}
// 全局注册
Vue.use(globalToast);
Q、 Virtual DOM(虚拟dom)为什么比普通dom渲染快
真实dom的每一次操作方法调用,都会触发浏览器的生成的一系列操作(回流、重绘),特别是多个数据多个dom更新多次操作dom是很费性能
而相比dom操作,js的计算性能消耗是极其小的,通过对patch(diff算法)的优化,更新数据变化部分,它保证了不管你的数据变化多少,每次重绘的性能都可以接受。保证了性能的下限。并保证不同平台的迁移成本,可以在其他平台使用,小程序、app、桌面端。
下面vue作者尤大的话:
Q、 diff算法
掘金 - 请阐述vue的diff算法
知乎 - vue3.0 diff算法详解(超详细)
Q、 vue.nextTick实现
nextTick本质上是一个微任务函数,在下次DOM更新循环结束之后执行的延迟回调,如果所有微任务的方法都不支持才会采用setTimeout回调。
Vue在更新dom时是异步执行的。只要监听到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用。
Q、watch与computed的区别
computed是一个属性,vue实例初始化属性的时候会运行computed属性的方法,收集方法内的属性进行监听,当监听的属性发生变化时,通知这个计算属性更新。
watch相当于添加一个观察者,但监听的属性变化时,触发watch定义的方法;