当前位置: 首页 > ds >正文

整理了几道前端面试题

1. 若是有两个数组ar1和ar2,求它们的并集和交集,要怎么做?

const ar1 = [1, 2, 3, 4];
const ar2 = [3, 4, 5, 6];

一、求并集 (Union)
目标: 把两个数组合并成一个新数组,新数组包含所有出现过的元素,但每个元素只出现一次。

期望的结果是:[1, 2, 3, 4, 5, 6]

最简单的方法(ES6):

利用 Set 对象自动去重的特性。

// 1. 先把两个数组合并在一起
const mergedArray = [...ar1, ...ar2]; // [1, 2, 3, 4, 3, 4, 5, 6]// 2. 将合并后的数组转换为Set,自动去掉重复的数字
const unionSet = new Set(mergedArray); // Set(6) {1, 2, 3, 4, 5, 6}// 3. 将Set变回数组
const unionArray = Array.from(unionSet); // [1, 2, 3, 4, 5, 6]// 也可以写成一行代码:
const union = Array.from(new Set([...ar1, ...ar2]));

二、求交集 (Intersection)
目标: 创建一个新数组,只包含两个数组中都出现的元素。

期望的结果是:[3, 4]

最直观的方法:

使用数组的 filter 方法,检查第一个数组的每个元素是否也存在于第二个数组中。

// 1. 遍历ar1中的每个元素
const intersectionArray = ar1.filter(item => {// 2. 检查当前元素是否存在于ar2中return ar2.includes(item);
});// 结果: [3, 4]// 也可以写成一行代码:
const intersection = ar1.filter(item => ar2.includes(item));

2. vue3和vue2相比,增加了哪些功能?

一、性能更快 (更快)

  • 响应式系统革命(引擎升级)

    • Vue 2:使用 Object.defineProperty 来监听数据变化。
      • 缺点:无法监听新增或删除的属性(所以需要 Vue.set / Vue.delete 这种特殊方法),也无法监听数组索引的变化。
    • Vue 3:使用 Proxy 来监听数据。
      • 好处:天生就能监听所有类型的变化,包括对象和数组的任何操作,性能也更好。
  • 虚拟DOM和编译优化(底盘调校)
    Vue 3 在编译模板时做了大量优化,使得更新时的计算量更少。

    • 编译时静态提升:把模板中永远不会变的部分(比如一个Logo图)「吊起来」,只在首次渲染时创建一次,之后复用,省去了每次比较和重新创建的开销。
    • Patch Flag(标记更新类型):在对比虚拟DOM时,Vue 3 会给动态元素打上一个「标记」,比如 1 代表只有文本内容会变。这样更新时就知道具体要更新哪一部分,无需全面对比,大大提升了diff效率。
    • Tree-Shaking 支持(按需引入):如果你没用某个功能(比如 v-model 指令),最终打包的代码里就不会包含它,使得项目体积更小。

二、组合式API (更强)
这是 Vue 3 最核心的开发模式变化,是为了解决大型项目中代码组织的问题。

  • Vue 2 (Options API)
    像是一个填色游戏,你必须把代码(数据、方法、计算属性等)填到规定的格子里(data, methods, computed 等选项)。

    • 缺点:一个功能的逻辑可能被拆分到多个不同的选项里,项目大了以后,阅读和维护会变得困难,需要来回滚动屏幕找代码。
  • Vue 3 (Composition API):
    你可以把同一个功能相关的所有代码(数据、逻辑、方法)集中放在一个函数里(称为「组合函数」)。

    • 好处:
      • 逻辑复用更简单:可以轻松地把一个功能逻辑抽离成一个函数,在多个组件中复用(解决了Vue 2中 mixins 带来的命名冲突、来源不清晰等问题)。

      • 代码组织更灵活:相关功能的代码紧紧挨在一起,更容易理解和维护。

