首页 > 其他分享 >中年被裁,记录下这段时间的心路历程,内含前端面试题和面经

中年被裁,记录下这段时间的心路历程,内含前端面试题和面经

时间:2024-09-25 13:55:36浏览次数:11  
标签:面试题 Vue const 函数 面经 被裁 Promise 组件 路由

前言

真正的转变都是痛苦且无声的。

大家好啊,好久不见,停更了一个月了,最近确实没时间更新我的公益服游戏,这段时间我经历了工作被裁员,学习复习,面试找工作,到最终找到工作。想把这段时间我的心路历程和面试题面经分享出来,说不定可以帮到你。

心路历程

坐标天津,从事互联网前端开发工作,具体的公司就不提了哈,从去年开始公司的股票就一跌再跌,今年年初也开始部门架构调整,工作任务压力一下变大,到下半年的各种施压PUA,开始裁员。部门领导找我谈话,罗列了一堆有的没的理由,欲加之罪何患无辞呢,我也只是简单解释了下,最终也算是好聚好散,后面和人事谈赔偿做了一点让步,也算是能接受。就这样,在一个风雨交加的中午,我背着我的瑞士军刀包,离开了这个我工作了两年多的地方。

从 14 年毕业到 24 年,整整 10 年的 IT 生涯,其实自己挺失败的,没进过独角兽 BAT TMD大厂,一直只是底层的搬砖码农,最普通的芸芸众生而已。我在想是不是我的码农生涯就到此为止了,正好也快到 35 岁门槛儿了,要不试试铁人三项(外卖、滴滴、快递),亦或是创业三部曲(摆摊、开店、自媒体),正好自己的自媒体副业还有些收入,但一想到每月大几千的房贷,立马把我拉回了现实,还是要再拼一把,继续做牛马找工作。

先简单翻了下 Boss 招聘(说实话真的不想看,下意识想逃避),有招聘合适的岗位只有个位数,薪资感觉比两年前降了 3K 左右,最多的要求还是 Vue 框架,所以我直接把重点放到了 Vue 上,向老友海军要了一些前端面试题,规划好时间,一边学习一边做笔记,这点很重要,一定要写下来,写成自己的理解,整理好方便后面查看和记忆,不建议自己敲代码写项目,因为时间已经来不及了。

说干就干,接下来的一周我就过上了相妻教子的生活,早晚接孩子上下学,白天开始疯狂刷面试题,偶尔晚上失眠也会起来刷题。不敢让自己停下来,乱了心神胡思乱想就很难进入状态,适当的压力还好,可以让自己提神保持专注,但是我也知道这种状态坚持不了多久,唯恐泄了气。

WechatIMG32.jpg

于是我听从军师老婆的建议,第二周开始改简历投简历,进入边面试边学习的状态。但市场环境真的给我上了一课,原本打算投一两个公司,一家一家的面,怕复习不到位,错失了面试机会。可我把能投的都投了,不管大小公司,稍远的一些,最终只收到了一家外包驻场开发的面试机会,直觉告诉我这可能是我唯一的机会了,所以我只能把赌注都压在这上面,针对性的做充足的准备。

面试分两轮,先是外包公司内部电话初试,这个难度不大,差不多的都会推进去,最难的在第二轮客户面试,先要进行机试,还好没有编程题,然后是现场面试,十个左右面试官(各组的组长)依次提问,当时去了十多个面试的,听说还有北京赶过来的,其他人都是面试十五分钟就出来了,我面试完出来一看面试了 30 分钟…也不知道是不是自己太啰嗦了,反正咔咔就是一顿说,这时候不争取表现自己还等何时。面试完老婆开车接我告诉我这家没问题,你能进去…此刻我只想说,老婆你这嘴真是开了光了…后来初试的面试人询问我复试问题,并告诉了我,一共去了七八个前端,最终只要了我一个人…

两天后我收到了 offer 入职通知,悬着的心终于放下了,虽然降薪去了外包,到是好在离家近基本不加班,项目比较稳定,相比于其他的公司,权衡下来倒也是目前最好的选择,至少不会为生计发愁了。明天就准备入职新公司了,一切都是未知,但我相信一切都是最好的安排。

废话不多说,下面是自己这段时间准备的面试题和面经(非前端人员下面的可以跳过哈),面试题可能没有那么详细,但知识点肯定都会提到,具体的可以自己查找补充,还有就是全部手写可能有拼写错误请无视。希望能帮助到你,祝你早日跳槽或者找到满意的工作,共勉!

Js 面试题

1.防抖和节流

防抖debounce,确保在指定的时间间隔内,无论连续出发多少次事件,只有最后一次事件会在该间隔结束后执行

案例:搜索框输入

核心逻辑,重置计时器,每次事件触发时,都会重置计时器

执行时机,只有在用户停止触发事件指定时间间隔后,才会执行最后一次事件

//创建一个防抖函数,它返回一个新的函数,该函数在指定的wait时间后执行func
function debounce(func, wait) {
    //保存定时器的引用
    let timeout;
    //返回的函数时用户时机调用的函数,它包含了防抖逻辑
    return function(...args) {
        // 保存当前的this上下文
        const content = this;
        //清除之前的定时器,如果存在
        if(timeout) clearTimout(timeout)
        //设置一个新的定时器
        //当指定wait时间过后,将执行func函数
        //并将房钱的this上下文和参数传入
        timeout = setTimeout(function() {
              //执行原始函数,绑定正确的this上下文和参数
              func.apply(content, args)      
        },wait)                                                                        
    }
}

当防抖函数被触发时,首先会检查是否已经存在一个timeout(即是否有一个定时器在运行)

如果存在,表示之前有触发过防抖函数但还未执行func,此时使用clearTimeout清除之前的定时器

然后,设置一个新的timeout,如果在wait指定的时间内再次触发防抖函数,之前的定时器会被清除并重新设置,这意味着func的函数会被不断的推迟

只有当指定的时间间隔wait内没有再次触发防抖函数,timeout才会到达,此时会执行原始函数func,并且使用apply方法将存储的context和args传递给它

节流是指定的时间间隔内,不论触发多少次事件,只有第一次事件会被执行,后续事件在个间隔内都不会执行(连续触发事件,但是在n秒钟只执行第一次触发函数)

案例:按钮高频率点击

核心逻辑,单次执行,在事件间隔内只执行一次事件处理函数

忽略后续触发,在时间间隔内,后续的时间触发将被忽略

//创建一个防抖函数,它返回一个新的函数,该函数在指定的wait时间后执行func
function debounce(func, wait) {
    //保存定时器的引用
    let timeout;
    //返回的函数时用户时机调用的函数,它包含了防抖逻辑
    return function(...args) {
        // 保存当前的this上下文
        const content = this;
        //清除之前的定时器,如果存在
        if(timeout) clearTimout(timeout)
        //设置一个新的定时器
        //当指定wait时间过后,将执行func函数
        //并将房钱的this上下文和参数传入
        timeout = setTimeout(function() {
              //执行原始函数,绑定正确的this上下文和参数
              func.apply(content, args)      
        },wait)                                                                        
    }
}

func需要被节流的函数

limit表示在指定的时间间隔后,func才能再次被执行的时间

inThrottle一个布尔值,用来标记func是否处于可执行状态

context保存当前的this上下文,却在在执行func时this指向正确

args使用扩展运算符...来收集所有的参数,以便将它们传递给func

