前端面试题库 - Vue框架篇

一、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算法

核心策略:

  1. 同层比较
  2. 双端比较
  3. 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 支持 支持
体积 较大 较小
相关推荐
孟陬2 小时前
一个小小 alias,提升开发幸福感
前端·后端·命令行
Hello--_--World2 小时前
为什么 用vite进行分包后,可以通过 浏览器强制缓存 提高性能?路由懒加载进行的分包与 vite进行的分包有什么不同?
前端·javascript·缓存·vite
三*一2 小时前
Mapbox GL JS 前端多边形分割实战:从踩坑到优雅实现
开发语言·前端·javascript·vue.js
秋収冬藏2 小时前
第一章:Dify 整体架构总览
前端
时光不负努力2 小时前
阶段 6:前端工程体系 - 企业级落地
前端
KaMeidebaby2 小时前
卡梅德生物技术快报|多肽库筛选技术构建药物递送功能肽库:流程、算法与质控体
前端·数据库·其他·百度·新浪微博
李剑一3 小时前
字节一面,考察的够全面的啊!面试官:请分阶段解释一下从输入URL到页面渲染整个链路中的关键环节和可观测点
前端
xChive3 小时前
前端请求取消:用 AbortController 从 fetch 到 axios
前端·vue.js·axios·fetch·abortcontroller
Python大数据分析@3 小时前
HTML会代替Markdown吗?为什么?
前端·html