三、更好的TypeScript支持 (更友好)

  • Vue 2:对 TS 的支持是「外挂式」的,需要借助 vue-class-component 等库,用起来比较别扭。

  • Vue 3:直接用 TypeScript 重写了代码库,提供了完美的类型推断。你在使用 Composition API 时,几乎能得到和写纯 TS 一样的开发体验,代码更健壮,提示也更强大。

四、其他新特性/变化

  • 根节点 (Fragment)

    • Vue 2:组件模板必须有一个根标签(比如

      包裹)。

    • Vue 3:支持多个根标签,内部会当作一个「片段」来处理。

  • 新组件:Teleport(传送)

    • 可以把组件的一部分模板「传送」到DOM中的其他位置去渲染。比如一个模态框(Modal),可以直接把它渲染到 标签下,避免被父组件的CSS样式影响。
  • 新组件:Suspense(异步组件)

    • 可以优雅地处理异步组件加载时的「等待状态」和「错误状态」,提供更好的用户体验。
  • 生命周期小调整

    • 销毁生命周期钩子改名了:beforeDestroy -> beforeUnmount,destroyed -> unmounted,名字更语义化。

3. scss中如何定义变量和函数?

一、定义变量 (Variables)

想象一下,如果你的网站有 3 个主要颜色,你在 CSS 里到处写 #3498db。有一天要改成红色,你得一个一个去改,非常麻烦。

变量就是给一个值(比如颜色、尺寸)起个名字,以后只用这个名字,修改时也只需改一个地方。

1. 如何定义:
使用 $ 符号开头,后面跟变量名和值。

// 定义变量
$primary-color: #3498db; // 主色调
$font-size-large: 24px; // 大号字体
$base-padding: 1rem; // 基础内边距
$container-width: 1200px; // 容器宽度

2. 如何使用:
在需要的地方,直接用 $变量名 来引用。

// 使用变量
.header {background-color: $primary-color; // 使用颜色变量font-size: $font-size-large;padding: $base-padding;
}.button {background-color: $primary-color;// ... 其他样式
}

编译后的CSS:

.header {background-color: #3498db;font-size: 24px;padding: 1rem;
}.button {background-color: #3498db;
}

3. 作用域:

变量有作用域的概念。在 {} 里定义的变量,只在 {} 里有效。

$global-color: red; // 全局变量.container {$local-size: 16px; // 局部变量,只在 .container 里能用color: $global-color; // 可以用全局变量font-size: $local-size; // 可以用局部变量
}.footer {color: $global-color; // 可以用全局变量// font-size: $local-size; // 这里用会报错!找不到这个变量
}

二、定义函数 (Functions)

函数就像一个小型计算器,你传一些值给它,它经过计算返回一个结果给你。SCSS 有很多内置函数,也允许你自定义函数。

1. 内置函数 (Built-in Functions):

SCSS 自带了很多实用的函数,主要是用来处理颜色、数字、字符串等。

  • 颜色函数:
$my-color: #3498db;.element {color: darken($my-color, 20%); // 颜色变深 20%background-color: lighten($my-color, 15%); // 颜色变亮 15%border-color: transparentize($my-color, 0.5); // 变透明 (50%)
}
  • 数字/字符串函数:
$width: 100px;.box {width: $width / 2; // 直接做数学运算,得到 50px// width: percentage($width / 1000); // 转换成百分比 (10%)
}

2. 自定义函数 (Custom Functions):

如果你需要重复进行一些复杂的计算,可以自己写一个函数。

使用 @function 关键字来定义,用 @return 来返回结果。

  • 例子1:计算REM单位

(假设 $base-font-size: 16px)

// 定义一个函数,将像素值转换为rem值
@function toRem($pxValue) {$remValue: ($pxValue / 16) + rem; // 计算过程@return $remValue; // 返回结果
}// 使用函数
.title {font-size: toRem(24); // 编译后 -> font-size: 1.5rem;margin-bottom: toRem(16); // 编译后 -> margin-bottom: 1rem;
}

例子2:根据亮度动态改变文字颜色

(让文字在深色背景上显示白色,在浅色背景上显示黑色)

