一、Vue核心原理
1. Vue的响应式原理
Vue 2.x - Object.defineProperty:
javascript
// 简化实现
class Observer {
constructor(data) {
this.walk(data);
}
walk(obj) {
Object.keys(obj).forEach(key => {
this.defineReactive(obj, key, obj[key]);
});
}
defineReactive(obj, key, value) {
const dep = new Dep(); // 依赖收集器
// 递归处理嵌套对象
if (typeof value === 'object') {
this.walk(value);
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
// 依赖收集
if (Dep.target) {
dep.depend();
}
return value;
},
set(newValue) {
if (newValue === value) return;
value = newValue;
// 新值也需要响应式处理
if (typeof newValue === 'object') {
this.walk(newValue);
}
// 通知更新
dep.notify();
}
});
}
}
// 依赖收集器
class Dep {
constructor() {
this.subs = [];
}
depend() {
if (Dep.target) {
this.subs.push(Dep.target);
}
}
notify() {
this.subs.forEach(watcher => watcher.update());
}
}
// 观察者
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get();
}
get() {
Dep.target = this;
const value = this.vm[this.exp];
Dep.target = null;
return value;
}
update() {
const newValue = this.vm[this.exp];
if (newValue !== this.value) {
this.value = newValue;
this.cb.call(this.vm, newValue);
}
}
}
// 使用示例
const data = { name: 'Alice', age: 18 };
new Observer(data);
new Watcher(data, 'name', (newValue) => {
console.log('name changed:', newValue);
});
data.name = 'Bob'; // 触发更新
Vue 3.x - Proxy:
javascript
// 简化实现
function reactive(target) {
const handler = {
get(target, key, receiver) {
// 依赖收集
track(target, key);
const result = Reflect.get(target, key, receiver);
// 嵌套对象也转为响应式
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 触发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
};
return new Proxy(target, handler);
}
// 依赖收集
const targetMap = new WeakMap();
let activeEffect = null;
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
dep.add(activeEffect);
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
dep.forEach(effect => effect());
}
}
// 副作用函数
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
// 使用示例
const state = reactive({ count: 0 });
effect(() => {
console.log('count:', state.count);
});
state.count++; // 自动触发打印
Vue 2 vs Vue 3 响应式对比:
| 特性 | Vue 2 (Object.defineProperty) | Vue 3 (Proxy) |
|---|---|---|
| 监听对象属性 | 需要遍历每个属性 | 直接代理整个对象 |
| 新增属性 | 需要Vue.set | 自动响应 |
| 删除属性 | 需要Vue.delete | 自动响应 |
| 数组变化 | 需要重写数组方法 | 自动响应 |
| 性能 | 初始化慢(递归遍历) | 初始化快(懒代理) |
| 兼容性 | IE9+ | 不支持IE |
2. Vue的模板编译原理
编译过程:
模板字符串 → 解析(parse) → AST → 转换(transform) → 代码生成(generate) → render函数
示例:
javascript
// 1. 模板
const template = `
<div id="app">
<p>{{ message }}</p>
<button @click="handleClick">Click</button>
</div>
`;
// 2. 解析为AST(抽象语法树)
const ast = {
type: 'Element',
tag: 'div',
props: [{ name: 'id', value: 'app' }],
children: [
{
type: 'Element',
tag: 'p',
children: [
{ type: 'Interpolation', content: 'message' }
]
},
{
type: 'Element',
tag: 'button',
props: [{ name: '@click', value: 'handleClick' }],
children: [{ type: 'Text', content: 'Click' }]
}
]
};
// 3. 生成render函数
function render() {
return h('div', { id: 'app' }, [
h('p', null, this.message),
h('button', { onClick: this.handleClick }, 'Click')
]);
}
优化标记:
javascript
// Vue 3的静态提升和补丁标记
// 模板
<div>
<p>静态内容</p>
<p>{{ dynamic }}</p>
</div>
// 编译后
const _hoisted_1 = h('p', null, '静态内容'); // 静态提升
function render() {
return h('div', null, [
_hoisted_1, // 复用静态节点
h('p', null, this.dynamic, 1 /* TEXT */) // 补丁标记
]);
}
3. Vue的diff算法
核心策略:
- 同层比较
- 双端比较
- key优化
javascript
// 简化的diff算法实现
function patchChildren(oldChildren, newChildren, container) {
let oldStartIdx = 0;
let oldEndIdx = oldChildren.length - 1;
let newStartIdx = 0;
let newEndIdx = newChildren.length - 1;
let oldStartVNode = oldChildren[oldStartIdx];
let oldEndVNode = oldChildren[oldEndIdx];
let newStartVNode = newChildren[newStartIdx];
let newEndVNode = newChildren[newEndIdx];
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
// 1. 旧头 vs 新头
if (isSameVNode(oldStartVNode, newStartVNode)) {
patch(oldStartVNode, newStartVNode, container);
oldStartVNode = oldChildren[++oldStartIdx];
newStartVNode = newChildren[++newStartIdx];
}
// 2. 旧尾 vs 新尾
else if (isSameVNode(oldEndVNode, newEndVNode)) {
patch(oldEndVNode, newEndVNode, container);
oldEndVNode = oldChildren[--oldEndIdx];
newEndVNode = newChildren[--newEndIdx];
}
// 3. 旧头 vs 新尾
else if (isSameVNode(oldStartVNode, newEndVNode)) {
patch(oldStartVNode, newEndVNode, container);
// 移动节点
container.insertBefore(oldStartVNode.el, oldEndVNode.el.nextSibling);
oldStartVNode = oldChildren[++oldStartIdx];
newEndVNode = newChildren[--newEndIdx];
}
// 4. 旧尾 vs 新头
else if (isSameVNode(oldEndVNode, newStartVNode)) {
patch(oldEndVNode, newStartVNode, container);
container.insertBefore(oldEndVNode.el, oldStartVNode.el);
oldEndVNode = oldChildren[--oldEndIdx];
newStartVNode = newChildren[++newStartIdx];
}
// 5. 都不匹配,使用key查找
else {
const idxInOld = findIdxInOld(newStartVNode, oldChildren);
if (idxInOld !== -1) {
patch(oldChildren[idxInOld], newStartVNode, container);
container.insertBefore(oldChildren[idxInOld].el, oldStartVNode.el);
oldChildren[idxInOld] = undefined;
} else {
// 新增节点
mount(newStartVNode, container, oldStartVNode.el);
}
newStartVNode = newChildren[++newStartIdx];
}
}
// 处理剩余节点
if (oldStartIdx > oldEndIdx) {
// 新增节点
for (let i = newStartIdx; i <= newEndIdx; i++) {
mount(newChildren[i], container);
}
} else {
// 删除节点
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
unmount(oldChildren[i]);
}
}
}
function isSameVNode(vnode1, vnode2) {
return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag;
}
二、Vue 3 Composition API
4. Composition API vs Options API
Options API(Vue 2):
javascript
export default {
data() {
return {
count: 0,
user: null
};
},
computed: {
doubleCount() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
},
async fetchUser() {
this.user = await fetch('/api/user').then(r => r.json());
}
},
mounted() {
this.fetchUser();
}
};
Composition API(Vue 3):
javascript
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const user = ref(null);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
const fetchUser = async () => {
user.value = await fetch('/api/user').then(r => r.json());
};
onMounted(() => {
fetchUser();
});
return {
count,
user,
doubleCount,
increment
};
}
};
Composition API优势:
javascript
// 1. 逻辑复用 - 组合式函数
function useCounter() {
const count = ref(0);
const increment = () => count.value++;
const decrement = () => count.value--;
return { count, increment, decrement };
}
function useFetch(url) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const fetchData = async () => {
loading.value = true;
try {
const response = await fetch(url);
data.value = await response.json();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
};
onMounted(fetchData);
return { data, loading, error, refetch: fetchData };
}
// 使用组合式函数
export default {
setup() {
const { count, increment } = useCounter();
const { data: users, loading } = useFetch('/api/users');
return { count, increment, users, loading };
}
};
// 2. 代码组织 - 按功能聚合
export default {
setup() {
// 计数功能
const count = ref(0);
const increment = () => count.value++;
// 用户功能
const users = ref([]);
const fetchUsers = async () => {
users.value = await fetch('/api/users').then(r => r.json());
};
// 搜索功能
const searchTerm = ref('');
const filteredUsers = computed(() => {
return users.value.filter(u => u.name.includes(searchTerm.value));
});
onMounted(fetchUsers);
return {
count, increment,
users, searchTerm, filteredUsers
};
}
};
5. ref、reactive、toRefs、toRef
ref - 基本类型响应式:
javascript
import { ref } from 'vue';
const count = ref(0);
console.log(count.value); // 0
count.value++; // 触发更新
// ref也可以包装对象
const user = ref({ name: 'Alice' });
user.value.name = 'Bob'; // 响应式
reactive - 对象类型响应式:
javascript
import { reactive } from 'vue';
const state = reactive({
count: 0,
user: {
name: 'Alice',
age: 18
}
});
state.count++; // 响应式
state.user.name = 'Bob'; // 响应式
// ❌ 不能直接替换整个对象
state = reactive({ count: 10 }); // 失去响应式
// ✅ 使用Object.assign
Object.assign(state, { count: 10 });
toRefs - 对象属性转ref:
javascript
import { reactive, toRefs } from 'vue';
const state = reactive({
count: 0,
name: 'Alice'
});
// ❌ 解构丢失响应式
const { count, name } = state;
// ✅ toRefs保持响应式
const { count, name } = toRefs(state);
count.value++; // 响应式
toRef - 单个属性转ref:
javascript
import { reactive, toRef } from 'vue';
const state = reactive({
count: 0,
name: 'Alice'
});
const count = toRef(state, 'count');
count.value++; // 修改会影响state.count
使用场景:
javascript
// 1. ref - 基本类型、需要重新赋值的场景
const count = ref(0);
const user = ref(null);
user.value = await fetchUser(); // 可以重新赋值
// 2. reactive - 对象、不需要重新赋值
const state = reactive({
loading: false,
data: [],
error: null
});
// 3. toRefs - 组合式函数返回
function useCounter() {
const state = reactive({
count: 0,
step: 1
});
const increment = () => {
state.count += state.step;
};
return {
...toRefs(state), // 保持响应式
increment
};
}
6. computed、watch、watchEffect
computed - 计算属性:
javascript
import { ref, computed } from 'vue';
const count = ref(0);
// 只读计算属性
const doubleCount = computed(() => count.value * 2);
// 可写计算属性
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value) {
[firstName.value, lastName.value] = value.split(' ');
}
});
watch - 监听器:
javascript
import { ref, watch } from 'vue';
const count = ref(0);
const user = ref({ name: 'Alice', age: 18 });
// 1. 监听单个ref
watch(count, (newValue, oldValue) => {
console.log(`count changed: ${oldValue} -> ${newValue}`);
});
// 2. 监听多个源
watch([count, user], ([newCount, newUser], [oldCount, oldUser]) => {
console.log('count or user changed');
});
// 3. 监听reactive对象
const state = reactive({ count: 0, name: 'Alice' });
// ❌ 监听整个对象(深度监听)
watch(state, (newValue) => {
console.log('state changed');
});
// ✅ 监听特定属性
watch(() => state.count, (newValue) => {
console.log('count changed:', newValue);
});
// 4. 配置选项
watch(
source,
callback,
{
immediate: true, // 立即执行
deep: true, // 深度监听
flush: 'post' // 回调时机:pre, post, sync
}
);
// 5. 停止监听
const stop = watch(count, callback);
stop(); // 停止监听
watchEffect - 自动依赖追踪:
javascript
import { ref, watchEffect } from 'vue';
const count = ref(0);
const user = ref({ name: 'Alice' });
// 自动追踪依赖
watchEffect(() => {
console.log(`count: ${count.value}, name: ${user.value.name}`);
});
// 等价于
watch([count, () => user.value.name], ([newCount, newName]) => {
console.log(`count: ${newCount}, name: ${newName}`);
});
// 清理副作用
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log('delayed');
}, 1000);
onCleanup(() => {
clearTimeout(timer);
});
});
对比:
| 特性 | computed | watch | watchEffect |
|---|---|---|---|
| 返回值 | 有(计算结果) | 无 | 无 |
| 依赖追踪 | 自动 | 手动指定 | 自动 |
| 执行时机 | 惰性(访问时) | 数据变化时 | 立即执行 |
| 使用场景 | 派生数据 | 副作用、异步操作 | 简单副作用 |
7. 生命周期钩子
Vue 3 Composition API生命周期:
javascript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue';
export default {
setup() {
console.log('setup执行');
onBeforeMount(() => {
console.log('挂载前');
});
onMounted(() => {
console.log('挂载后');
// DOM操作、数据请求
});
onBeforeUpdate(() => {
console.log('更新前');
});
onUpdated(() => {
console.log('更新后');
});
onBeforeUnmount(() => {
console.log('卸载前');
// 清理定时器、事件监听
});
onUnmounted(() => {
console.log('卸载后');
});
}
};
生命周期对比:
| Options API | Composition API |
|---|---|
| beforeCreate | setup() |
| created | setup() |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeUnmount | onBeforeUnmount |
| unmounted | onUnmounted |
| errorCaptured | onErrorCaptured |
| renderTracked | onRenderTracked |
| renderTriggered | onRenderTriggered |
三、Vue Router与Vuex
8. Vue Router核心原理
路由模式:
javascript
// 1. Hash模式
const router = createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
});
// URL: http://example.com/#/about
// 监听hashchange事件
// 2. History模式
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/about', component: About }
]
});
// URL: http://example.com/about
// 使用pushState/replaceState API
导航守卫:
javascript
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
// 全局后置钩子
router.afterEach((to, from) => {
document.title = to.meta.title || 'App';
});
// 路由独享守卫
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAdmin()) {
next();
} else {
next('/');
}
}
}
];
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 渲染前调用,无法访问this
next(vm => {
// 通过vm访问组件实例
});
},
beforeRouteUpdate(to, from, next) {
// 路由改变,组件复用时调用
next();
},
beforeRouteLeave(to, from, next) {
// 离开当前路由时调用
const answer = window.confirm('确定离开吗?');
if (answer) {
next();
} else {
next(false);
}
}
};
9. Vuex/Pinia状态管理
Vuex(Vue 2/3):
javascript
import { createStore } from 'vuex';
const store = createStore({
state: {
count: 0,
user: null
},
getters: {
doubleCount: state => state.count * 2,
isLoggedIn: state => !!state.user
},
mutations: {
INCREMENT(state) {
state.count++;
},
SET_USER(state, user) {
state.user = user;
}
},
actions: {
async login({ commit }, credentials) {
const user = await api.login(credentials);
commit('SET_USER', user);
}
},
modules: {
cart: {
namespaced: true,
state: { items: [] },
mutations: { ADD_ITEM(state, item) { state.items.push(item); } }
}
}
});
// 使用
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
computed: {
...mapState(['count', 'user']),
...mapGetters(['doubleCount', 'isLoggedIn'])
},
methods: {
...mapMutations(['INCREMENT']),
...mapActions(['login'])
}
};
Pinia(Vue 3推荐):
javascript
import { defineStore } from 'pinia';
// 定义store
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'Counter'
}),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++;
},
async fetchData() {
const data = await fetch('/api/data').then(r => r.json());
this.count = data.count;
}
}
});
// 使用store
import { useCounterStore } from '@/stores/counter';
export default {
setup() {
const counter = useCounterStore();
// 访问state
console.log(counter.count);
// 访问getters
console.log(counter.doubleCount);
// 调用actions
counter.increment();
// 解构(需要storeToRefs)
import { storeToRefs } from 'pinia';
const { count, name } = storeToRefs(counter);
const { increment } = counter; // actions不需要
return { count, increment };
}
};
Vuex vs Pinia:
| 特性 | Vuex | Pinia |
|---|---|---|
| TypeScript支持 | 一般 | 优秀 |
| mutations | 必需 | 不需要 |
| actions | 支持 | 支持(可同步) |
| 模块化 | 复杂 | 简单 |
| DevTools | 支持 | 支持 |
| 体积 | 较大 | 较小 |