春招面试拷打实录(三):面试血泪史,面经干货分享!!!

引言

经历了几场面试的"狂轰滥炸",这才发现准备不足简直是自寻死路啊!痛定思痛之后,我决定把近几天遇到的"面试怪兽"------从刁钻问题到各种挑战,浓缩成一份精华面经。废话免谈,直接端上干货。希望这份面经能成为你求职路上的秘密武器,助你在面试战场上披荆斩棘,所向无敌!

vue3 指令

在 Vue 3 中,指令(Directives)是一种特殊的标记,用于在 DOM 元素上应用一些特殊的行为。Vue 3 提供了一些内置指令,同时也允许开发者自定义指令。

内置指令

Vue 3 提供了以下常用的内置指令:

  1. v-bind:动态绑定一个或多个属性,或一个组件 prop 到表达式。

    html 复制代码
    <img v-bind:src="imageSrc">
    <!-- 简写 -->
    <img :src="imageSrc">
  2. v-model:在表单控件或组件上创建双向数据绑定。

    html 复制代码
    <input v-model="message">
  3. v-ifv-else-ifv-else:根据表达式的值条件性地渲染元素。

    html 复制代码
    <div v-if="isVisible">Visible</div>
    <div v-else>Not Visible</div>
  4. v-for:基于源数据多次渲染元素或模板块。

    html 复制代码
    <ul>
      <li v-for="item in items" :key="item.id">{{ item.text }}</li>
    </ul>
  5. v-on:监听 DOM 事件并在触发时执行一些 JavaScript 代码。

    html 复制代码
    <button v-on:click="handleClick">Click me</button>
    <!-- 简写 -->
    <button @click="handleClick">Click me</button>
  6. v-show :根据表达式的值条件性地显示元素(通过 CSS 的 display 属性)。

    html 复制代码
    <div v-show="isVisible">Visible</div>
  7. v-text :更新元素的 textContent

    html 复制代码
    <span v-text="message"></span>
  8. v-html :更新元素的 innerHTML

    html 复制代码
    <div v-html="rawHtml"></div>
  9. v-pre:跳过这个元素和它的子元素的编译过程。

    html 复制代码
    <div v-pre>{{ this will not be compiled }}</div>
  10. v-cloak:这个指令保持在元素上直到关联组件实例结束编译。通常与 CSS 规则一起使用,以隐藏未编译的 Mustache 标签。

    html 复制代码
    <div v-cloak>
      {{ message }}
    </div>
    css 复制代码
    [v-cloak] {
      display: none;
    }
  11. v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。

    html 复制代码
    <span v-once>{{ message }}</span>
  12. v-memo(Vue3.2+):缓存模板,避免不必要更新 。

    html 复制代码
    <div v-memo="[valueA, valueB]">
        {{ expensiveCalc}}
    </div>

自定义指令

(1)** 核心原理**