@function getTextColor($backgroundColor) {// 使用内置函数计算背景色的亮度@if (lightness($backgroundColor) > 50) {@return #000; // 如果背景亮,返回黑色} @else {@return #fff; // 如果背景暗,返回白色}
}// 使用函数
.box-blue {background: $primary-color;color: getTextColor($primary-color); // 自动返回白色文字
}.box-yellow {background: yellow;color: getTextColor(yellow); // 自动返回黑色文字
}

4. ts中,若是想要让一个对象中的value,都能按照定义来进行,需要怎么办?

想象一下,您定义了一个“水果价格表”的类型:

type FruitPrice = {apple: number;banana: number;orange?: number; // 橘子价格可选
}

情况一:约束对象的“值”的类型(Value Type)
这是最常用的。
我们希望 apple 的值必须是 number,banana 的值也必须是 number。

方法:直接使用类型注解

const myPrice: FruitPrice = {apple: 5,    // ✅ 正确banana: 3,   // ✅ 正确orange: 4    // ✅ 正确,因为它是可选的
};const badPrice: FruitPrice = {apple: 5,banana: "3块钱", // ❌ 错误!类型 'string' 不能赋值给类型 'number'
};

通俗解释: 这就好比给这个对象 myPrice 贴上一个标签,告诉 TypeScript:“嘿,请你帮我检查一下,这个对象里的每个值,必须符合 FruitPrice 这个标签上写的规矩”。TS 就会帮我们做检查。

情况二:约束对象“必须有且仅有”定义的键(Key)

有时候,我们不仅希望值类型对,还希望对象不能多出一些奇怪的键,也不能少掉必须的键(除非标记为可选)。

方法:在变量声明时直接定义结构
TS 默认就会做这件事。如果你用上面的 : FruitPrice 方式,TS 会检查是否多了或少了键。

const badPrice: FruitPrice = {apple: 5,banana: 3,peach: 10 // ❌ 错误!“peach”不在类型“FruitPrice”中
};const badPrice2: FruitPrice = {apple: 5,
}; // ❌ 错误!缺少属性“banana”

情况三:让对象的值变成“字面量”类型(Literal Type)

有时,我们不仅希望 apple 是 number,还希望它就是一个具体的数字,比如必须是 5,不能是别的数字。

方法:使用 as const 断言

// 假设我们要求价格必须是固定的
const fixedPrice = {apple: 5 as const, // 将值断言为字面量类型 5banana: 3 as const,
};// fixedPrice.apple 的类型现在是 5,而不是 number
// fixedPrice.banana 的类型现在是 3,而不是 numberlet num: number = 10;
num = fixedPrice.apple; // ✅ 正确,数字5可以赋值给number类型
fixedPrice.apple = 10; // ❌ 错误!不能将10赋值给5

如果想整个对象都变成只读的字面量,可以:

const fixedPrice = {apple: 5,banana: 3,
} as const; // 在对象后面加 as const// 现在整个对象的所有属性都变成了只读的字面量类型

总结

要确保对象的值按定义来,主要有三种武器:

  • 类型注解 (: Type):最常用,保证值的类型和结构正确。

  • TS 本身的类型检查:默认就会检查键的多少,确保结构完整。

  • 常量断言 (as const):当你需要值不再是宽泛的类型,而是一个具体的字面量时,就用它来“锁死”值。

一个长度不满10位的字符串,若想对它前面进行补0,以达到长度10,要怎么办?

在JavaScript(也就是TypeScript)里,字符串有一个现成的“补丁”方法,叫做 .padStart()。

它的用法非常直观:
字符串.padStart(目标长度, ‘用来填充的字符’)

// 假设我们有一个短的字符串,比如是数字
let shortString = "123";
console.log(shortString); // 输出: "123"// 我们想把它补到10位,前面用0填充
let paddedString = shortString.padStart(10, '0');
console.log(paddedString); // 输出: "0000000123"

但有些特殊情况需要注意:

