PS:参考一些博客,整理供自己复习使用~
谈谈你对VUE的理解
vue是易用、灵活且高效的渐进式JavaScript框架。它的主要特点是MVVM模式。代码简介体积小,运行效率高,适合移动PC端开发。本身只关注UI,可以轻松引入VUE插件或其他的第三方库进行开发。
详细说下MVVM
MVVM全称为Model-View-ViewModel,Model表示数据模型层、View表示视图层、ViewModel是View和Model层的桥梁,数据绑定到ViewModel层并自动渲染到页面中,视图变化通知ViewModel层更新数据。
vue是如何实现响应式数据?
vue2.x中是采用数据劫持结合发布者-订阅者模式的方法,通过Object.defineProperty来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。但是有一个弊端,对于对象上新增的属性无能为力。
Object.defineProperty(data, 'count', {
get() {},
set() {},
})
vue3.x是通过proxy来实现的。拦截的是「修改data上的任意key」和「读取data上的任意key」。
new Proxy(data, {
get(key) { },
set(key, value) { },
})
vue是如何监听数组变化
数据就是使用object.defineProperty重新定义数组的每一项。
- 用函数劫持的方式,重写了数组方法,具体就是更改了数据的原型,更改成自己的,用户调数组的一些方法时,走的就是自己的方法,然后通知视图去更新。
- 数组里每一项可能是对象,就会对数组的每一项进行观测。(只有是对象的时候才会观测)。
vue为什么要采用异步渲染
vue是组件级更新,如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,为了性能,vue会在本轮数据更新后,在异步更新视图。核心思想是nextTick。
简单说下nextTick
异步方法,异步渲染最后一步,与JS事件循环联系紧密。主要使用宏任务微任务定义了一个异步方法,多次调用nextTick会将方法存入队列,通过异步方法清空当前队列。
父子组件生命周期调用顺序
- 渲染顺序:先父后子,完成顺序:先子后父。
- 更新顺序:父更新导致子更新,子更新完成后父更新。
- 销毁顺序:先父后子,完成顺序:先子后父。
Vue组件通信
大致以下四类
- 父子组件之前的通信
- 兄弟组件之间的通信
- 祖孙与后代组件之间的通信
- 非关系组件之间的通信
组件通信方案
- 父亲提供数据通过属性
props传递给儿子;
children.vue
props: {
// 1、字符串形式
name: String, // 接受参数类型
// 2、对象形式
age: {
type: Number, // 接受参数类型
default: 16, // 默认值为16
require: true, // 是否为必须传递的值
}
}
father.vue
<children
name="33"
age=18
/>
- 儿子通过
$on绑定父事件,再通过$emit触发自己的事件。
children.vue
this.$emit('add', good)
father.vue
<Children @add="cartAdd($event)" />
- 通过使用ref,父组件在使用子组件的时候设置ref,父组件通过设置子组件ref来获取数据
father.vue
<Children ref="foo" />
this.$refs.foo // 获取子组件实例,通过子组件实例我们就能拿到对应的数据
- EventBus,兄弟组件传值。创建一个中央事件总线EventBus,兄弟组件通过emit第二个参数为传递的数值,另一个兄弟组件通过$on监听自定义事件。
// Bus.js
class Bus {
constructor() {
this.callbacks = {}; // 存放事件的名字
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上
// 另一种方式
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
// Children1.vue
this.$bus.$emit('foo')
Children2.vue
this.$bus.$on('foo', this.handle)
- attrs与listeners,祖先传递数据给子孙,设置批量向下传属性listeners,可以通过v-bind="$attrs"传入内部组件
// child:并未在props中声明foo
<p>{{$attrs.foo}}</p>
// parent
<HelloWorld foo="foo"/>
// 给Grandson隔代传值,communication/index.vue
<Child2 msg="lalala" @some-event="onSomeEvent"></Child2>
// Child2做展开
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>
// Grandson使⽤
<div @click="$emit('some-event', 'msg from grandson')">
{{msg}}
</div>
- Provide与Inject,给祖先定义provide,返回传递的值,再给后代组件通过inject接收组件传递过来的值
// 祖先组件
provide(){
return {
foo:'foo'
}
}
// 后代组件
inject:['foo'] // 获取到祖先组件传递过来的值
$parent、$children,通过共同祖辈root搭建通信 兄弟组件
this.$parent.on('add',this.add)
另一个兄弟组件
this.$parent.emit('add')
-
Vuex
-
state用来存放共享变量的地方 -
getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值 -
mutations用来存放修改state的方法。 -
actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作
获取父子组件实例的方法:
- 父组件提供数据,子组件注入。
ref获取组件实例,调用组件的属性、方法。- 跨组件通信
Event Bus。 vuex状态管理实现通信。
Vuex工作原理
- Vuex是一个专门为Vue.js应用程序开发的状态管理模式。
- 状态自管理应用包含以下几个部分:
- state,驱动应用的数据源。
- view,以声明方式将state映射到视图。
- actions,响应在view上的用户输入导致的状态变化。
- Vuex,多组件共享状态,因-单向数据流简洁性很容易被破坏。
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
如何从真实DOM到虚拟DOM
Vue的模版编译原理,主要过程为:
- 将模版转换为AST树,AST用对象来描述真实的JS语法(将真实的DOM转换成虚拟DOM)。
- 优化树。
- 将AST树生成代码。
虚拟节点就是用一个对象来描述一个真实的DOM元素。首先将template先转成AST,AST树通过codepen生成render函数,render函数里的_c方法将它转为虚拟DOM。
DIFF算法
时间复杂度:一个树的完全Diff算法是一个时间复杂度为O(n*3),vue进行优化转化成O(n)
- 最小量更新,
key很重要,这是节点的唯一标识,告诉diff算法,在更改前后他们是否为同一个DOM节点。v-for为什么要有key,没有的话会暴力复用。加key会减少操作DOM。
- 只有是同一个虚拟节点才会进行精细化比较,否则就会暴力删除旧的,插入新的。
- 只同层比较,不会进行跨层比较。
diff算法的优化策略: 四种命中查找,四个指针
- 旧前与新前
- 旧后与新后
- 旧前与新后
- 旧后与新前
Computed和Watch
Computed:默认computed也是一个watcher具备缓存,只有当依赖的数据变化时才会计算,当数据没有变化时,它会读取缓存数据。如果一个数据依赖于其他数据,使用computed。
watch:每次都需要执行函数。watch更适用于数据变化时的异步操作。如果需要在某个数据变化时做一些事情,使用watch。
v-for和v-if不能连用
v-for比v-if的优先级更高,连用的话会把v-if的每个元素都添加以下,造成性能问题。
组件中的data为什么是函数
避免组件中的数据互相影响。同一个组件被复用多次会创建多个实例,本质上,这些实例都是同一个构造函数。如果data是一个对象的话,对象属于引用数据类型,会影响到所有的实例。为了保证组件的数据独立,要求每个组件都必须通过data函数返回一个对象作为组件的状态。
keep-alive
keep-alive可以实现组件的缓存,当组件切换时不会对当前组件进行卸载。