自定义指令本质是一个 包含生命周期钩子的对象,Vue 会在特定时机自动调用这些钩子,并注入以下关键参数:

  • el:指令绑定的 DOM 元素(可直接操作)
  • binding:一个对象,包含以下属性:
    • value:指令的绑定值(如 v-my-directive="123" 中的 123
    • arg:指令的参数(如 v-my-directive:foo 中的 "foo"
    • modifiers:修饰符对象(如 v-my-directive.modifier 中的 { modifier: true }
js 复制代码
// 示例:打印 binding 对象
app.directive('demo', {
  mounted(el, binding) {
    console.log(binding.value)    // 输出指令值
    console.log(binding.arg)     // 输出参数
    console.log(binding.modifiers) // 输出修饰符
  }
})
(2)全局注册
javascript 复制代码
const app = Vue.createApp({});

app.directive('focus', {
  mounted(el) {
    el.focus(); // 自动聚焦
  }
});

app.mount('#app');
  • 使用:
html 复制代码
<input v-focus>
(3)局部注册(<script setup>
vue 复制代码
<script setup>
const vHighlight = {
  mounted(el, binding) {
    el.style.backgroundColor = binding.value || 'yellow'
  }
}
</script>

<template>
  <div v-highlight="'#ff0'">高亮背景</div>
</template>
(4)指令生命周期(钩子函数)
钩子 触发时机
created 元素属性初始化,但未挂载
beforeMount 绑定到 DOM 前
mounted 元素插入父 DOM 后
beforeUpdate 所在组件更新前
updated 所在组件更新后
beforeUnmount 卸载前
unmounted 卸载后
示例:自定义指令 v-color
javascript 复制代码
const app = Vue.createApp({});

app.directive('color', {
  mounted(el, binding) {
    el.style.color = binding.value;
  },
  updated(el, binding) {
    el.style.color = binding.value;
  }
});

app.mount('#app');
html 复制代码
<p v-color="'red'">This text is red.</p>

在这个例子中,v-color 指令会根据传入的值动态改变文本的颜色。

为何 v-for 需要使用 key

在 Vue.js 中,使用 v-for 渲染列表时,添加 key 属性是一个重要的最佳实践。

  • 提高性能 :当 Vue 更新视图时,它会根据 key 来识别哪些元素被修改、添加或移除。如果没有 key,Vue 会依赖其默认的算法(基于元素的位置)来比较元素,这样可能导致不必要的 DOM 操作。使用 key 后,Vue 能精确地找到每个项,从而减少不必要的 DOM 重排和重绘,提升性能。
  • 保持组件状态 :如果渲染的是一个组件(而不是普通的 DOM 元素),使用 key 可以确保组件在渲染更新时保持正确的状态。例如,如果列表中有表单输入框,每个输入框都有自己的状态,使用 key 可以确保输入框状态不会因列表排序或元素移除而丢失。
  • 避免渲染错误:key 的存在可以帮助 Vue 确保在列表更新时,元素的顺序和内容保持稳定,避免出现不稳定的渲染或顺序错乱。

路由传参

1. 动态路由参数(Params)

动态路由参数是通过路由路径中的占位符传递的。例如,定义一个动态路由:

定义动态路由

在路由配置中,使用 : 来定义动态参数:

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/user/:id', // 动态路由参数
    component: () => import('@/views/User.vue'),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;
传递参数

在跳转时,可以通过 params 传递参数:

javascript 复制代码
// 使用 router.push 跳转
this.$router.push({ name: 'user', params: { id: 123 } });

// 或者在模板中使用 <router-link>
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>
获取参数

在目标组件中,可以过 useRoutethis.$route 获取参数:

javascript 复制代码
import { useRoute } from 'vue-router';

export default {
  setup() {
    const route = useRoute();
    const userId = route.params.id; // 获取动态参数
    return { userId };
  },
};

2. 查询参数(Query)

查询参数是通过 URL 中的 ? 后面的键值对传递的。例如:/user?id=123

传递查询参数

在跳转时,可以通过 query 传递参数:

javascript 复制代码
// 使用 router.push 跳转
this.$router.push({ path: '/user', query: { id: 123 } });

// 或者在模板中使用 <router-link>
<router-link :to="{ path: '/user', query: { id: 123 }}">User</router-link>
获取查询参数

在目标组件中,可以过 useRoutethis.$route 获取查询参数:

javascript 复制代码
import { useRoute } from 'vue-router';

export default {
  setup() {
    const route = useRoute();
    const userId = route.query.id; // 获取查询参数
    return { userId };
  },
};

3. 通过 props 传递参数

Vue Router 允许将路由参数作为组件的 props 传递,这样可以更方便地在组件中使用参数。

配置路由

在路由配置中,启用 props

javascript 复制代码
const routes = [
  {
    path: '/user/:id',
    component: () => import('@/views/User.vue'),
    props: true, // 将 params 作为 props 传递
  },
  {
    path: '/profile',
    component: () => import('@/views/Profile.vue'),
    props: (route) => ({ query: route.query }), // 自定义 props
  },
];
在组件中使用 props

在目标组件中,可以直接通过 props 接收参数:

javascript 复制代码
export default {
  props: ['id'], // 接收动态路由参数
  setup(props) {
    console.log(props.id); // 使用 props
  },
};

4. 通过 state 传递参数

Vue Router 还支持通过 state 传递参数,这种方式不会在 URL 中暴露参数。

传递 state

在跳转时,可以通过 state 传递参数:

javascript 复制代码
this.$router.push({
  path: '/user',
  state: { id: 123 }, // 通过 state 传递参数
});
获取 state

在目标组件中,可以通过 history.state 获取参数:

javascript 复制代码
export default {
  setup() {
    const userId = history.state.id; // 获取 state 参数
    return { userId };
  },
};

总结

方式 特点 示例 URL
动态路由参数(Params) 参数是 URL 的一部分,适合传递必要参数 /user/123
查询参数(Query) 参数通过 ? 传递,适合传递可选参数 /user?id=123
props 传递参数 将参数作为组件的 props 传递,代码更清晰 /user/123
state 传递参数 参数不会暴露在 URL 中,适合传递敏感或不希望暴露的数据 /user
使用场景
  • 动态路由参数:适合传递必要参数,如用户 ID、文章 ID 等。
  • 查询参数:适合传递可选参数,如分页、排序等。
  • props 传递参数:适合将路由参数与组件解耦,使代码更清晰。
  • state 传递参数:适合传递敏感数据或不希望暴露在 URL 中的数据。

template标签 会编译成什么

在 Vue 3 中,<template> 标签是一个特殊的标签,用于包裹模板内容,但它本身不会被渲染到最终的 DOM 中。Vue 的编译器会将 <template> 标签中的内容编译成 渲染函数(Render Function) ,最终生成虚拟 DOM(Virtual DOM),然后由 Vue 的运行时将其渲染为真实的 DOM。

1. 模板解析

Vue 的编译器会解析 <template> 中的内容,生成一个抽象语法树(AST,Abstract Syntax Tree)。

例如:

html 复制代码
<template>
  <div>{{ message }}</div>
</template>

生成的 AST 可能类似于:

javascript 复制代码
{
  type: 'element',
  tag: 'div',
  children: [
    {
      type: 'expression',
      content: 'message'
    }
  ]
}

2. 生成渲染函数

Vue 将 AST 转换为渲染函数。渲染函数是一个 JavaScript 函数,用于生成虚拟 DOM。

例如,上述模板会被编译为:

javascript 复制代码
function render() {
  return h('div', this.message);
}

3. 生成虚拟 DOM

渲染函数执行后,会生成虚拟 DOM 节点。虚拟 DOM 是一个轻量级的 JavaScript 对象,描述了真实 DOM 的结构。

例如:

javascript 复制代码
{
  tag: 'div',
  children: 'Hello, Vue!'
}

4. 渲染为真实 DOM

Vue 的运行时将虚拟 DOM 渲染为真实 DOM,并插入到页面中。

总结

  • Vue 的 <template> 是一个 编译时的占位符,最终会被转换为虚拟 DOM 的渲染逻辑。
  • 它不会生成真实的 DOM 节点,主要用于组织模板结构或配合指令控制渲染流程。
  • 与 HTML 标准的 <template> 标签不同,前者是 Vue 模板语法的一部分,后者是浏览器原生支持的惰性模板。

监听键盘事件

1. 使用 v-on 指令(@ 简写)

Vue 提供了 v-on 指令(或 @ 简写)来监听 DOM 事件,包括键盘事件。常用的键盘事件有:

  • keydown:按下键盘时触发。
  • keyup:松开键盘时触发。
  • keypress:按下并松开键盘时触发(不推荐使用,已被废弃)。
示例:监听 keydown 事件
html 复制代码
<template>
  <input type="text" @keydown="handleKeyDown" />
</template>

<script>
export default {
  methods: {
    handleKeyDown(event) {
      console.log('按下的键:', event.key);
      console.log('键码:', event.keyCode);
    },
  },
};
</script>

2. 监听特定按键

可以通过事件对象的 keykeyCode 属性来判断按下的具体按键。

示例:监听回车键(Enter)
html 复制代码
<template>
  <input type="text" @keyup.enter="handleEnter" />
</template>

<script>
export default {
  methods: {
    handleEnter() {
      console.log('按下了回车键');
    },
  },
};
</script>
示例:监听组合键(如 Ctrl + S
html 复制代码
<template>
  <input type="text" @keydown.ctrl.s="handleSave" />
</template>

<script>
export default {
  methods: {
    handleSave(event) {
      event.preventDefault(); // 阻止默认行为
      console.log('按下了 Ctrl + S');
    },
  },
};
</script>

3. 使用修饰符

Vue 提供了一些键盘事件的修饰符,可以更方便地监听特定按键:

  • .enter:回车键。
  • .tab:Tab 键。
  • .delete:删除键或退格键。
  • .esc:Esc 键。
  • .space:空格键。
  • .up:上箭头键。
  • .down:下箭头键。
  • .left:左箭头键。
  • .right:右箭头键。
  • .ctrl:Ctrl 键。
  • .alt:Alt 键。
  • .shift:Shift 键。
  • .meta:Windows 键或 Command 键。
示例:使用修饰符监听特定按键
html 复制代码
<template>
  <input type="text" @keyup.esc="handleEscape" />
</template>

<script>
export default {
  methods: {
    handleEscape() {
      console.log('按下了 Esc 键');
    },
  },
};
</script>

4. 全局监听键盘事件

如果需要在组件外部或全局监听键盘事件,可以直接在 mounted 钩子中添加事件监听器,在 unmounted 钩子中移除。

示例:全局监听 keydown 事件
html 复制代码
<template>
  <div>
    <p>按下任意键查看键码</p>
  </div>
</template>

<script>
export default {
  mounted() {
    window.addEventListener('keydown', this.handleGlobalKeyDown);
  },
  unmounted() {
    window.removeEventListener('keydown', this.handleGlobalKeyDown);
  },
  methods: {
    handleGlobalKeyDown(event) {
      console.log('全局按下的键:', event.key);
    },
  },
};
</script>

5. 使用第三方库

如果需要更复杂的键盘事件处理(例如监听组合键、长按等),可以使用第三方库,如:

  • Mousetrap:轻量级的键盘事件库。
  • Vueuse :Vue 组合式 API 工具库,提供了 useKeyModifieruseKeyPress 等工具。
示例:使用 Vueuse 监听键盘事件
bash 复制代码
npm install @vueuse/core
html 复制代码
<template>
  <div>
    <p>按下 Enter 键查看效果</p>
  </div>
</template>

<script>
import { useKeyPress } from '@vueuse/core';

export default {
  setup() {
    useKeyPress('Enter', () => {
      console.log('按下了 Enter 键');
    });
  },
};
</script>

总结

方法 适用场景 示例
v-on 指令 监听单个元素的键盘事件 @keydown="handleKeyDown"
修饰符 监听特定按键 @keyup.enter="handleEnter"
全局监听 监听全局键盘事件 window.addEventListener
第三方库(如 Vueuse) 复杂键盘事件处理(组合键、长按等) useKeyPress('Enter', callback)

null 和 undefined

特性 undefined null
含义 表示未定义的值 表示空值或无对象引用
类型 undefined object(历史遗留问题)
使用场景 变量未初始化、函数无返回值、属性不存在 显式表示"无"或"空"
相等性 null == undefinedtrue null === undefinedfalse
类型检查 typeof undefined"undefined" typeof null"object"

为什么 typeof null 返回 "object"

1. JavaScript 的类型标签机制

在 JavaScript 的早期实现中,值的类型信息是通过一个 类型标签 来存储的。这个标签通常是一个低位的二进制值,用于标识值的类型。

  • 对象的类型标签是 000
  • null 的二进制表示是全 0(即 00000000)。
  • 由于 null 的二进制表示与对象的类型标签相同,typeof 错误地将 null 识别为 object
2. 历史遗留问题

这个问题在 JavaScript 的早期版本中就已经存在,但由于修复这个问题可能会导致大量现有代码无法正常运行,因此 JavaScript 的标准化组织(ECMA)决定保留这一行为,以避免破坏现有的 Web 应用。

vue 的响应式api

一、Vue 2 响应式原理(基于 Object.defineProperty

javascript 复制代码
// 简化版实现原理
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`读取 ${key}: ${val}`);
      return val;
    },
    set(newVal) {
      if (newVal !== val) {
        console.log(`设置 ${key}: ${newVal}`);
        val = newVal;
      }
    }
  });
}

const data = { count: 0 };
defineReactive(data, 'count', data.count);
data.count++; // 触发 setter

局限性

  • 无法检测对象属性的添加/删除
  • 数组变异方法需要特殊处理(如 push, pop

二、Vue 3 响应式 API(基于 Proxy

Vue 3 通过 Proxy 实现更强大的响应式系统:

1. reactive

reactive 用于创建一个响应式的对象。它返回一个对象的代理(Proxy),当对象的属性被访问或修改时,Vue 可以自动追踪依赖并触发更新。

用法
javascript 复制代码
import { reactive } from 'vue';

const state = reactive({
  count: 0,
  message: 'Hello, Vue 3!',
});

console.log(state.count); // 0
state.count++; // 修改属性,触发响应式更新
特点
  • 只适用于对象(包括数组)。
  • 嵌套对象也会被递归地转换为响应式对象。

2. ref

ref 用于创建一个响应式的基本类型值 (如 numberstringboolean 等)。它返回一个包含 value 属性的对象,通过 value 属性访问或修改值。

用法
javascript 复制代码
import { ref } from 'vue';

const count = ref(0);

console.log(count.value); // 0
count.value++; // 修改值,触发响应式更新
特点
  • 适用于基本类型值。
  • 在模板中使用时,Vue 会自动解包 ref,无需通过 .value 访问。

3. computed

computed 用于创建一个计算属性。它接收一个 getter 函数,并返回一个只读的响应式引用(ref)。当依赖的响应式数据发生变化时,计算属性会自动重新计算。

用法
javascript 复制代码
import { reactive, computed } from 'vue';

const state = reactive({
  count: 0,
});

const doubleCount = computed(() => state.count * 2);

console.log(doubleCount.value); // 0
state.count++;
console.log(doubleCount.value); // 2
特点
  • 计算属性是惰性的,只有在依赖的响应式数据变化时才会重新计算。
  • 可以设置 setter 来支持双向绑定。

4. watch

watch 用于监听响应式数据的变化,并在变化时执行回调函数。

用法
javascript 复制代码
import { ref, watch } from 'vue';

const count = ref(0);

watch(count, (newValue, oldValue) => {
  console.log(`count changed from ${oldValue} to ${newValue}`);
});

count.value++; // 触发 watch 回调
特点
  • 可以监听 refreactive 对象或计算属性。
  • 支持深度监听(deep: true)。

5. watchEffect

watchEffect 用于立即执行一个函数,并自动追踪函数中使用的响应式数据。当依赖的响应式数据变化时,函数会重新执行。

用法
javascript 复制代码
import { ref, watchEffect } from 'vue';

const count = ref(0);

watchEffect(() => {
  console.log(`count is ${count.value}`);
});

count.value++; // 触发 watchEffect
特点
  • 不需要显式指定依赖,自动追踪。
  • 适合处理副作用逻辑(如异步请求、DOM 操作等)。

6. toReftoRefs
  • toRef :将响应式对象的某个属性转换为 ref
  • toRefs :将响应式对象的所有属性转换为 ref
用法
javascript 复制代码
import { reactive, toRef, toRefs } from 'vue';

const state = reactive({
  count: 0,
  message: 'Hello',
});

const countRef = toRef(state, 'count'); // 将 count 转换为 ref
const { count, message } = toRefs(state); // 将所有属性转换为 ref

console.log(countRef.value); // 0
console.log(count.value); // 0
console.log(message.value); // Hello
特点
  • 用于解构响应式对象时保持响应性。
  • 常用于组合式 API 中返回响应式数据。

7. readonly

readonly 用于创建一个只读的响应式对象。任何尝试修改只读对象的操作都会失败。

用法
javascript 复制代码
import { reactive, readonly } from 'vue';

const state = reactive({ count: 0 });
const readonlyState = readonly(state);

console.log(readonlyState.count); // 0
readonlyState.count++; // 报错:无法修改只读对象
特点
  • 适用于保护数据不被意外修改。

8. shallowReactiveshallowRef
  • shallowReactive:创建一个浅层响应式对象,只有顶层属性是响应式的。
  • shallowRef :创建一个浅层 ref,只有 .value 是响应式的。
用法
javascript 复制代码
import { shallowReactive, shallowRef } from 'vue';

const state = shallowReactive({
  count: 0,
  nested: { value: 1 }, // nested 不是响应式的
});

const countRef = shallowRef(0);
countRef.value++; // 响应式
特点
  • 适用于性能优化,避免不必要的深度响应式转换。

9. triggerRef

triggerRef 用于手动触发 shallowRef 的更新。

用法
javascript 复制代码
import { shallowRef, triggerRef } from 'vue';

const countRef = shallowRef(0);

countRef.value = 1; // 不会触发更新
triggerRef(countRef); // 手动触发更新

10. markRaw

markRaw 用于标记一个对象,使其不会被转换为响应式对象。

用法
javascript 复制代码
import { reactive, markRaw } from 'vue';

const rawObject = markRaw({ value: 1 });
const state = reactive({
  rawObject, // rawObject 不会被转换为响应式
});

三、最佳实践建议

  1. 优先选择

    • 对象/数组用 reactive
    • 基本类型用 ref
    • 组合逻辑时用 toRefs 解构
  2. 避免陷阱

    javascript 复制代码
    // 错误:直接替换 reactive 对象
    state = { ...state, newProp: 1 }; // ❌ 失去响应式
    
    // 正确:修改属性
    Object.assign(state, { newProp: 1 }); // ✅
    
    // 正确:使用 ref 包装对象
    const objRef = ref({ foo: 'bar' });
    objRef.value.foo = 'new'; // ✅
  3. 性能优化

    • 大数据量时用 shallowRef/shallowReactive
    • 频繁更新的数据用 ref 而非 reactive
    • 使用 markRaw 跳过不需要响应式的数据

四、与 Vue 2 的对比

特性 Vue 2 Vue 3
响应式核心 Object.defineProperty Proxy
数组监听 需要特殊处理 原生支持
新增属性 需要 Vue.set 直接添加即可响应
性能 中等 提升 1.3~2 倍

五、常见问题

Q:为什么需要 ref

  • 用于包装基本类型值(string, number, boolean
  • 统一响应式对象的访问方式(通过 .value

Q:reactiveref 如何选择?

  • 对象/数组 → reactive
  • 基本类型/需要灵活替换 → ref

Q:如何避免深层响应式?

javascript 复制代码
const shallow = shallowReactive({ nested: { data: 1 } }); 
shallow.nested.data = 2; // 不会触发响应

用过ts的哪些特性

一、核心类型系统

1. 类型注解与类型推断
typescript 复制代码
// 显式类型注解
let count: number = 0;

// 类型推断自动推导类型(推导为 string)
const message = "Hello TS"; 

// 函数参数与返回值类型
function add(x: number, y: number): number {
  return x + y;
}

应用场景

  • 强制接口参数类型安全
  • 避免因隐式类型转换导致的 bug
  • 结合 Vue 的 Prop 类型验证 (defineProps<{id: number}>())

2. 接口(Interface)与类型别名(Type Alias)
typescript 复制代码
// 接口定义对象结构
interface User {
  id: number;
  name: string;
  email?: string; // 可选属性
}

// 类型别名定义复杂类型
type ApiResponse<T> = {
  code: number;
  data: T;
  error?: string;
};

// Vue 组件 Props 类型
defineProps<{
  user: User;
  onSuccess: (result: ApiResponse<boolean>) => void;
}>();

应用场景

  • 定义 API 响应格式
  • 约束组件 Props/Emits 类型
  • 规范 Redux/Vuex 的 State 结构

3. 泛型(Generics)
typescript 复制代码
// 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 泛型接口
interface Pagination<T> {
  list: T[];
  total: number;
}

// Vue 组合式函数中的泛型
function useFetch<T>(url: string): Promise<T> {
  return fetch(url).then(res => res.json());
}

应用场景

  • 封装可复用的数据请求 Hook
  • 处理响应式数据 (Ref<T>, Reactive<T>)
  • 构建通用工具函数库

二、高级类型操作

1. 联合类型与类型守卫
typescript 复制代码
type Status = 'loading' | 'success' | 'error';

function handleStatus(status: Status) {
  if (status === 'loading') {
    // 类型守卫下自动推导为特定字面量类型
    console.log('加载中...');
  }
}

应用场景

  • Redux/Vuex 的 Action 类型区分
  • 处理 API 状态机逻辑
  • 联合类型 + 类型收窄代替枚举

2. 工具类型(Utility Types)
typescript 复制代码
interface User {
  id: number;
  name: string;
  role: 'admin' | 'user';
}

// 生成新类型
type UserPreview = Pick<User, 'id' | 'name'>;
type PartialUser = Partial<User>;
type ReadonlyUser = Readonly<User>;

// Vue 中的 emits 类型约束
const emit = defineEmits<{
  (e: 'update:name', value: string): void;
}>();

应用场景

  • 快速生成表单的 Partial 类型
  • 限制组件事件触发的参数类型
  • 避免重复定义相似类型

3. 类型断言与守卫
typescript 复制代码
// 类型断言(慎用)
const element = document.getElementById('root') as HTMLElement;

// 自定义类型守卫
function isErrorResponse(res: any): res is ApiResponse<never> {
  return res.code >= 400;
}

应用场景

  • 处理第三方库未提供类型的对象
  • 复杂运行时类型校验(如 API 响应格式验证)

三、工程化特性

1. 声明文件(.d.ts)
typescript 复制代码
// 扩展第三方库类型
declare module 'untyped-lib' {
  export function parse(input: string): Record<string, any>;
}

// 全局类型声明
declare interface Window {
  __DEV__: boolean;
}

应用场景

  • 为无类型声明的老旧库补充类型
  • 定义全局变量类型(如注入的环境变量)

2. 装饰器(Decorators)
typescript 复制代码
// 类装饰器(Vue Class Component)
@Component
class MyComp extends Vue {
  @Prop({ type: Number, required: true }) 
  readonly id!: number;
}

应用场景

  • 旧版 Vue Class Component 开发
  • NestJS 等后端框架的控制器定义

四、最新特性实践

1. 模板字面量类型(Template Literal Types)
typescript 复制代码
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiPath = `/api/v1/${string}`;

function request(method: HttpMethod, url: ApiPath) { /* ... */ }

request('POST', '/api/v1/users'); // ✅
request('GET', '/other/path');    // ❌ 类型错误

应用场景

  • 强约束 API 路径格式
  • 生成动态 CSS 类名类型

2. satisfies 运算符(TS 4.9+)
typescript 复制代码
const config = {
  theme: 'dark',
  layout: 'grid'
} satisfies Record<string, string>;

// 自动推导出 theme 和 layout 的字符串类型

高频使用总结

特性分类 使用频率 典型场景
接口 & 类型别名 ⭐⭐⭐⭐⭐ 组件 Props、API 数据结构定义
泛型 ⭐⭐⭐⭐ 工具函数、Hooks 封装
联合类型 ⭐⭐⭐⭐ 状态机、Action 类型区分
工具类型 ⭐⭐⭐ 快速生成衍生类型
类型断言 ⭐⭐ 兼容无类型库、DOM 操作
装饰器 旧版类式组件开发

避坑经验

  1. 避免过度使用 any

    优先使用 unknown + 类型守卫,或通过泛型传递类型信息

  2. 区分 interfacetype

    • 接口:扩展性强(extends),适合描述对象形状
    • 类型别名:支持联合/交叉类型,适合复杂类型组合
  3. Vue 3 TS 最佳实践

    typescript 复制代码
    // 推荐使用 defineProps 泛型
    const props = defineProps<{
      id: number;
      list: Array<{ title: string }>;
    }>();
    
    // 精确的 emit 类型
    const emit = defineEmits<{
      (e: 'update', value: number): void;
    }>();

浏览器沙箱机制

浏览器沙箱机制 是一种安全机制,用于隔离 Web 应用程序的运行环境,防止恶意代码对用户系统造成损害。

特性 说明
核心思想 隔离 Web 应用程序的运行环境,限制其访问系统资源。
作用 防止恶意代码、保护用户隐私、隔离运行环境。
实现方式 进程隔离、同源策略、CSP、权限控制、沙箱化的 iframe。
局限性 浏览器漏洞、用户操作、XSS 攻击。
增强安全性 使用最新浏览器、实施 CSP、遵循最小权限原则、用户教育。

虚拟列表

虚拟列表(Virtual List) 是一种优化长列表渲染性能的技术,通过只渲染当前可见区域的内容,减少 DOM 元素的数量,从而提升页面的渲染效率和用户体验。虚拟列表通常用于处理包含大量数据的列表(如聊天记录、表格数据、社交媒体动态等)。

1. 为什么需要虚拟列表?

在传统的列表渲染中,所有数据都会被一次性渲染到 DOM 中。当数据量很大时,会导致以下问题:

  • DOM 元素过多:大量 DOM 元素会占用大量内存,导致页面卡顿。
  • 渲染性能低下:浏览器需要处理大量的布局和绘制操作,影响页面性能。
  • 用户体验差:页面加载慢,滚动卡顿,交互不流畅。

虚拟列表通过只渲染可见区域的内容,解决了这些问题。

2. 虚拟列表的核心思想

虚拟列表的核心思想是:

  • 只渲染可见区域的内容:根据滚动位置计算当前可见的数据项,只渲染这些数据项。
  • 动态更新内容:当用户滚动时,动态更新可见区域的内容。

实现

虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域内需要的列表项,当滚动发生时,动态通过计算获得可视区域内的列表项,并将非可视区域内存在的列表项删除。

  • 计算当前可视区域起始数据索引(startIndex)
  • 计算当前可视区域结束数据索引(endIndex)
  • 计算当前可视区域的数据,并渲染到页面中
  • 计算startIndex对应的数据在整个列表中的偏移位置startOffset并设置到列表上

websocket断开了该如何解决

1. 检测 WebSocket 断开

WebSocket 提供了以下事件来检测连接状态:

  • onclose:当连接关闭时触发。
  • onerror:当发生错误时触发。
示例
javascript 复制代码
const socket = new WebSocket('wss://example.com');

socket.onclose = (event) => {
  console.log('WebSocket 断开:', event);
  // 在这里处理断开逻辑
};

socket.onerror = (error) => {
  console.error('WebSocket 错误:', error);
  // 在这里处理错误逻辑
};

2. 自动重连机制

当 WebSocket 断开时,可以通过以下方式实现自动重连:

(1) 简单重连

onclose 事件中设置重连逻辑。

javascript 复制代码
let socket;
const url = 'wss://example.com';

function connect() {
  socket = new WebSocket(url);

  socket.onopen = () => {
    console.log('WebSocket 连接成功');
  };

  socket.onclose = (event) => {
    console.log('WebSocket 断开,尝试重连...');
    setTimeout(connect, 3000); // 3 秒后重连
  };

  socket.onerror = (error) => {
    console.error('WebSocket 错误:', error);
    socket.close(); // 关闭连接,触发 onclose
  };
}

connect();
(2) 指数退避重连

为了避免频繁重连,可以使用指数退避策略(Exponential Backoff)。

javascript 复制代码
let socket;
const url = 'wss://example.com';
let reconnectDelay = 1000; // 初始重连延迟

function connect() {
  socket = new WebSocket(url);

  socket.onopen = () => {
    console.log('WebSocket 连接成功');
    reconnectDelay = 1000; // 重置重连延迟
  };

  socket.onclose = (event) => {
    console.log('WebSocket 断开,尝试重连...');
    setTimeout(connect, reconnectDelay);
    reconnectDelay *= 2; // 延迟时间翻倍
    if (reconnectDelay > 30000) {
      reconnectDelay = 30000; // 最大延迟 30 秒
    }
  };

  socket.onerror = (error) => {
    console.error('WebSocket 错误:', error);
    socket.close(); // 关闭连接,触发 onclose
  };
}

connect();

3. 处理网络问题

(1) 检测网络状态

使用 navigator.onLine 检测网络连接状态。

javascript 复制代码
window.addEventListener('online', () => {
  console.log('网络已恢复');
  connect(); // 重新连接 WebSocket
});

window.addEventListener('offline', () => {
  console.log('网络已断开');
  socket.close(); // 关闭 WebSocket
});
(2) 心跳机制

通过定时发送心跳包(Ping/Pong)检测连接状态。

javascript 复制代码
let heartbeatInterval;

socket.onopen = () => {
  console.log('WebSocket 连接成功');
  heartbeatInterval = setInterval(() => {
    if (socket.readyState === WebSocket.OPEN) {
      socket.send('ping'); // 发送心跳包
    }
  }, 30000); // 每 30 秒发送一次
};

socket.onclose = () => {
  clearInterval(heartbeatInterval); // 清除心跳定时器
};

4. 服务器端处理

(1) 服务器重连

确保服务器支持客户端重连,并在客户端重连时恢复会话状态。

(2) 负载均衡

如果使用负载均衡,确保 WebSocket 连接被正确路由到同一台服务器。

(3) 连接超时

设置合理的连接超时时间,避免长时间占用资源。

5. 调试和日志

(1) 记录日志

记录 WebSocket 的连接、断开和错误信息,便于排查问题。

javascript 复制代码
socket.onclose = (event) => {
  console.log('WebSocket 断开:', event.code, event.reason);
};

socket.onerror = (error) => {
  console.error('WebSocket 错误:', error);
};
(2) 调试工具

使用浏览器开发者工具(如 Chrome DevTools)查看 WebSocket 的连接状态和数据传输。

6. 总结

方法 说明
检测断开 使用 oncloseonerror 事件检测连接状态。
自动重连 onclose 中实现重连逻辑,支持简单重连和指数退避重连。
处理网络问题 检测网络状态,使用心跳机制保持连接。
服务器端处理 确保服务器支持重连,设置合理的超时时间。
调试和日志 记录日志,使用开发者工具调试 WebSocket 连接。

组件销毁

在 Vue.js 中,组件销毁是指组件实例从 DOM 中移除并释放资源的过程。组件销毁时,Vue 会触发一系列生命周期钩子,开发者可以在这些钩子中执行清理操作(如取消定时器、解绑事件、释放内存等)。

1. 组件销毁的生命周期钩子

Vue 提供了以下生命周期钩子,用于处理组件销毁时的逻辑:

(1) beforeDestroy
  • 在组件销毁之前调用。
  • 适合执行一些清理操作,如取消定时器、解绑事件等。
(2) destroyed
  • 在组件销毁之后调用。
  • 此时,组件已经从 DOM 中移除,所有的事件监听器和子组件也已被销毁。

2. 组件销毁的常见场景

(1) 路由切换

当使用 Vue Router 切换路由时,当前组件会被销毁。

(2) 条件渲染

当使用 v-ifv-show 控制组件显示时,条件为 false 时组件会被销毁。

(3) 动态组件

当使用 <component :is="..."> 切换动态组件时,旧组件会被销毁。

(4) 手动销毁

通过 $destroy 方法手动销毁组件。

3. 组件销毁时的清理操作

(1) 取消定时器

如果组件中设置了定时器(如 setInterval),需要在组件销毁时清除,否则会导致内存泄漏。

javascript 复制代码
export default {
  data() {
    return {
      timer: null,
    };
  },
  mounted() {
    this.timer = setInterval(() => {
      console.log('定时器运行中...');
    }, 1000);
  },
  beforeDestroy() {
    clearInterval(this.timer); // 清除定时器
  },
};
(2) 解绑事件

如果组件中绑定了全局事件(如 window.addEventListener),需要在组件销毁时解绑。

javascript 复制代码
export default {
  mounted() {
    window.addEventListener('resize', this.handleResize);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize); // 解绑事件
  },
  methods: {
    handleResize() {
      console.log('窗口大小改变');
    },
  },
};
(3) 取消异步请求

如果组件中发起了异步请求(如 Axios),可以在组件销毁时取消请求。

javascript 复制代码
import axios from 'axios';

export default {
  data() {
    return {
      cancelToken: null,
    };
  },
  methods: {
    fetchData() {
      this.cancelToken = axios.CancelToken.source();
      axios.get('/api/data', {
        cancelToken: this.cancelToken.token,
      });
    },
  },
  beforeDestroy() {
    if (this.cancelToken) {
      this.cancelToken.cancel('组件销毁,取消请求'); // 取消请求
    }
  },
};
(4) 释放资源

如果组件中使用了第三方库或创建了对象,需要在组件销毁时释放资源。

javascript 复制代码
export default {
  mounted() {
    this.chart = new Chart(...); // 初始化图表
  },
  beforeDestroy() {
    this.chart.destroy(); // 销毁图表
  },
};

4. 手动销毁组件

可以通过 $destroy 方法手动销毁组件。

javascript 复制代码
export default {
  methods: {
    destroyComponent() {
      this.$destroy(); // 手动销毁组件
    },
  },
};

5. 总结

场景 说明
生命周期钩子 beforeDestroydestroyed 用于处理组件销毁逻辑。
常见场景 路由切换、条件渲染、动态组件、手动销毁。
清理操作 取消定时器、解绑事件、取消异步请求、释放资源。
手动销毁 使用 $destroy 方法手动销毁组件。

路由守卫

路由守卫的使用

(1) 全局前置守卫(beforeEach

在路由导航之前进行权限验证。

javascript 复制代码
const router = new VueRouter({ ... });

router.beforeEach((to, from, next) => {
  const isAuthenticated = checkAuth(); // 检查用户是否登录
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login'); // 跳转到登录页
  } else {
    next(); // 继续导航
  }
});
(2) 全局解析守卫(beforeResolve

确保所有数据已加载后再进入路由。

javascript 复制代码
router.beforeResolve((to, from, next) => {
  if (to.meta.requiresData) {
    fetchData().then(() => next()); // 加载数据
  } else {
    next();
  }
});
(3) 全局后置钩子(afterEach

用于页面访问统计或日志记录。

javascript 复制代码
router.afterEach((to, from) => {
  logPageView(to.path); // 记录页面访问
});
(4) 路由独享守卫(beforeEnter

在进入特定路由之前触发。

javascript 复制代码
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      const isAdmin = checkAdmin(); // 检查用户是否是管理员
      if (isAdmin) {
        next();
      } else {
        next('/403'); // 跳转到无权限页面
      }
    },
  },
];
(5) 组件内守卫

在组件内部定义路由守卫。

javascript 复制代码
export default {
  beforeRouteEnter(to, from, next) {
    // 在进入组件之前触发
    next((vm) => {
      // 可以访问组件实例 `vm`
    });
  },
  beforeRouteUpdate(to, from, next) {
    // 在当前路由改变但组件复用时触发
    next();
  },
  beforeRouteLeave(to, from, next) {
    // 在离开组件之前触发
    const confirmLeave = confirm('确定离开吗?');
    if (confirmLeave) {
      next();
    } else {
      next(false); // 取消导航
    }
  },
};

路由守卫的参数

路由守卫的回调函数接收以下参数:

  • to:目标路由对象。

  • from:当前路由对象。

  • next:控制导航行为的函数。

    • next():继续导航。
    • next(false):取消导航。
    • next('/path'):跳转到指定路径。
    • next(error):导航失败,触发错误。

路由守卫的应用场景

(1) 登录验证

在全局前置守卫中检查用户是否登录。

javascript 复制代码
router.beforeEach((to, from, next) => {
  const isAuthenticated = checkAuth();
  if (to.meta.requiresAuth && !isAuthenticated) {
    next('/login');
  } else {
    next();
  }
});
(2) 权限控制

在路由独享守卫中检查用户权限。

javascript 复制代码
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter: (to, from, next) => {
      const isAdmin = checkAdmin();
      if (isAdmin) {
        next();
      } else {
        next('/403');
      }
    },
  },
];
(3) 数据预加载

在组件内守卫中加载数据。

javascript 复制代码
export default {
  beforeRouteEnter(to, from, next) {
    fetchData().then((data) => {
      next((vm) => {
        vm.data = data; // 将数据传递给组件实例
      });
    });
  },
};
(4) 页面访问统计

在全局后置钩子中记录页面访问。

javascript 复制代码
router.afterEach((to, from) => {
  logPageView(to.path);
});

实现三栏布局

1. 使用浮动(Float)

通过 float 属性实现三栏布局,左右两栏固定宽度,中间栏自适应。

HTML 结构
html 复制代码
<div class="container">
  <div class="left">左侧栏</div>
  <div class="right">右侧栏</div>
  <div class="center">中间内容</div>
</div>
CSS 样式
css 复制代码
.container {
  overflow: hidden; /* 清除浮动 */
}

.left {
  float: left;
  width: 200px;
  background-color: #f0f0f0;
}

.right {
  float: right;
  width: 200px;
  background-color: #f0f0f0;
}

.center {
  margin-left: 200px; /* 左侧栏宽度 */
  margin-right: 200px; /* 右侧栏宽度 */
  background-color: #ccc;
}

2. 使用 Flexbox

通过 flex 布局实现三栏布局,中间栏自适应宽度。

HTML 结构
html 复制代码
<div class="container">
  <div class="left">左侧栏</div>
  <div class="center">中间内容</div>
  <div class="right">右侧栏</div>
</div>
CSS 样式
css 复制代码
.container {
  display: flex;
}

.left {
  width: 200px;
  background-color: #f0f0f0;
}

.right {
  width: 200px;
  background-color: #f0f0f0;
}

.center {
  flex: 1; /* 中间栏自适应 */
  background-color: #ccc;
}

3. 使用 Grid

通过 grid 布局实现三栏布局,代码简洁且功能强大。

HTML 结构
html 复制代码
<div class="container">
  <div class="left">左侧栏</div>
  <div class="center">中间内容</div>
  <div class="right">右侧栏</div>
</div>
CSS 样式
css 复制代码
.container {
  display: grid;
  grid-template-columns: 200px 1fr 200px; /* 三列布局 */
}

.left {
  background-color: #f0f0f0;
}

.right {
  background-color: #f0f0f0;
}

.center {
  background-color: #ccc;
}

4. 使用绝对定位

通过 position: absolute 实现三栏布局,左右两栏固定宽度,中间栏自适应。

HTML 结构
html 复制代码
<div class="container">
  <div class="left">左侧栏</div>
  <div class="center">中间内容</div>
  <div class="right">右侧栏</div>
</div>
CSS 样式
css 复制代码
.container {
  position: relative;
}

.left {
  position: absolute;
  left: 0;
  width: 200px;
  background-color: #f0f0f0;
}

.right {
  position: absolute;
  right: 0;
  width: 200px;
  background-color: #f0f0f0;
}

.center {
  margin-left: 200px; /* 左侧栏宽度 */
  margin-right: 200px; /* 右侧栏宽度 */
  background-color: #ccc;
}

5. 使用表格布局

通过 display: table 实现三栏布局,兼容性好但灵活性较差。

HTML 结构
html 复制代码
<div class="container">
  <div class="left">左侧栏</div>
  <div class="center">中间内容</div>
  <div class="right">右侧栏</div>
</div>
CSS 样式
css 复制代码
.container {
  display: table;
  width: 100%;
}

.left, .center, .right {
  display: table-cell;
}

.left {
  width: 200px;
  background-color: #f0f0f0;
}

.right {
  width: 200px;
  background-color: #f0f0f0;
}

.center {
  background-color: #ccc;
}

6. 总结

方法 优点 缺点 适用场景
浮动(Float) 兼容性好 需要清除浮动,代码较复杂 传统布局,兼容旧浏览器
Flexbox 代码简洁,布局灵活 兼容性较差(IE9 以下不支持) 现代浏览器,响应式布局
Grid 功能强大,代码简洁 兼容性较差(IE11 部分支持) 现代浏览器,复杂布局
绝对定位 布局精确 需要手动计算间距,灵活性差 固定宽度布局
表格布局 兼容性好 语义化差,灵活性差 传统布局,兼容旧浏览器
相关推荐
没有bug.的程序员1 分钟前
Spring Boot Actuator 监控机制解析
java·前端·spring boot·spring·源码
倔强青铜三12 分钟前
苦练Python第71天:一行代码就搭出服务器?别眨眼,http.server真有这么爽!
人工智能·python·面试
倔强青铜三14 分钟前
苦练Python第70天:征服网络请求!揭开urllib.request的神秘面纱
人工智能·python·面试
倔强青铜三15 分钟前
苦练Python第72天:colorsys 模块 10 分钟入门,让你的代码瞬间“好色”!
人工智能·python·面试
OpenTiny社区33 分钟前
如何使用 TinyEditor 快速部署一个协同编辑器
前端·开源·编辑器·opentiny
IT_陈寒37 分钟前
震惊!我用JavaScript实现了Excel的这5个核心功能,同事直呼内行!
前端·人工智能·后端
前端伪大叔1 小时前
freqtrade智能挂单策略,让你的资金利用率提升 50%+
前端·javascript·后端
江城开朗的豌豆1 小时前
从“any”战士到类型高手:我的TypeScript进阶心得
前端·javascript·前端框架
麦麦大数据1 小时前
F043 vue+flask天气预测可视化系统大数据+机器学习+管理端+爬虫+超酷界面+顶级可视化水平 【黑色版】
大数据·vue.js·flask·天气预测·气温预测·天气大数据·天气可视化
红尘散仙1 小时前
TRNovel王者归来:让小说阅读"声"临其境的终端神器
前端·rust·ui kit