情况一:如果原字符串长度已经等于或超过10了怎么办?
这个方法很聪明,它会“偷懒”。如果原字符串本身长度已经达到或超过了10,那它就什么都不做,直接把原字符串返回给你。

let longString = "1234567890ABCDEFG";
let result = longString.padStart(10, '0');
console.log(result); // 输出还是: "1234567890ABCDEFG"

情况二:在TypeScript里,我们需要注意类型

let myNumber: number = 42;// 先转换成字符串,再补零
let resultString = myNumber.toString().padStart(10, '0');
console.log(resultString); // 输出: "0000000042"

数组转字符串,以及字符串转数组,都有哪些方法?

一、 数组 转 字符串 (Array → String)

1. join() 方法 (最常用、最灵活)

let fruits = ['Apple', 'Banana', 'Orange'];// 用逗号连接(最常用)
let str1 = fruits.join(); // 如果不传参数,默认就是用逗号
console.log(str1); // 输出: "Apple,Banana,Orange"// 用自定义符号连接,比如横杠
let str2 = fruits.join(' - ');
console.log(str2); // 输出: "Apple - Banana - Orange"// 直接紧密连接,中间什么都不加
let str3 = fruits.join('');
console.log(str3); // 输出: "AppleBananaOrange"

2. toString() 方法 (简单,但固定)
这个方法也会把数组转成字符串,但它固定只能用逗号连接,你不能自己选择连接符。

let fruits = ['Apple', 'Banana', 'Orange'];
let str = fruits.toString();
console.log(str); // 输出: "Apple,Banana,Orange"
// 效果和 fruits.join() 一模一样

3. JSON.stringify() (处理复杂数组)

如果数组里面不是简单的字符串,而是数字、对象、甚至嵌套数组,你想把它整个结构都变成字符串(比如要存入本地存储LocalStorage或者发给后端),就用这个方法。

let complexArray = [1, 'hello', {name: 'Bob'}, [true, false]];
let str = JSON.stringify(complexArray);
console.log(str); // 输出: "[1,"hello",{"name":"Bob"},[true,false]]"
// 注意:这个结果本身也是一个字符串

二、 字符串 转 数组 (String → Array)

1. split() 方法 (最常用、最灵活)

这是join()的逆操作。你指定一个“分隔符”,字符串就会在出现这个符号的地方“切一刀”,然后把切下来的各部分放进数组。

let fruitStr = "Apple,Banana,Orange";// 用逗号作为分隔符来切
let arr1 = fruitStr.split(',');
console.log(arr1); // 输出: ['Apple', 'Banana', 'Orange']// 也可以用其他符号,比如空格
let nameStr = "John Doe Jane Smith";
let arr2 = nameStr.split(' ');
console.log(arr2); // 输出: ['John', 'Doe', 'Jane', 'Smith']// 一个非常有用的技巧:把字符串的每一个字符都拆开
let word = "Hello";
let arr3 = word.split('');
console.log(arr3); // 输出: ['H', 'e', 'l', 'l', 'o']

2. Array.from() 方法 (处理类数组或可迭代对象)

它可以把一个类数组对象(比如字符串、arguments对象)或者可迭代对象(比如Map, Set)变成一个真正的数组。用来拆分字符串也非常方便。

let word = "Hello";// 把字符串直接转成字符数组
let arr = Array.from(word);
console.log(arr); // 输出: ['H', 'e', 'l', 'l', 'o']
// 效果和 word.split('') 一模一样

3. 展开运算符 … (现代、简洁的语法)

这是ES6的新语法,非常简洁,作用和Array.from()很像。

let word = "Hello";
let arr = [...word];
console.log(arr); // 输出: ['H', 'e', 'l', 'l', 'o']

4. JSON.parse() (处理JSON格式的字符串)

这是JSON.stringify()的逆操作。当你有一个用JSON.stringify()生成的字符串,想把它变回原来的数组(或对象)时,就用它。

