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

前言

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

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

心路历程

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

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

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

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

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

面试分两轮,先是外包公司内部电话初试,这个难度不大,差不多的都会推进去,最难的在第二轮客户面试,先要进行机试,还好没有编程题,然后是现场面试,十个左右面试官(各组的组长)依次提问,当时去了十多个面试的,听说还有北京赶过来的,其他人都是面试十五分钟就出来了,我面试完出来一看面试了 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><h1>{{ title }}</h1><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><h1>This is the header</h1></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', // 基础 URLtimeout: 10000, // 请求超时设置
});

添加请求拦截器

apiClient.interceptors.request.use(config => {// 在请求发送之前做某些事情(如添加 token)const token = localStorage.getItem('token'); // 假设我们从本地存储获取 tokenif (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可以直接修改statemutations: {increment(state) {state.count++;},decrement(state) {state.count--;},},//actions处理异步操作或复杂逻辑的函数,通常调用mutations//increment 调用commit提交increment mutationactions: {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><h1>Count: {{ count }}</h1><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定制的开发
  • 父子组件创建和挂载的顺序

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

总结

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

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

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

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

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

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

(完)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/431487.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么 ECB 模式不安全

我们先来简单了解下 ECB 模式是如何工作的 ECB 模式不涉及链接模式&#xff0c;所以也就用不着初始化向量&#xff0c;那么相同的明文分组就会被加密成相同的密文分组&#xff0c;而且每个分组运算都是独立的&#xff0c;这也就意味着可以并行提高运算效率&#xff0c;但也正是…

电脑ip地址怎么换地区:操作步骤与利弊分析

在当今全球化的信息时代&#xff0c;人们经常需要访问不同地区的网络资源。然而&#xff0c;由于地理位置的限制&#xff0c;某些内容或服务可能只对特定地区的用户开放。这时&#xff0c;更换电脑IP地址的地区就成为了一个实用的解决方案。本文将详细介绍两种更换电脑IP地址地…

WebRTC关键技术及应用场景:EasyCVR视频汇聚平台高效低延迟视频监控解决方案

众所周知&#xff0c;WebRTC是一项开源的实时通信技术&#xff0c;它通过集成音频、视频和数据传输到Web浏览器中&#xff0c;使得实时通信变得简单且无需任何插件或第三方软件。WebRTC不仅是一个API&#xff0c;也是一系列关键技术和协议的集合&#xff0c;它的出现改变了传统…

羽毛球场馆预约系统,便捷管理预约

全国羽毛球运动的热度不断上升&#xff0c;在健身行业中掀起了一股羽毛球热潮。同时羽毛球运动的风靡&#xff0c;也吸引了不少人入局&#xff0c;各种大大小小的羽毛球馆不断出现&#xff0c;为大众的羽毛球喜好提供了场地。 随着互联网的发展&#xff0c;羽毛球馆也开始向线…

共享单车轨迹数据分析:以厦门市共享单车数据为例(六)

副标题&#xff1a;.基于POI数据的站点功能混合度探究——以厦门市为例 为了保证数据时间尺度上的一致性&#xff0c;我们从互联网上下载了2020年的POI数据&#xff0c;POI数据来源于高德地图 API平台,包括名称、大小类、地理坐标等。并将高德地图 POI数据的火星坐标 系 GCJ-0…

idea 创建多模块项目

一、新建项目&#xff0c;创建父工程 新建项目&#xff0c;选择 spring initializr 填写相关信息后提交 删除不相关的目录&#xff0c;如下 修改打包方式为 pom&#xff0c;在 pom.xml 文件中新增一行&#xff0c;如下 二、创建子模块 新增子模块 填写子模块信息&#x…

《机器学习》周志华-CH8(集成学习)

8.1个体与集成 集成学习(ensemble learning)通过构建并结合多个学习器来完成学习任务&#xff0c;有时也被称为多分类器系统&#xff0c;基于委员会的学习。 同质”集成“&#xff1a;只包含同种类型的个体学习器&#xff0c;同质集成中的个体学习器亦称“基学习器”&#xff0…

C# winforms 使用菜单和右键菜单

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

累加求和-C语言

1.问题&#xff1a; 计算123……100的和&#xff0c;要求分别用while、do while、for循环实现。 2.解答&#xff1a; 累加问题&#xff0c;先后将100个数相加。要重复进行100次加法运算&#xff0c;可以用循环结构来实现。重复执行循环体100次&#xff0c;每次加一个数。 3.代…

【C++掌中宝】C++ 中的空指针救世主——nullptr

文章目录 1. 什么是 NULL&#xff1f;2. NULL 在 C 和 C 中的区别3. C11 引入 nullptr 的原因4. nullptr 与 NULL 的区别5. nullptr 的应用场景6. 模拟 nullptr 的实现7. 总结结语 1. 什么是 NULL&#xff1f; 在 C 和 C 编程中&#xff0c;NULL 常用于表示空指针&#xff0c;…

如何修改音频的音量增益

一、前言 在开发音频相关的功能&#xff08;比如说语音通话、播放音乐&#xff09;时&#xff0c;经常会遇到音量太小的问题&#xff0c;这时候就需要我们对原始数据进行处理。本文将介绍如何通过修改原始音频数据来增加增益&#xff0c;本文包含如下内容&#xff1a; 1.音频数…

【Java】虚拟机(JVM)内存模型全解析

目录 一、运行时数据区域划分 版本的差异&#xff1a; 二、程序计数器 程序计数器主要作用 三、Java虚拟机 1. 虚拟机运行原理 2. 活动栈被弹出的方式 3. 虚拟机栈可能产生的错误 4. 虚拟机栈的大小 四、本地方法栈 五、堆 1. 堆区的组成&#xff1a;新生代老生代 …

速通LLaMA3:《The Llama 3 Herd of Models》全文解读

文章目录 概览论文开篇IntroductionGeneral OverviewPre-TrainingPre-Training DataModel ArchitectureInfrastructure, Scaling, and EfficiencyTraining Recipe Post-TrainingResultsVision ExperimentsSpeech Experiments⭐Related WorkConclusionLlama 3 模型中的数学原理1…

目标检测系列(一)什么是目标检测

目录 一、相关名词解释 二、目标检测算法 三、目标检测模型 四、目标检测应用 五、目标检测数据集 六、目标检测常用标注工具 一、相关名词解释 关于图像识别的计算机视觉四大类任务&#xff1a; 分类&#xff08;Classification&#xff09;&#xff1a;解决“是什么&…

【LLM开源项目】LLMs-微调框架-LLaMA-Factory入门指南v3.0

【#】SFT 训练 项目地址&#xff1a;https://llamafactory.readthedocs.io/zh-cn/latest/getting_started/sft.html 可以使用以下命令进行微调&#xff1a; llamafactory-cli train examples/train_lora/llama3_lora_sft.yamlexamples/train_lora/llama3_lora_sft.yaml 提供…

车辆识别数据集,图片数量20500,模型已训练200轮

车辆识别数据集&#xff08;Vehicle Recognition Dataset, VDRD&#xff09; 摘要 VDRD 是一个专为车辆识别设计的大规模数据集&#xff0c;它包含了20500张不同类型的汽车、货车、公交车以及其他类型车辆的图像。数据集提供了四种车辆类别&#xff1a;汽车、货车、其他车辆和…

单片机原理及应用

单片机原理 一、单片机概述1.1 各种单片机系列1.2嵌入式处理器1.2.1 嵌入式DSP1.2.2 嵌入式微处理器 二、AT89S522.1 硬件组成2.2 引脚功能2.2.1 电源、时钟引脚2.2.2 控制引脚2.2.3 并行I/O口引脚 2.3 AT89S52单片机的CPU2.3.1 运算器2.3.2 控制器 2.4 AT89S52单片机的存储器结…

【代码随想录训练营第42期 Day60打卡 - 图论Part10 - Bellman_ford算法系列运用

目录 一、Bellman_ford算法的应用 二、题目与题解 题目一&#xff1a;卡码网 94. 城市间货物运输 I 题目链接 题解&#xff1a;队列优化Bellman-Ford算法&#xff08;SPFA&#xff09; 题目二&#xff1a;卡码网 95. 城市间货物运输 II 题目链接 题解&#xff1a; 队列优…

MySQL_表_进阶(1/2)

我们的进阶篇中&#xff0c;还是借四张表&#xff0c;来学习接下来最后关于表的需求&#xff0c;以此完成对表的基本学习。 照例给出四张表&#xff1a; 学院表&#xff1a;(testdb.dept) 课程表&#xff1a;(testdb.course) 选课表:&#xff08;testdb.sc&#xff09; 学生表…

python调用c++动态链接库,环境是VS2022和vscode2023

目录 前言&#xff1a;配置环境&#xff1a;基础夯实&#xff08;对于ctypes的介绍&#xff09;&#xff1a;1. 加载共享库2. 定义函数原型3. 调用函数4. 处理数据结构5. 处理指针6. 错误处理7. 使用 ctypes.util总结 效果展示&#xff1a;操作步骤(保姆级教学)一在VS中创建dll…