setTimeout在指定的limit时间后执行,将inThrottle重置为false,这样func就可以在下一次调用时被执行了

2.上下文Context

上下文通常指的事this所指向的对象,在不同的函数调用方式中,this的指向可能不同

1.全局上下文,在全局作用于中,this指向全局对象(浏览器中是window)

2.对象方法上下文,当一个函数作为对象的方法被调用时,this指向该对象

3.构造函数上下文,在构造函数中,this指向新创建的实例

4.事件处理上下文,在事件处理函数中,this通常指向触发事件的Dom元素

什么时候使用上下文:

1.对象的方法

const person = {
  name: 'John',
  greet() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};
person.greet(); // 输出: Hello, my name is John.

2.事件处理器

const button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this); // 在这里,this 指向 button 元素
});

3.构造函数

function Person(name) {
  this.name = name;
}
const john = new Person('John');
console.log(john.name); // 输出: John

3.扩展运算符

扩展用算符...是ES6中引入的一中语法,可以在函数调用,数组和对象字面量中使用,它用于展开可迭代的对象(数组或者字符串)或者合并多个数组和对象

扩展运算符提供了一个搞笑的方式来操作数组和对象,非常有用

1.数组的展开

讲一个数组展开为多个元素

const arr = [1, 2, 3];
const newArr = [...arr, 4, 5]; // [1, 2, 3, 4, 5]

2.对象的合并

将多个对象合并为一个新对象

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const mergedObj = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 }

3.函数参数

将数组的元素作为参数传入函数

function sum(x, y, z) {
    return x + y + z;
}

const nums = [1, 2, 3];
console.log(sum(...nums)); // 输出: 6

4.创建一个数组的浅拷贝

const original = [1, 2, 3];
const copy = [...original]; // [1, 2, 3]

注意事项

扩展运算符只是对可迭代对象的浅拷贝,深层嵌套的对象或者数组仍会引用原始对象

在对象合并时,如果有重复的键,后者会覆盖前者的值

4.数组的深拷贝

几种常见的实现方式

1.JSON.parse和JSON.stringfy

该方法简单有效,但不能处理函数,undifinded,Date对象等

function deepCopyArray(arr) {
    return JSON.parse(JSON.stringify(arr));
}

// 示例
const original = [1, 2, [3, 4]];
const copy = deepCopyArray(original);
copy[2][0] = 5;

console.log(original); // 输出: [1, 2, [3, 4]]
console.log(copy);     // 输出: [1, 2, [5, 4]]

2.使用递归

递归方法适用于更复杂的数据结构,可以处理多种类型的对象

function deepCopyArray(arr) {
    if (!Array.isArray(arr)) return arr; // 基本情况
    const copy = [];
    for (const item of arr) {
        copy.push(deepCopyArray(item)); // 递归拷贝
    }
    return copy;
}

// 示例
const original = [1, 2, [3, 4]];
const copy = deepCopyArray(original);
copy[2][0] = 5;

console.log(original); // 输出: [1, 2, [3, 4]]
console.log(copy);     // 输出: [1, 2, [5, 4]]

3.使用structureClone,现在浏览器支持

structrueClone是一个内置函数,可以处理更多的情况

function deepCopyArray(arr) {
    return structuredClone(arr);
}

// 示例
const original = [1, 2, [3, 4], new Date()];
const copy = deepCopyArray(original);
copy[2][0] = 5;

console.log(original); // 输出: [1, 2, [3, 4], Date对象]
console.log(copy);     // 输出: [1, 2, [5, 4], Date对象]

5.数组常用方法

添加元素:

push(),在末尾添加一个或多个元素

unshift(),在开头添加一个或多个元素

删除元素:

pop(),删除并返回数组最后一个元素

shift(),删除并返回数组的第一个元素

查找遍历

forEach(),遍历数组,并执行给定的函数

map(),创建一个新数组,包含调用函数处理每个元素的结果

filter(),创建一个新的数组,包干所有通过测试的元素

find(),返回数组中满足条件的第一个元素

sort(),对数组进行排序(默认升序)

reverse(),反转数组中的元素顺序

链接和切割

contact(),合并数组

slice(),复制一段数组,参数是start和end(不包含end)

splice(),改变原数组,可以添加删除或替换,参数start,deleteCount,item1,item2要添加的元素

其他方法

join(),将数组元素连接成字符串

includes(),判断数组中是否包含某个元素

6.字符串常用方法

基本操作

length,获取字符串的长度

提取子字符串

charAt(index),返回指定索引出的字符

slice(start, end),返回从start到end(不含end)的子字符串

substring(),同上一个函数

查找和替换

indexOf(searchValue),返回第一次出现searchValue的索引,未找到返回-1

includes(searchValue),判断字符串是否包含某个子字符串

replace(searchValue, newValue),替换第一个匹配的子字符串

replaceAll(searchValue, newValue),替换所有匹配的子字符串

分割字符串

split(separator),按指定分隔符拆分字符串,返回一个数组

去除空格

trim(),去除字符串两端的空格

7.构造函数

构造函数是js中一种特殊的函数,用于创建对象,通常以大写字母开头,可以使用new关键字调用创建实例。

1.定义构造函数

function Person(name, age) {
    this.name = name;  // 给新对象添加属性
    this.age = age;
}