let str = '[1,"hello",{"name":"Bob"},[true,false]]'; // 一个JSON字符串
let originalArray = JSON.parse(str);
console.log(originalArray); // 输出: [1, "hello", {name: "Bob"}, [true, false]]
// 现在它又变回一个真正的数组了

vue3中,如果子组件想要调用父组件的方法,要怎么办?反过来呢,如果父组件想要调用子组件的方法,又该如何?

情况一:子组件想调用父组件的方法(子 → 父)

在Vue里的实现方式是:props + emit 事件。

步骤:

爸爸(父组件): 把一个自己的方法通过 props 传给儿子,或者在家里装一个 @自定义事件 的监听器。

儿子(子组件): 在合适的时机,通过 emit 发射一个事件来“喊”爸爸。

爸爸(父组件): 听到儿子喊,就执行自己的方法。

1. 使用 Props 传递函数(类似React的方式)

<!-- 父组件 Parent.vue -->
<template><Child :onCallDad="dadMethod" />
</template><script setup>
import Child from './Child.vue';const dadMethod = () => {console.log('爸爸的方法被调用了!');
};
</script>
<!-- 子组件 Child.vue -->
<template><button @click="onCallDad()">呼叫爸爸!</button>
</template><script setup>
// 接收爸爸传过来的方法
defineProps(['onCallDad']);
</script>

2. 使用 Emit 事件(更推荐的标准Vue方式)

<!-- 父组件 Parent.vue -->
<template><!-- 监听子组件发出的 "call-dad" 事件 --><Child @call-dad="dadMethod" />
</template><script setup>
import Child from './Child.vue';const dadMethod = () => {console.log('爸爸听到呼叫,方法被执行了!');
};
</script>
<!-- 子组件 Child.vue -->
<template><!-- 点击按钮时,发射一个名为 "call-dad" 的事件 --><button @click="$emit('call-dad')">呼叫爸爸!</button>
</template><script setup>
// 在 setup 语法糖中,不需要显式定义 emits 也可以直接使用 $emit
// 但好的习惯是定义一下,让组件意图更清晰
defineEmits(['call-dad']);
</script>

小结:子调父,核心是 emit。子组件“发事件”,父组件“监听事件”。

情况二:父组件想调用子组件的方法(父 → 子)

通俗理解: 爸爸想让儿子做件事。爸爸不能直接伸手去儿子房间里操作,但他可以 “拿到儿子的引用(reference)”,然后通过这个引用来直接命令儿子。

在Vue里的实现方式是: ref 和 defineExpose。

步骤:

爸爸(父组件): 给儿子的标签贴上一个 ref “标签”,这样就拿到了儿子组件实例的引用。

儿子(子组件): 必须用 defineExpose 把自己内部的方法“暴露”出来,不然爸爸即使拿到引用也看不到里面的东西。

爸爸(父组件): 通过 [ref变量名].value.方法名 来调用儿子暴露出来的方法。

<!-- 父组件 Parent.vue -->
<template><!-- 1. 给子组件贴上 ref 标签,名字叫 childRef --><Child ref="childRef" /><button @click="callChild">爸爸命令儿子!</button>
</template><script setup>
import { ref } from 'vue';
import Child from './Child.vue';// 2. 声明一个和标签名同名的 ref
const childRef = ref(null);const callChild = () => {// 3. 通过 ref.value 访问子组件实例,并调用其暴露的方法childRef.value.sonMethod();
};
</script>
<!-- 子组件 Child.vue -->
<template><div>我是子组件</div>
</template><script setup>
import { defineExpose } from 'vue';const sonMethod = () => {console.log('儿子的方法被爸爸调用了!');
};// 最关键的一步:把方法暴露出去,爸爸才能调用到
defineExpose({sonMethod
});
</script>

小结:父调子,核心是 ref。父组件“拿引用”,子组件“暴露方法”。

ts中面向对象的特性

