目录
前言
MVVM模式
Vue的响应式原理
路由守卫
前言
网上有许多前端八股文,但是缺少便于理解的说明和案例,需要自行查阅资料。这篇文章我就按照面试的高频题来记录自己的理解和实操。
MVVM模式
一、三者含义
M是Model,数据模型;V是View,视图;VM是ViewModel,视图模型。
二、三者关系
VM是M和V的桥梁,可以和M、V进行双向数据传输。
1、M与VM之间,可以通过axios发送数据和接收数据。
2、V与VM之间,可以通过ES5的Object.defineProperty或ES6的Proxy方法实现数据的双向绑定。
Vue的响应式原理
一、场景
理解Vue的响应式原理能够帮助理解MVVM的运作方式
二、原理
Vue是通过Object.defineProperty()来劫持各个属性的setter和getter,结合发布者-订阅者模式,在数据变动时发布消息给订阅者,触发相应的监听回调,从而实现数据的双向绑定。
三、理解
首先我们得搞懂双向绑定具体是怎么回事。我们说A和B双向绑定了,那么A改变,B也跟着改变;B改变,A也跟着改变。这里面涉及修改(set)和获取(get)两个重要的操作(你可能会想到增加和删除这两个操作,后面再说)。而恰好ES5提供的Object.defineProperty方法可以做到。
1、Object.defineProperty的基本使用
首先我们来了解一下Object.defineProperty的基本用法。假设我们有对象A和对象B,我们希望通过A就可以调用B的某个属性,比如age。
let A = {
age: 10
};
let B = {};
// 参数含义:给person对象,添加“age”属性,后面的{}是对“age”属性的配置
Object.defineProperty(B, "age", {
// 4个常用属性:属性的值、是否能枚举、是否能修改、是否能删除
// value: 18,
// enumerable: true, // 代表age可被枚举
// writeable: true, // 代表age可被修改
// configurable: true, // 代表age可被删除
// 这函数就是getter,当“B.age”属性被获取时调用
get(){
console.log("获取: " + A.age);
return A.age;
},
// 这函数就是setter,当“B.age”属性被修改时调用
set(value){
console.log("修改:" + A.age + " 被改成 " + value);
A.age = value;
}
});
你会发现,修改B.age,A.age也会相应地变;修改A.age,B.age也会相应地变。不同的是,只有修改B.age才能触发setter和getter,也就是说通过B才能实现对A的age属性的劫持。
但通常来说,对象有很多个属性,于是就用Object.keys方法遍历一次对象A的属性,让A的所有属性都被绑定到B上。
let A = {
name: "hillbox", // String型
age: 10, // Number型
isGirl: true // Boolean型
};
let B = {};
Object.keys(A).forEach(function (key) {
Object.defineProperty(B, key, {
get() {
console.log("获取: " + A[key]);// 这里用[]而不是.是因为key是变量而不是字符串
return A[key];
},
set(value) {
console.log("修改:" + A[key] + " 被改成 " + value);
A[key] = value;
}
})
});
打印A对象、B对象,如下:
这里我们操作A对象的属性,就会修改B对象的属性,反之亦然。同样地,只有操作A的属性,才会触发getter和setter,依然是通过A才能实现对B的数据劫持。
这一小节希望读者已经知道Object.defineProperty如何使用了吧~
2、双向绑定
上面的例子,我们总需要一个现成的对象A,再将所有属性绑到另一个对象B上,这样就可以通过对象B触发setter和getter来劫持A的属性了。但是这样有些麻烦,能不能在对象A的每个属性上直接触发setter和getter,省去对象B这个“中介”呢?我有想过直接把绑定好的对象B赋值给对象A。
A = B;
结果在操作person的属性时,报了栈溢出的错误。
这是因为在获取属性的时候getter被触发,而return语句中的A[key]又会继续触发getter,无限循环调用,出不来了。那肯定得想出个解决办法。
Vue是使用Observer类来解决的。字面意思也能看出它扮演观察者的角色,和前面的B有相同的作用,但是并不会引起栈溢出错误。
let A = {
name: "hillbox", // String型
age: 10, // Number型
isGirl: true // Boolean型
};
// 创建person对象对应的“观察者”实例对象
const obs = new Observer(A);
A = obs;
// Observer类
function Observer(obj){
// 对象的所有属性
const keys = Object.keys(obj);
// 遍历每个属性
keys.forEach((key) => {
// this代表当前Observer实例
Object.defineProperty(this, key, {
get() {
console.log("获取: " + obj[key]);
return obj[key];
},
set(value) {
console.log("修改:" + obj[key] + " 被改成 " + value);
obj[key] = value;
}
})
});
}
打印A,如下:
就很奇怪,为什么对象B不行,而Observer类的实例对象obs就可以呢。
且听下回分解。
路由守卫
一、场景
我们希望路由跳转时会触发相应的事件,比如登录权限验证。
二、分类
全局守卫、独享守卫、组件守卫
1、全局守卫:
场景:为网站的所有路由配置守卫
分类:全局前置守卫、全局解析守卫、全局后置守卫
// main.js 入口文件
import router from './router'; // 引入路由
// 1、全局前置守卫,进入路由之前调用
router.beforeEach((to, from, next) => { // to是将要进入的路由,from是将要离开的路由
next();
});
// 2、全局解析守卫,在beforeRouteEnter调用之后调用
router.beforeResolve((to, from, next) => {
next();
});
// 3、全局后置守卫,进入路由之后调用
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
2、独享守卫
场景:单独为某个路由配置守卫。
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
}
}
]
})
3、组件守卫
场景:相当于给组件添加生命周期钩子
分类:路由进入前、路由更新前、路由离开前
export default{
data(){
// ...
},
beforeRouteEnter(to, from, next){
// 在路由独享守卫后调用
// 不!可!以!访问组件实例 `this`
},
beforeRouteUpdate(to, from, next){
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next){
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}