// 方法可以通过原型添加
Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name}`);
};

2.使用构造函数创建对象

const john = new Person('John', 30);
john.greet(); // 输出: Hello, my name is John

3.特点

实例化,使用new创建对象会自动获取构造函数原型中的方法

this指向,在构造函数中,this关键字指向新创建的对象

4.重要性

构造函数时实现对象的封装与继承的关键手段,用于创建多个相似的对象非常有效

5.示例

function Car(brand, model) {
    this.brand = brand;
    this.model = model;
}

Car.prototype.getDescription = function() {
    return `${this.brand} ${this.model}`;
};

const myCar = new Car('Toyota', 'Corolla');
console.log(myCar.getDescription()); // 输出: Toyota Corolla

构造函数是js面向对象编程的基础,使得对象的管理和扩展非常灵活。

8.原型与原型链

js是面向对象的,每个实例对象都有一个__proto__属性指向它的原型对象,该实例的构造函数有一个原型属性prototype,与实例的__proto__属性指向同一个对象,同时,原型对象的constructor指向构造函数本身。

当一个对象在查找一个属性时,自身没有就会根据__proto__属性向它的原型进行查找,如果还是没有,则向它的原型的原型继续查找,直到查找到Object.prototype.__proto__也就是null,这样就形成了原型链。

9.闭包

闭包是js中的一个重要的概念,它指的是一个函数能够记住并访问其外部作用域中的变量,即使外部函数已经执行完毕,闭包由函数及其相关的作用域组成,允许函数访问其外部上下文的变量

1.闭包的形成

当一个函数在其外部函数的作用域中被定义并返回时,闭包就形成了,即使外部函数已经结束,内部函数仍然可以访问外部的变量

2.闭包示例

function makeCounter() {
    let count = 0; // 私有变量

    return function() {
        count++; // 访问外部变量
        return count;
    };
}

const counter = makeCounter();
console.log(counter()); // 输出: 1
console.log(counter()); // 输出: 2
console.log(counter()); // 输出: 3

3.闭包的用途

数据封装,保护变量不被外部访问,实现私有变量

创建函数工厂,可以生成具有特定状态的函数

延迟执行,可以在某个时间点后再调用某个函数

4.注意事项

内存消耗,闭包会保持对外部作用域的引用,可能会导致内存泄漏,尤其是在大规模使用的时候

调试难度,调试闭包中代码可能会比较复杂,因为作用域层级较多

总结:闭包是一个强大的工具,可以帮助管理私有状态和实现高级编程模式,理解闭包的工作机制是掌握js的关键

函数嵌套函数,且内部函数调用父级作用域的变量就可以称之为闭包了。

10.Js数据类型

JavaScript 中的数据类型主要分为两类:基本数据类型和对象。

1. 基本数据类型

基本数据类型也称为原始数据类型,包括:

String,表示文本字符串。

Number,表示数值,包括整数和浮点数。

Boolean,表示逻辑值,只有 true 和 false 两个值。

Undefined,表示未定义的值,变量声明但未赋值的默认值。

Null,表示“空”或“无”值,表示变量为空。

Symbol,表示唯一且不可变的值,主要用于对象属性。

BigInt,用于表示非常大的整数,超出 Number 的安全范围。

2. 对象类型

对象是存储键值对的集合,包括:

Object,基本的对象类型。

Array,数组是对象的一种特殊形式,用于存储有序数据。

Function,函数也是对象,可以被调用。

总结

JavaScript 的数据类型包括:

基本类型:String, Number, Boolean, Undefined, Null, Symbol, BigInt

对象类型:Object, Array, Function

理解这些数据类型对于有效编程和调试是非常重要的。

11.call,apply,bind

func.call(thisArg, arg1, arg2, ...)

立即调用,第一个参数this上下文,后面的参数是传递给函数的参数

func.apply(thisArg, [argsArray])

立即调用函数,第一个参数是this上下文,第二个参数是一个数组类数组的对象,标识传递给函数的参数

onst boundFunc = func.bind(thisArg, arg1, arg2, ...)

不会立即调用函数,而是返回一个新的函数

该函数的this被固定为thisArg,可以在后续调用时传递参数

总结:

call和apply用于立即调用函数,唯一的区别在于参数的传递方式

bind用于创建一个新的函数,以便未来调用时保持this上下文

12.箭头函数

1.箭头函数是定义函数的一种新的方式,比普通函数更加方便简单

2.箭头函数不绑定this,会捕获其所在的上下文的this,作为自己的this

3.箭头函数不能用作构造函数,不能使用new命令,否则会报错

4.箭头函数不能绑定arguments,取而代之用reset参数解决,同时没有super和new.target

5.使用call,apply,bind并不会改变箭头函数的this指向

13.Event Loop 事件循环

js是单线程的,这意味着它只有一个主执行线程来处理代码,事件和消息,为了异步操作,js使用事件循环机制

1.调用栈

js代码的执行是通过调用任务栈进行管理的,当一个函数被调用时,它会被压入栈中,当执行完成后,它从栈中弹出,顺序执行

2.异步操作

当异步操作发生时(事件监听,网络请求),他们会被交给浏览器处理,不会阻塞主线程

3.任务队列 Task Queue

当异步操作完成后,相应的回调函数被放入任务队列中

有两种队列,宏观队列(如setTimeout)和围观队列(如Promise)

4.事件循环

事件循环持续检查调用栈是否为空

如果为空,它会从微任务队列中取出任务执行,然后在取宏观任务队列中的任务

console.log('Start');

setTimeout(() => {
  console.log('Timeout 1');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise 1');
});

console.log('End');

输出顺序
Start
End
Promise 1
Timeout 1

事件循环让js能够处理异步任务,同时保持代码执行的顺序和效率。

14.Promise

Promise是异步编程的一种解决方案。

Promise是一个构造函数,接收一个函数作为参数,返回一个Promise实例。

Promise实例有三个状态pending,fulfilled,rejected,分别代表进行中,已成功,已失败,实例状态只能由pending转变为fulfilled和rejected状态,且状态一经改变就无法再改变

状态的改变是通过resolved()和reject()函数来实现,可以在异步操作结束后调用这两个函数改变Promise实例的状态。

Promise的原型上定义了一个then方法,使用这个then方法可以为两个状态的改变注册回调函数,这个回调函数属于微任务,会在本轮事件循环的末尾执行

Promise.all是一个用于处理多个Promise的静态方法,它接收一个可迭代的对象,如数组,并返回一个新的Promise,该Promise会在所有输入的Promise都成功时解析,或者在任何一个Promise失败时拒绝。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// 使用 Promise.all
Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // 输出: [3, 42, 'foo']
  })
  .catch(error => {
    console.error('Error:', error);
  });

注意:

所有Promise成功,只有当所有的Promise成功时,Promise.all的返回才会解析成功

任意Promise失败,如果任意一个Promise被拒绝,Promise.all返回的Promise会立即被拒绝,并返回那个错误

输入的数组可以包含普通值

const fetchData1 = () => axios.get('/api/data1');
const fetchData2 = () => axios.get('/api/data2');

Promise.all([fetchData1(), fetchData2()])
  .then(([response1, response2]) => {
    console.log('Data 1:', response1.data);
    console.log('Data 2:', response2.data);
  })
  .catch(error => {
    console.error('Error fetching data:', error);
  });

Promise.all 非常适合处理多个异步请求,确保他们全部成功后再执行下一步操作

15.async/await

async/await是js中用于处理异步操作的语法,提供了一种更简洁的方式来处理Promises

async,用来定义一个异步函数,该函数会返回一个Promise

await,用于等待一个Promise完成,并返回结果

定义一个异步函数

async function fetchData() {
  return 'Hello, World!';
}

fetchData().then(console.log); // 输出: Hello, World!

使用await

async function getUserData() {
  const response = await axios.get('https://api.example.com/user');
  console.log(response.data);
}

getUserData();

async/await 使得异步代码更易读和维护

常用于网络请求和其他异步操作场景,能有效替代传统的Promise链式处理

16.ES6新特性

let和const变量声明,具有块级作用域

if (true) {
  let a = 10;
}
console.log(a); // ReferenceError

箭头函数,不绑定this

const add = (x, y) => x + y;

模板字面量

使用反引号``创建字符串,包括字符串插值

const name = 'World';
console.log(`Hello, ${name}!`); // 输出: Hello, World!

解构赋值

从数组或者对象中取值

const arr = [1, 2, 3];
const [x, y] = arr;

const obj = { a: 1, b: 2 };
const { a, b } = obj;

默认参数

为函数参数设置默认值

function multiply(a, b = 1) {
  return a * b;
}

扩展运算符

用于展开数组或者对象

const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]

const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2 }

Promise

用于异步编程,简化处理异步操作

const promise = new Promise((resolve, reject) => {
  // 异步操作
});

使用class关键字定义类,支持继承

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}

模块

使用import和export语法实现模块化

// 在 module.js 中
export const pi = 3.14;

// 在 main.js 中
import { pi } from './module.js';

符号 Symbol

一种新的原始数据类型,用于唯一标识

const sym = Symbol('description');

17.重绘和回流

重绘是指元素外观(颜色,背景,边框)发生变化,布局未受影响,浏览器重新绘制该元素外观。

回流是指元素的布局(位置,大小,显隐)发生改变,导致页面重新计算布局和渲染,比重绘的性能开销要大。

Vue 面试题

1.Scoped

使用style scoped可以有效的隔离样式,仅作用于当前的组件,避免了全局污染。

2.MVC/MVVM

MVC:

模型Model用于处理数据逻辑部分

视图View数据展示的视图界面

控制器Controller处理交互,从视图读取数据发送给模型

MVC的思想就是Controller负责将Model的数据用View展示出来

MVVM:

ViewModel层实现了数据的双向绑定。将模型转化成视图,数据绑定实现数据传到页面;将视图转化成模型,DOM事件监听实现页面转化成数据

MVVM比MVC精简了许多,实现了View与Model的自动同步,不在需要手动操作DOM来改变View现实,解决了频繁数据操作DOM的问题,会提升性能。

Vue没有严格遵循MVVM,因为MVVM不允许View和Model直接通信,但是Vue提供能$refs使得Model可以直接操作View。

3.Data是一个函数?

组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据,而单纯的写成对象的形式,就会使所有的组件实例共用了一份data,就会造成一个变全部变的结果

4.Vue组件通信

父子组件间:props,$emit,$parent,ref,$attrs

props:父组件向子组件传递数据

$emit:子组件向父组件触发事件传递数据

父组件:

<template>
  <div>
    <ChildComponent :message="parentMessage" @childEvent="handleChildEvent" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      parentMessage: 'Hello from Parent!'
    };
  },
  methods: {
    handleChildEvent(payload) {
      console.log('Event from Child:', payload);
    }
  }
}
</script>

子组件:

<template>
  <div>
    <p>{{ message }}</p>
    <button @click="sendToParent">Send Event</button>
  </div>
</template>

<script>
export default {
  props: ['message'],
  methods: {
    sendToParent() {
      this.$emit('childEvent', 'Hello from Child!');
    }
  }
}
</script>

$parent:子组件可以通过$parent访问父组件的方法和数据,但不鼓励,违背了数据流的单向性

父组件:

<template>
  <div>
    {{ title }}
    <ChildComponent />
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: 'Parent Component'
    };
  },
  methods: {
    alertTitle() {
      alert(this.title);
    }
  }
}
</script>

子组件:

<template>
  <div>
    <button @click="callParentMethod">Alert Parent Title</button>
  </div>
</template>

<script>
export default {
  methods: {
    callParentMethod() {
      this.$parent.alertTitle();  // 访问父组件的方法
    }
  }
}
</script>

ref:父组件使用ref可以获取子组件实例,调用子组件的方法和属性

父组件:

<template>
  <div>
    <ChildComponent ref="child" />
    <button @click="callChildMethod">Call Child Method</button>
    <button @click="getChildData">Get Child Data</button>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  methods: {
    callChildMethod() {
      // 调用子组件的方法
      this.$refs.child.childMethod();
    },
    getChildData() {
      // 访问子组件的数据
      const childData = this.$refs.child.someData;  
      console.log('Child data:', childData);
    }
  }
}
</script>

子组件:

<template>
  <div>
    Child Component Data: {{ someData }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      someData: 'This is data from the child component'
    };
  },
  methods: {
    childMethod() {
      console.log('Child method called!');
    }
  }
}
</script>

$attrs:它是vue组件中一个包含所有未声明props属性的对象,通常用于将父组件的属性转发给子组件的根元素

父组件:

<template>
  <div>
  //父组件传递了 id、class 和 disabled 属性给 CustomButton 组件。
    <CustomButton id="submit-btn" class="primary-button" disabled>
      Click Me
    </CustomButton>
  </div>
</template>

<script>
import CustomButton from './CustomButton.vue';

export default {
  components: { CustomButton }
}
</script>

子元素:

<template>
// v-bind="$attrs":将 $attrs 中的所有属性绑定到 <button> 元素上。
  <button v-bind="$attrs">
    <slot></slot>  <!-- 插槽内容 -->
  </button>
</template>

<script>
export default {
  inheritAttrs: false  // 关闭默认的属性继承
}
</script>

兄弟组件:$parent,$root,eventbus,vuex

$parent:通过父组件进行传值,兄弟组件通过共同的父组件进行交流

父组件:

<template>
  <div>
    <SiblingA @sendData="receiveData" />
    <SiblingB :receivedData="dataFromSiblingA" />
  </div>
</template>

<script>
import SiblingA from './SiblingA.vue';
import SiblingB from './SiblingB.vue';

export default {
  components: { SiblingA, SiblingB },
  data() {
    return {
      dataFromSiblingA: ''
    };
  },
  methods: {
    receiveData(data) {
      this.dataFromSiblingA = data;
    }
  }
}
</script>

组件A:

<template>
  <div>
    <button @click="sendData">Send Data to Sibling B</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendData() {
      this.$emit('sendData', 'Hello from Sibling A');
    }
  }
}
</script>

组件B:

<template>
  <div>
    Received: {{ receivedData }}
  </div>
</template>

<script>
export default {
  props: ['receivedData']
}
</script>

$root: 适合根实例进行传值,适合全局范围的数据传递。

根组件app.vue(应用程序的最上层组件):

<template>
  <div>
    <SiblingA />
    <SiblingB />
  </div>
</template>

<script>
import SiblingA from './SiblingA.vue';
import SiblingB from './SiblingB.vue';

export default {
  components: { SiblingA, SiblingB }
}
</script>

兄弟组件A:

<template>
  <div>
    <button @click="sendData">Send Data to Sibling B</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendData() {
      this.$root.sharedData = 'Hello from Sibling A';
    }
  }
}
</script>

兄弟组件B:

<template>
  <div>
    Received: {{ $root.sharedData }}
  </div>
</template>

<script>
export default {
  computed: {
    updatedData() {
      return this.$root.sharedData;
    }
  }
}
</script>

eventbus:创建一个简单的事件总线,实现组件间的通信

EventBus(eventbus.js)

import Vue from 'vue';
export const EventBus = new Vue();

兄弟组件A:

<template>
  <div>
    <button @click="sendData">Send Data to Sibling B</button>
  </div>
</template>

<script>
import { EventBus } from './eventBus.js';

export default {
  methods: {
    sendData() {
      EventBus.$emit('dataSent', 'Hello from Sibling A');
    }
  }
}
</script>

兄弟组件B:

<template>
  <div>
    Received: {{ receivedData }}
  </div>
</template>

<script>
import { EventBus } from './eventBus.js';

export default {
  data() {
    return {
      receivedData: ''
    };
  },
  created() {
    EventBus.$on('dataSent', (data) => {
      this.receivedData = data;
    });
  },
  beforeDestroy() {
    EventBus.$off('dataSent'); // 清理事件监听
  }
}
</script>

vuex:对于复杂状态或全局共享数据,使用vuex是最佳的实践(后文有详细展开Vuex)

跨层级组件:eventBus,vuex,provide+inject

provid+inject:provide是祖先组件提供数据,在子孙组件中用inject接收

// ParentComponent.vue
<template>
  <ChildComponent />
</template>

<script>
export default {
  provide() {
    return {
      message: 'Hello from Parent Component'
    };
  }
};
</script>

// ChildComponent.vue
<template>
  <GrandChildComponent />
</template>

<script>
import GrandChildComponent from './GrandChildComponent.vue';

export default {
  components: { GrandChildComponent }
};
</script>

// GrandChildComponent.vue
<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  inject: ['message']
};
</script>

5.v-if/v-for

v-if和v-for两个不能放一起,vue2中v-for优先级大于v-if,先循环再判断条件,哪怕只渲染一小部分数,也需要全部循环,比较浪费。vue3中正好相反,v-if优先级高于v-for,所以执行v-if时,它调用的变量不存在,那就会发生异常。

解决办法是可以用计算属性预先过滤,只渲染所需的数据

computed: {
  filteredItems(
) {
    return this.items.filter(item => item.visible);
  }
}

<div v-for="item in filteredItems" :key="item.id">
  {{ item.name }}
</div>

6.Vue生命周期

每个Vue组件实例被创建后都会经历一系列初始化步骤,比如,它需要数据观测,模板编译,挂载实例到dom上,以及数据变化时更新dom,这个过程中会运行叫做生命周期钩子的函数,以便用户在特定阶段有机会添加自己的代码。

Vue生命周期总共分为8个阶段:创建前后,载入前后,更新前后,销毁前后,以及一些特殊场景的生命周期。

beforeCreate:组件实例被创建之初,通常用于插件开发中执行一些初始化任务

created:组件实例已经完全创建,组件初始化完毕,可以访问各种数据,获取接口数据等

beforeMount:组件挂载之前

mounted:组件挂载到实例上之后,dom已创建,可用于获取访问数据和dom元素,访问子组件等

beforeUpdate:组件数据发生变化,更新之前,此时view层还未更新,可用于获取更新前的各种状态

updated:数据更新之后,完成view层更新,更新后,所有状态已是最新

beforeUmount:组件实例销毁之前,可用于一些定时器或者订阅的取消

umounted:组件实例销毁之后,可清理它与其他实例的连接,解除它的全部指令及事件监听器

actived:keep-alive缓存的组件激活时

deactivated:keep-alive缓存的组件停用时调用

errorCaptured:补货一个来自子孙组件的错误时被调用

7.双向绑定原理

vue中双向绑定是一个指令v-model,可以绑定一个响应式数据到视图,同时视图中的变化能改变该值。

v-model是语法糖,默认情况下相当于:value和@input,v-model减少大量繁琐的事件处理代码,提高了开发效率。

    <input v-model="sth" />  //这一行等于下一行
    <input v-bind:value="sth" v-on:input="sth = $event.target.value" />

text和taxtarea元素使用value property和input事件

checkbox和radio使用checked property和change事件

select字段将value作为prop并将change作为事件

8.子改变父组件数据

组件化开发过程中有个单向数据流原则,不在子组件中修改父组件

所有的prop都是的其父子组件之间形成一个单向下行绑定:父级prop的更新会向下流动到子组件中,但反过来不行,这样会防止从子组件意外变更父级组件的状态,导致应用的数据流向难以理解。

父组件发生变更时,子组件中的所有的prop都将会刷新为最新的值,所以不应该在一个子组件的内部改变prop,否则控制台会发出警告。

9.数据响应式

所谓数据响应式就是,能够使数据变化可以被监测并对这种变化做出响应的机制。

以Vue为例,通过数据响应式加上虚拟Dom和patch算法,开发人员只需要操作数据,关心业务,而不用频繁操作Dom,提升开发效率,降低开发难度。

在vue2中,数据响应式会根据不同的类型做处理,如果是对象则采用Object.defineProperty()的方式定义数据拦截,当数据被访问或发生变化时,感知并作出响应,如果是数组则通过覆盖数组对象原型的7个变更方法,使得这些方法可以额外的做更新通知,从而作出响应。

在vue3中重写了这部分的现实,利用ES6的Proxy代理要响应化的数据,初始化性能和内存消耗改善。

10.虚拟DOM

VDOM本身就是一个javascript对象,只不过它是通过不同的属性去描述一个视图结构。

好处:

将真实的元素节点抽象成VNode,有效减少直接操作Dom次数,从而提高程序性能,因频繁操纵Dom容易引起页面的重绘和回流,但是抽象出VNode进行中间处理就可以有效的减少。

vdom是如何生成的呢?在vue中我们常常会为组件编写模板template,这个模板会被编译器compiler编译为渲染函数,在接下来的挂载mount过程中会调用render函数,返回的对象就是虚拟dom,但它们还不是真正的dom,所以会在后续的patch过程中进一步转化为dom。

11.diff算法

Vue中diff算法被称为patching算法,虚拟Dom要想转化为真实Dom就需要通过patch方法转换。

Vue中diff执行的时刻是组件内响应式数据变更触发实例执行其更新函数时,更新函数会再次执行render函数获得最新的虚拟Dom,然后执行patch函数,并传入新旧两次虚拟Dom,对比变化,最后将其转化对应的Dom操作

patch过程是一个递归过程,遵循深度优先,同层比较的策略。

12.动态路由

很多时候,我们需要将给定匹配模式的路由映射到同一个组件,这种情况就需要定义动态路由。

例如,我们有一个User组件,它应该对所有用户进行渲染,但用户ID不同,在Vue Router中,我们可以在路径中使用一个动态字段来实现,例如{path:'/users/:id', component:User},其中:id就是路径参数。

路径参数用冒号表示,当一个路由被匹配时,它的params的值将在每一个组件中以this.$route.params形式暴露出来。

13.v-for加key

key的作用主要是为了更高效的更新虚拟Dom

key可以帮助Vue更高效的跟踪元素的变化,当列表元素发生更新,插入操作时,vue能够通过key快速准确的识别到具体哪个元素发生了变化,从而更精准的进行diff操作(在 Vue 中通常指的是比较新旧虚拟 DOM(Virtual DOM)树的差异),提高性能。

其次,使用key可以避免一些不必要的重新渲染,如果没有key,Vue可能会在某些对整个列表进行不必要的重新渲染,导致性能下降。

14.nextTick

nextTick是等待下一次Dom更新刷新的工具方法

Vue有个异步更新策略,意思是如果数据变化,vue不会立刻更新Dom,而是开启一个队列,把组件更新函数保存在队列中,在同一个事件循环中发生所有数据变化会异步批量更新,这一策略导致我们对数据修改不会立刻体现在Dom上,此刻如果我们想要获取更新后的Dom状态,就需要使用nextTick

有两个场景会用到nextTick,created中获取想要获取Dom时,响应式数据变化后获取Dom更新后的状态,比如希望获取列表更新后的高度

function nextTick(callback?: () => void): Promise(void)

所以我们只需要在传入的回调函数中访问最新的Dom状态即可,或者我们可以在await nextTick()方法返回的Promise之后做这件事

15.watch/computed

watch主要用于监听某个特定数据的变化,然后执行相应的回调函数,它更适合处理数据变时需要执行一些异步操作或者开销大的操作

computed则是基于其他数据计算得到一个新的值,并且具有缓存特性,只有当它依赖的数据发生变化才会重新计算

如果需要在数据发生变化时发送网络请求,可能选择watch,如果只是根据已有的数据计算得出新值,并且希望能高效的利用缓存,就使用computed

16.v-if/v-show

v-if在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点,适用于在运行时很少改变条件,不需要频繁切换条件的场景

v-show会被编译成指令,条件不满足时控制样式将对应的节点隐藏,适用于频繁切换条件的场景

17.Vue事件绑定

原生事件绑定是通过addEventListener绑定给真实元素,组件事件绑定是通过Vue自定义的$on实现的,如果要在组件上使用原生事件,需要加.native修饰符,这样就相当于在父组件中把子组件当做普通的html标签,然后加上原生事件

$on $emit是基于发布订阅模式的,维护一个事件中心,on的时候将事件按名称存在事件中心里,称之为订阅者,然后emit将对应的事件进行发布,去执行事件中心的对应的监视器

18.keep-alive

keep-alive是vue内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载

工作原理,Vue内部将Dom节点,抽象成一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的,它将满足条件的组件在cache对象中缓存起来,重新渲染的时候再将VNode节点从cache对象中取出并渲染。

常见的两个属性inclue、exclude,允许组件有条件的进行缓存

两个生命周期activated、deactivated,用来得知当前组件是否处于活跃状态

keep-alive中还运用了LRU算法,选择最近最久未使用的组件予以淘汰

19.$router/$route

$router是VueRouter的实例对象,是一个全局的路由对象 ,包含了所有路由的对象和属性

$route是一个跳转路由对象,可以认为是当前组件的路由管理,指当前激活的路由对象,包含当前url解析得到的数据,可以从对象里获取一些数据,如name,path,params,query等

20.vue-router路由传参

1.声明式导航router-link

<router-link :to="'/users?userId:1'"></router-link>
<router-link :to="{ name: 'users', params: { userId: 1 } }"></router-link>
<router-link :to="{ path: '/users', query: { userId: 1 } }"></router-link>

2.编程时导航 router-push

通过params传参

this.$router.push({
    name: 'users',
    params: {
        userId: 1
    }
});
// 路由配置
{
    path: '/users',
    name: 'users',
    component: User
}
// 跳转后获取路由参数
this.$route.params.userId // 为 1

通过query传参

this.$router.push({
    path: '/users',
    query: {
        userId: 1
    } 
});
// 路由配置
{
    path: '/users',
    name: 'users',
    component: User
}
// 跳转后获取路由参数
this.$route.query.userId

动态路由

this.$router.push('/users/${userId}');
// 路由配置
{
    path: '/users/:userId',
    name: 'users',
    component: User
}
// 跳转后获取路由参数
this.$route.params.userId

21.Vue模板编译

Vue中有个独特的编译器模块compiler,它的主要作用就是将用户编写的template编译为js中可执行的render函数。

22.mixin

mixin混入,他提供了一种非常灵活的方式,来分发Vue组件中的可复用功能

使用场景:不同组件中经常会用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过mixin将相同或者相似的代码提出来。

缺点,变量来源不明确,多minxi可能造成命名冲突,mixin和组件出现多对多的关系,使项目复杂度变高

23.slot

在Vue中插槽slot是一种让组件能够接收并渲染外部内容的机制,插槽可以让你创建更灵活和可复用的组件

1.默认插槽,你可以在父组件中使用子组件时,传入内容,用于简单的内容传递

<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <p>This will be rendered in the child component!</p>
  </ChildComponent>
</template>

<!-- ChildComponent.vue -->
<template>
  <div>
    <slot></slot> <!-- 这里将渲染父组件传入的内容 -->
  </div>
</template>

2.命名插槽,允许你定义多个插槽,以便在组件中指定不同的内容,允许更灵活的结构

<!-- ParentComponent.vue -->
<template>
  <ChildComponent>
    <template v-slot:header>
      This is the header
    </template>
    <template v-slot:footer>
      <p>This is the footer</p>
    </template>
  </ChildComponent>
</template>

<!-- ChildComponent.vue -->
<template>
  <div>
    <slot name="header"></slot>
    <div>Main content here</div>
    <slot name="footer"></slot>
  </div>
</template>

3.作用于插槽,允许子组件向插槽提供数据,使得父组件能够使用这些数据,父组件能访问子组件的数据

<!-- ParentComponent.vue -->
<template>
  <ChildComponent v-slot:default="{ message }">
    <p>{{ message }}</p>
  </ChildComponent>
</template>

<!-- ChildComponent.vue -->
<template>
  <div>
    <slot :message="childMessage"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      childMessage: 'Hello from Child!'
    };
  }
};
</script>

24.Vue修饰符

事件修饰符:

stop,阻止了事件冒泡,相当于调用了event.stopPropagation方法

prevent,阻止了事件默认行为,相当于调用了event.preventDefault方法

self,值当在event.target是当前元素自身时触发处理函数

once,绑定了事件以后只触发一次,第二次就不会触发

capture,使用事件捕获模式,即元素自身触发的事件先于此处理,然后才交由内部元素进行处理

passive,告诉浏览器你不想阻止事件的默认行为

native,让组件变成html内置标签那样监听根元素的原生事件,否则组件上使用v-on只会监听自定义事件,但在vue3的Composition API中,可以直接使用@click,不需要使用.native,而是直接在组件根元素上处理事件

25.Vue SSR

SSR,即Server Side Render服务端渲染,就是将vue在客户端把标签渲染成html的工作放在服务端完成,然后再把html直接返回给客户端。

优点:有着更好的seo,并且首屏加载速度更快。

缺点:开发条件受限制,服务端渲染只支持beforeCreated和created两个钩子。

26.Axios拦截器

创建一个axios示例,并设置拦截器

import axios from 'axios';

// 创建 Axios 实例
const apiClient = axios.create({
  baseURL: 'https://api.example.com', // 基础 URL
  timeout: 10000, // 请求超时设置
});

添加请求拦截器

apiClient.interceptors.request.use(
  config => {
    // 在请求发送之前做某些事情(如添加 token)
    const token = localStorage.getItem('token'); // 假设我们从本地存储获取 token
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  error => {
    // 请求错误时做些事情
    return Promise.reject(error);
  }
);

添加响应拦截器

apiClient.interceptors.response.use(
  response => {
    // 对响应数据做点什么(如处理数据)
    return response.data; // 只返回数据部分
  },
  error => {
    // 对响应错误做点什么(如统一错误处理)
    console.error('API call failed', error);
    return Promise.reject(error);
  }
);

使用axios拦截器可以在请求和响应的声明周期中加入自定义逻辑,如添加认证头,处理api错误,减少代码重复,统一处理请求和响应逻辑

27.项目性能提升

路由懒加载

keep-alive缓存页面,避免重复创建组件实例,且能保留缓存状态组件的状态。

v-for遍历避免同时使用v-if,vue3中是错误的用法

v-once 不再变化的数据使用v-once

事件销毁,组件销毁前把全局变量和定时器销毁

图片懒加载

第三方插件按需引入

子组件分割,较重的状态组件适合拆分

服务端渲染

28.路由懒加载

路由懒加载是一个优化技术,用于提升单页面应该的性能,主要目的是减少初始化的加载时间,使用户体验更好

按需加载,路由懒加载允许应用在用户访问特定路由时,动态加载该路由组件,而不是在应用启动时就加载

组件分割,每个路由对应的组件会被拆分成独立的文件,这样只有访问该路由时才下载相关的代码

提高性能,只有在需要时加载组件,减少初始加载包的大小,加快时间,用户可以更快的看到页面内容,不必等到所有资源加载完成

在Vue中使用懒加载,使用import()函数实现懒加载,结合Vue router进行配置

import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/home',
    component: () => import('./components/Home.vue'), // 懒加载
  },
  {
    path: '/about',
    component: () => import('./components/About.vue'), // 懒加载
  },
];

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

export default router;

路由懒加载是一种高效的性能优化策略,它通过按需加载路由组件,改善用户体验和应用性能,通过简单的配置,可以显著降低初始化加载的时间,提升应用的响应速度

29.Ref/reactive

这是vue3响应式中非常重要的两个概念

ref接收内部值inner value返回响应式ref对象,reactive返回响应式代理对象

从定义上看ref通常用于处理单值响应式,reactive用于处理对象类型的数据响应式

两者均用于构造响应式数据,ref主要解决原始值的响应式问题

ref返回的响应式数据在js中需要加上.value才能访问其值,在视图中使用会自动脱ref,不需要.value。ref可以接收对象或者数据等非原始值,但内部依然是reactive实现响应式,reactive内部如果接收对象会自动脱ref,使用展开运算符...展开reactive返回的响应式对象会使其失去响应性,可以结合toRef()将值转换为Ref对象之后再展开reactive内部使用Proxy代理传入对象并拦截该对象各种操作,从而实现响应式,ref内部封装了一个Refflmpl类,并设置get set value,拦截用户对值的访问,从而实现响应式

30.自定义指令

使用Vue.directive,全局注册指令,在main.js中注册指令

import Vue from 'vue';

Vue.directive('color', {
  // 在元素绑定时调用
  bind(el, binding) {
    // 设置元素的文字颜色
    el.style.color = binding.value;
  }
});

组件中使用自定义指令:

<template>
  <div>
    <p v-color="'red'">这是红色文本</p>
    <p v-color="'blue'">这是蓝色文本</p>
  </div>
</template>

<script>
export default {
  // 组件逻辑
};
</script>

31.v-once

v-once是vue的内置指令,作用是仅渲染指定组件或元素一次,并跳过未来对其更新

如果我们有一些元素或者组件在初始化渲染之后不再需要变化,这样的情况下适合使用v-once,这样哪怕这些数据变化,vue也会跳过更新,是一种代码优化的手段

我们只需要在作用的组件或者元素上加上v-once即可

编译器发现元素上面有v-once时,会将首次计算结果存入到缓存对象中,组件再次渲染时会从缓存中获取,从而避免再次计算

32.Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。

Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。

Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

下面是使用Vuex管理状态的简单示例,包括状态管理,获取状态,提交mutations和分发actions

创建Vuex Store

在store.js中创建 Vuex Store

import { createStore } from 'vuex';

const store = createStore({
  state() {
    return {
      count: 0,
    };
  },
  //mutations函数定义的对象,用于修改state,只有mutations可以直接修改state
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
  },
  //actions处理异步操作或复杂逻辑的函数,通常调用mutations
  //increment 调用commit提交increment mutation
  actions: {
    increment({ commit }) {
      commit('increment');
    },
    decrement({ commit }) {
      commit('decrement');
    },
  },
  //计算并返回state的派生状态
  getters: {
    doubleCount(state) {
      return state.count * 2;
    },
  },
});

export default store;

在组件中使用Store,组件中使用vuex来访问store的状态getter action mutation

<template>
  <div>
    Count: {{ count }}
    <h2>Double Count: {{ doubleCount }}</h2>
    <button @click="increment">Increment</button>
    <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['count']), // 映射 state 中的 count
    ...mapGetters(['doubleCount']), // 映射 getters 中的 doubleCount
  },
  methods: {
    ...mapActions(['increment', 'decrement']), // 映射 actions
  },
};
</script>

33.大量数据优化

尽量避免大数据量,可以采取分页的方式获取

使用vue-virtual-scroller虚拟滚动技术,只渲染可视窗口范围内的数据

避免更新,使用v-once方式只渲染一次

按需加载,使用懒加载方式

34.Vue Router

vue router是路由管理器,用于在单页面应用spa中实现不同视图之间的导航,它允许开发者通过定义路由来将URL和组件进行映射

在main.js中引入并使用Vue Router

import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';

Vue.use(VueRouter);

定义一个路由,创建一个路由实例,并定义路由映射

const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About }
];

const router = new VueRouter({
  routes
});

路由懒加载如何实现

路由懒加载是指路由被访问时才加载对应的组件,从而优化性能,可以通过动态导入实现

const Home = () => import('./components/Home.vue');
const About = () => import('./components/About.vue');

const routes = [
  { path: '/home', component: Home },
  { path: '/about', component: About }
];

嵌套路由

在一个路由组件中再定义子路由,用于构建多层嵌套的视图

const routes = [
  {
    path: '/user',
    component: User,
    children: [
      { path: 'profile', component: UserProfile },
      { path: 'posts', component: UserPosts }
    ]
  }
];

路由跳转

可以使用$router.push方法进行编程时导航,或使用进行声明式导航

// 编程式导航
this.$router.push('/about');

// 声明式导航
<router-link to="/about">About</router-link>

路由守卫

Vue Router 提供了全局守卫,路由独享守卫和组件内守卫,可以在路由定义时添加守卫函数

router.beforeEach((to, from, next) => {
  // 判断是否需要认证
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!isAuthenticated()) {
      next({ path: '/login' });
    } else {
      next();
    }
  } else {
    next();
  }
});

路由传递参数

可以通过url参数和查询参数传参

// 定义路由
const routes = [
  { path: '/user/:id', component: User }
];

// 使用
this.$router.push({ path: `/user/${userId}` });

获取路由信息

通过this.route访问当前路由对象,包括路径,参数,查询字符串等

console.log(this.$route.params.id);
console.log(this.$route.query);

其他面试题

1.em/rem

em,是相对于父元素的字体大小的单位,适合局部调整

当你在一个元素上使用em时,大小会继承自父元素的字体大小,嵌套使用时会累积,可能导致意外的结果,比如,子元素的em会根据其父元素的em计算

rem,是相对于根元素,html标签的字体大小,适合整体布局和响应式设计

使用rem可以避免嵌套问题,因为它始终基于根元素的大小,便于全局调整,只需修改根元素的字体大小即可影响整个页面

2.前端安全

1.跨站脚本攻击 XSS

攻击者通过注入恶意脚本到网页中,窃取用户数据

防御方式:

输入验证与清理

使用Content Security Policy(CSP)

避免使用innerHtml v-html等危险的方法

2.跨站请求伪造 CSRF

攻击者诱使用户在不知情的情况下发送请求,执行用户的身份下操作

防御方式:

使用CSRF令牌

设置HTTP头部sameSte

证请求的来源

3.安全HTTP头

常见头部:

Content-Security-Policy 防止XSS攻击

X-Content-Type-Options 防止MIME类型混淆

X-Frame-Options 防止点击劫持

Strict-Transport-Security 强制 HTTPS

4.安全存储

不能在客户端存储敏感数据,如密码或个人信息等

解决方案:

使用安全的HTTP-only cookie

避免使用localStorage存储敏感信息

5.SQL注入

尽可能避免前端直接与数据库交互

3.http请求状态码

1xx:信息性状态码

100 Continue: 客户端的请求部分已接收,客户端可以继续发送请求的其余部分。

101 Switching Protocols: 服务器根据客户端的请求切换协议。

2xx:成功状态码

200 OK: 请求成功,服务器返回所请求的资源。

201 Created: 请求成功并创建了新的资源。

202 Accepted: 请求已接受,但尚未处理。

204 No Content: 请求成功,但没有返回内容。

3xx:重定向状态码

301 Moved Permanently: 请求的资源已永久移动到新位置。

302 Found: 请求的资源临时移动到新位置。

304 Not Modified: 请求的资源未被修改,客户端可使用缓存的版本。

4xx:客户端错误状态码

400 Bad Request: 请求格式不正确。

401 Unauthorized: 未授权,需要身份验证。

403 Forbidden: 服务器拒绝请求,无法访问资源。

404 Not Found: 请求的资源未找到。

5xx:服务器错误状态码

500 Internal Server Error: 服务器内部错误。

502 Bad Gateway: 网关或代理服务器接收到无效响应。

503 Service Unavailable: 服务器当前无法处理请求,常见于过载或维护状态。

了解 HTTP 状态码有助于你诊断和处理网络请求的结果,确保客户端与服务器之间的良好沟通。

面试经验

面试首先让自我介绍了一番,无非是一些个人基础信息,学历专业啊,上一家的项目等等,还有自己的亮点,比如自己一个人独立开发能力,做过前端组长有一定管理能力等等,后面主要还是根据简历上的项目去提问,所以记住简历千万别挖坑,往自己擅长的方向去引导,不熟悉的技能建议不要写。

面试项目相关的和基础知识大概各占百分之50,我能想到的面试问题罗列如下:

  • 最近的一个项目,介绍了一些功能流程
  • Vue路由传参方式有哪些
  • 对项目进行了哪些优化(这个我感觉面试必问,vue优化有哪些,结合项目说更真实)
  • JQuery怎么选择第一个和最后一个按钮
  • 怎么快速熟悉一个项目(答案比较开放)
  • 跨域问题怎么产生的,怎么解决
  • 是否有独立开发一整个前端项目的能力
  • 项目怎么做的权限管理
  • 是否熟悉echarts,做过3D定制的开发
  • 父子组件创建和挂载的顺序

嗯,大概就以上这些吧,尽可能的多说,模糊的不确定的说完,可以说大概就是这样,项目中接触的不多,只是自己学习了解的,但是自己很感兴趣等等,表明自己的求知和学习意向。

总结

半个月的时间并不长,但半个月的经历让我很难忘,我会发呆看看路上的匆匆忙忙行人,让自己慢下来,也有时间去幼儿园接孩子,陪孩子多玩玩,更早的做晚饭吃饭,工作日开车陪老婆去医院。终于不再是牛马,当打工人日子太久了,我已经快忘了生活原本的模样,也会去思考五年十年后自己的出路,是的,我已经有了自己的规划…

还有就是如果你也像我一样离职了且心态不好,一周的复习时间就足够了,面试这个东西不只看你的复习情况,还有运气也占一部分,需要去碰,边面试边复习会乱了心神,无法专注,第一周的复习很重要,如果你还在职那大可以一步一步稳着来,但离职的状态拖的越久就会越消极怀疑,越糟越糟…

嗯,还是要感谢我的军师大老婆,陪伴我走过这么一段艰难的时光,相信我,疏导我的情绪和压力,事实证明人不能一直绷着一根弦儿,这样迟早会崩的,该学习学习,该生活也要生活。我想我的第二次投胎是投对了,爱你,我的臭老婆。

background.jpg 收到 offer 后正好是中秋,老婆也请了假,一家三口坐飞机去了成都,看熊猫,去了都江堰和九寨沟看水,也算是再次做牛马前最后的狂欢了吧。

WechatIMG173.jpg

最后也希望如果是想跳槽,或者离职了找工作的你可以找到自己心仪的工作,相信一切都是最好的安排,好运伴你~

哦,对了,我的公益服游戏还会继续为爱发电哈,感谢你们的陪伴和支持,如有问题可通过我的博客 https://echeverra.cn 或微信公众号 echeverra 联系我。祝你工作顺利,生活顺心。

(完)

标签:面试题,Vue,const,函数,面经,被裁,Promise,组件,路由
From: https://blog.51cto.com/echeverra/12108306

相关文章

  • 大模型算法岗常见面试题100道(值得收藏)
    大模型应该是目前当之无愧的最有影响力的AI技术,它正在革新各个行业,包括自然语言处理、机器翻译、内容创作和客户服务等等,正在成为未来商业环境的重要组成部分。截至目前大模型已经超过200个,在大模型纵横的时代,不仅大模型技术越来越卷,就连大模型相关的岗位和面试也开始越来......
  • Spring面试题
    1.Spring框架基本理解关键词:核心思想IOC/AOP、作用(解耦、简化),简单描述框架组成Spring是一个企业级、轻量级开源的分层架构核心思想:IOC(控制反转)和AOP(面向切面编程)1.为Java应用程序开发提供组件管理服务;2.用于组件之间的解耦 3.简化第三方JavaEE中间技术的使用Spring框架包......
  • 超详细的系列总结!大模型岗面试题(含答案)来了!(大语音模型基础篇二)
    前言大模型应该是目前当之无愧的最有影响力的AI技术,它正在革新各个行业,包括自然语言处理、机器翻译、内容创作和客户服务等,正成为未来商业环境的重要组成部分。截至目前大模型已超过200个,在大模型纵横的时代,不仅大模型技术越来越卷,就连大模型相关岗位和面试也开始越来越卷......
  • AI大模型面经之BERT和GPT的区别
    前言本篇介绍bert和gpt区别。BERT和GPT是自然语言处理(NLP)领域中的两种重要预训练语言模型,它们在多个方面存在显著的区别。以下是对BERT和GPT区别的详细分析一、模型基础与架构BERT:全称:BidirectionalEncoderRepresentationsfromTransformers。架构:基于Transformer的编码器部分进......
  • AI大模型大厂面经——LoRA面试题最全总结
    前言大家的显卡都比较吃紧,LoRA家族越来越壮大,基于LoRA出现了各种各样的改进,最近比较火的一个改进版是dora,听大家反馈口碑也不错。基于PEFT的话用409024G显存也可以进行大模型的微调,所以LoRA家族这块还是很有研究和实际落地的潜力。LoRA整个系列分为两个部分:1、LoRA总述2、LoRA家族......
  • 2025秋招LLM大模型多模态面试题(八)- langchain完整面试题
    目录什么是LangChainLangChain包含哪些核心模块模型输入/输出(ModelI/O)组件管理数据处理链式组合记忆与上下文管理外部集成一些核心概念什么是LangChainAgent?什么是LangChainmodel?LangChain包含哪些特点?LangChain如何使用?LangChain如何调用......
  • Android Wear 开发 (一),阿里、腾讯、华为、京东等多家大厂最新安卓面试题
    importandroid.support.v4.app.NotificationCompat.WearableExtender;普通通知栏手机:普通的通知栏在手机上的效果应该都不陌生,这里就不展开说明手表:手表端的效果是由2张卡片构成的,第一张是手机通知栏的信息组成,第二张是点击开发手机应用,具体的效果与手机通知栏的点击事......
  • Vue 2&3进阶面试题:(第五天)
    目录17.keep-alive18.$router和$route的区别19.vue-router路由模式有几种?20.vue的路由传参param和query的区别17.keep-alivekeep-alive是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。keep-alive是一个抽象组件:它自身不会渲染成一个DO......
  • MySQL 增删操作面试题
    在数据库操作中,数据的增删是最基础也是最常见的操作。MySQL作为流行的关系型数据库,增删操作在面试中经常涉及。本文准备了30道关于MySQL增删操作的面试题,按照简单、中等、困难的难度划分,并提供了详细的答案和对应的SQL语句。通过这些问题,可以深入理解MySQL在实际应用中的增删操作。......
  • 2024最新版Java面试题及答案汇总
    1.对字符串的都有哪些方法?详细说明下。具体有String、StringBuffer和StringBuilder这三个类。String是不可变类,每次操作都会生成新的String对象,并将结果指针指向新的对象,由此会产生内存碎片。如果要频繁对字符串修改,建议采用StringBuffer和StringBuilder。StringBuff......