1. 类 (Class) - “蓝图”
通俗理解: “类”就是一张设计蓝图。比如“汽车”的蓝图,它规定了每辆汽车都应该有“颜色”、“品牌”这些属性,和“启动”、“刹车”这些方法。

class Car {// 属性 (描述特征)color: string;brand: string;// 构造函数 (根据蓝图造车时的初始化步骤)constructor(color: string, brand: string) {this.color = color;this.brand = brand;}// 方法 (描述行为)drive(): void {console.log(`The ${this.color} ${this.brand} is driving.`);}
}// 根据“蓝图”造一辆实实在在的“车”(创建对象/实例)
let myCar = new Car('red', 'Toyota');
myCar.drive(); // 输出: The red Toyota is driving.

2. 继承 (Inheritance) - “子承父业”

通俗理解: 儿子可以继承爸爸的一切,同时还可以有自己的新东西。比如“电动汽车”也是“车”,它拥有普通车的所有属性和方法(颜色、品牌、能开),但它自己还有“电池容量”这个新属性和“充电”这个新方法。

// ElectricCar 继承自 Car
class ElectricCar extends Car {batteryCapacity: number; // 新属性constructor(color: string, brand: string, batteryCapacity: number) {super(color, brand); // 必须先调用super()来执行父类的构造函数this.batteryCapacity = batteryCapacity;}charge(): void { // 新方法console.log(`Charging the ${this.brand}...`);}// 还可以重写父类的方法 (Override)drive(): void {console.log(`The ${this.color} ${this.brand} is driving silently.`);}
}let myTesla = new ElectricCar('white', 'Tesla', 100);
myTesla.drive(); // 输出: The white Tesla is driving silently. (调用的是子类重写后的方法)
myTesla.charge(); // 输出: Charging the Tesla...

3. 封装 (Encapsulation) - “访问权限”

通俗理解: 给东西加上不同的锁。有些东西是公开的(谁都能用),有些是受保护的(只给家里人用),有些是私有的(只给自己用,藏起来)。

三个访问修饰符:

  • public (默认):公开的,谁都能访问。

  • protected:受保护的,只有自己和子类能访问。

  • private:私有的,只有自己才能访问。

class Person {public name: string;      // 公开属性protected age: number;    // 受保护属性private secret: string;   // 私有属性constructor(name: string, age: number, secret: string) {this.name = name;this.age = age;this.secret = secret;}public tellSecret(): void {console.log(this.secret); // ✅ 正确:类内部可以访问私有属性}
}class Employee extends Person {showAge(): void {console.log(this.age); // ✅ 正确:子类可以访问受保护的属性// console.log(this.secret); // ❌ 错误:子类也不能访问父类的私有属性}
}let person = new Person('Alice', 30, 'I love cookies');
console.log(person.name); // ✅ 正确:公开属性
// console.log(person.age); // ❌ 错误:外部不能访问受保护的属性
// console.log(person.secret); // ❌ 错误:外部更不能访问私有属性
person.tellSecret(); // ✅ 正确:通过公共方法间接访问私有属性

4. 多态 (Polymorphism) - “同一个方法,不同表现”

通俗理解: 同样是“叫”这个方法,狗是“汪汪汪”,猫是“喵喵喵”。虽然方法名一样,但不同的子类有不同的实现方式。

class Animal {makeSound(): void {console.log('Some generic animal sound');}
}class Dog extends Animal {makeSound(): void { // 重写了父类的方法console.log('Woof! Woof!');}
}class Cat extends Animal {makeSound(): void { // 重写了父类的方法console.log('Meow! Meow!');}
}// 创建一个动物数组,里面既有狗也有猫
let animals: Animal[] = [new Dog(), new Cat()];for (let animal of animals) {animal.makeSound(); // 同一个方法,不同的表现
}
// 输出:
// Woof! Woof!
// Meow! Meow!

5. 抽象类 (Abstract Class) - “不完整的蓝图”

通俗理解: 一个规定了你“必须做什么”,但“不告诉你具体怎么做”的蓝图。它自己不能被直接用来造东西,只能被更具体的蓝图(子类)继承并实现细节。

关键词: abstract

// 这是一个“抽象”的蓝图,不能直接new Shape()
abstract class Shape {// 抽象方法:只声明,不实现。要求子类必须实现它abstract getArea(): number;// 普通方法:可以有具体实现printArea(): void {console.log(`Area: ${this.getArea()}`);}
}// 子类必须实现抽象方法
class Circle extends Shape {radius: number;constructor(radius: number) {super();this.radius = radius;}// 实现父类规定的抽象方法getArea(): number {return Math.PI * this.radius * this.radius;}
}let myCircle = new Circle(5);
myCircle.printArea(); // 输出: Area: 78.539...

6. 接口 (Interface) - “行为契约”

通俗理解: 它不关心你是什么东西,只关心你“能做什么”。它是一份合同,只要你满足了合同里规定的方法和属性,我就认为你符合要求。

// 定义一个“可飞行的”接口合同
interface Flyable {fly(): void; // 合同里规定,必须有一个叫fly的方法
}// 鸟签了这份合同,所以它必须实现fly方法
class Bird implements Flyable {fly(): void {console.log('Flying with wings');}
}// 飞机也签了这份合同,也必须实现fly方法
class Airplane implements Flyable {fly(): void {console.log('Flying with engines');}
}// 这个函数只关心你能不能飞,不关心你到底是鸟还是飞机
function letItFly(entity: Flyable) {entity.fly();
}letItFly(new Bird()); // 输出: Flying with wings
letItFly(new Airplane()); // 输出: Flying with engines

总结

  • 类 (Class):造东西的蓝图。

  • 继承 (Inheritance):子类继承父类,实现代码复用和扩展。

  • 封装 (Encapsulation):用 public、protected、private 控制访问权限,保证安全。

  • 多态 (Polymorphism):同一方法在不同子类中有不同实现。

  • 抽象类 (Abstract Class):定义规则,不实现细节,等着被继承。

  • 接口 (Interface):定义一份合同,不关心谁来实现,只关心有没有实现。

说一说vue中的插槽的种类

1. 默认插槽 (Default Slot) - “万能后备位”

通俗理解: 子组件说:“我这儿挖了一个坑,父组件你想往里填什么都行。如果你啥也没填,我就用我自己准备的默认内容把这个坑填上。”

<!-- 子组件 MyDialog.vue -->
<template><div class="dialog"><div class="dialog-header">我是标题</div><div class="dialog-body"><!-- 这就是一个“坑”,一个默认插槽 --><slot>这是默认内容,如果父组件没传东西,就显示我。</slot></div></div>
</template>
<!-- 父组件 Parent.vue -->
<template><MyDialog><!-- 往这个“坑”里填的内容 --><p>这是父组件传入的一段文字!</p><button>点击我</button><!-- 任何HTML、组件都可以塞进去 --></MyDialog><MyDialog /> <!-- 这次不传内容,子组件将显示默认的“这是默认内容...-->
</template>

特点: 一个子组件里只能有一个默认插槽。

2. 具名插槽 (Named Slots) - “对号入座”

通俗理解: 子组件说:“我身上可不止一个坑,我有好几个呢!为了不搞混,我给每个坑都起了个名字(比如header、footer)。父组件你往里填东西时,必须告诉我你要填到哪个坑里。”

<!-- 子组件 Layout.vue -->
<template><div class="container"><header><!-- 一个名叫 header 的坑 --><slot name="header"></slot></header><main><!-- 一个名叫 default 的坑 (默认插槽) --><slot>主内容区默认值</slot></main><footer><!-- 一个名叫 footer 的坑 --><slot name="footer"></slot></footer></div>
</template>
<!-- 父组件 Parent.vue -->
<template><Layout><!-- 方式一:用 template 标签 + v-slot 指令,指定要放入的插槽名 --><template v-slot:header><h1>这里是头部标题</h1></template><!-- 方式二(推荐):用简写 # 代替 v-slot: --><template #footer><p>© 2023 公司版权所有</p></template><!-- 所有没有被 template 包裹的内容,都会放入默认插槽 --><p>这里是主要内容...</p><img src="./image.jpg" alt=""></Layout>
</template>

3. 作用域插槽 (Scoped Slots) - “儿子给爹递材料”

通俗理解: 这是最强大也最难理解的一种。子组件说:“父组件,坑还是我来挖,但是填坑的材料(数据)在我手里。我把材料给你,你来决定怎么用它来装饰这个坑!”

核心: 子组件通过插槽向父组件传递数据。

代码示例:
子组件有一个列表数据,它负责循环,但希望父组件来决定每个列表项长什么样。

<!-- 子组件 TodoList.vue -->
<template><ul><!-- 循环每一项 --><li v-for="todo in todos" :key="todo.id"><!-- 把当前这一项的数据 todo 通过插槽传递出去 --><!-- 可以传递多个数据,类似组件的 props --><slot :todoItem="todo" :isDone="todo.isDone"></slot></li></ul>
</template><script setup>
defineProps({todos: Array
})
</script>
<!-- 父组件 Parent.vue -->
<template><TodoList :todos="todoList"><!-- 接收子组件传过来的数据 --><!-- 这里通过“解构”来获取传递过来的数据 --><template v-slot:default="slotProps"><!-- 现在父组件可以根据子组件的数据,自由决定渲染样式 --><span :class="{ 'finished': slotProps.todoItem.isDone }">{{ slotProps.todoItem.text }}</span></template><!-- 通常我们会用解构写法,更简洁 --><template #default="{ todoItem, isDone }"><span :class="{ 'finished': isDone }">{{ todoItem.text }} (状态: {{ isDone ? '完成' : '未完成' }})</span></template></TodoList>
</template>

特点: 实现了数据在子组件,控制权在父组件的逆转让,极大提升了组件的复用性。像 Element Plus、Vant 等UI库的表格、列表组件大量使用了作用域插槽。

http://www.xdnf.cn/news/20332.html

相关文章:

  • 字符串格式化——`vsnprintf`函数
  • 图像处理:实现多图点重叠效果
  • More Effective C++ 条款29:引用计数
  • 【完整源码+数据集+部署教程】骰子点数识别图像实例分割系统源码和数据集:改进yolo11-DCNV2
  • 【知识点讲解】模型扩展法则(Scaling Law)与计算最优模型全面解析:从入门到前沿
  • 深入了解synchronized
  • 2025世界职校技能大赛总决赛争夺赛汽车制造与维修赛道比赛资讯
  • 告别Qt Slider!用纯C++打造更轻量的TpSlider组件
  • 一文了解太阳光模拟器的汽车材料老化测试及标准解析
  • 企业级 AI Agent 开发指南:基于函数计算 FC Sandbox 方案实现类 Chat Coding AI Agent
  • 集成学习 | MATLAB基于CNN-LSTM-Adaboost多输入单输出回归预测
  • 调试技巧:Chrome DevTools 与 Node.js Inspector
  • 从零开始学大模型之大模型训练流程实践
  • Multisim14.0(五)仿真设计
  • OpenResty 和 Nginx 到底有啥区别?你真的了解吗!
  • 分布式3PC理论
  • Qt---字节数据处理QByteArray
  • 【FastDDS】Layer Transport ( 02-Transport API )
  • k8s基础练习环境搭建
  • 服务器硬盘“Unconfigured Bad“状态解决方案
  • WebSocket:实现实时通信的革命性技术
  • Iwip驱动8211FS项目——MPSOC实战1
  • 当服务器出现网卡故障时如何检测网卡硬件故障并解决?
  • Grizzly_高性能 Java 网络应用框架深度解析
  • 基于智能合约实现非托管支付
  • Qt添加图标资源
  • conda配置pytorch虚拟环境
  • git cherry-pick 用法
  • dpdk example
  • 自动化流水线