前言
真正的转变都是痛苦且无声的。
大家好啊,好久不见,停更了一个月了,最近确实没时间更新我的公益服游戏,这段时间我经历了工作被裁员,学习复习,面试找工作,到最终找到工作。想把这段时间我的心路历程和面试题面经分享出来,说不定可以帮到你。
心路历程
坐标天津,从事互联网前端开发工作,具体的公司就不提了哈,从去年开始公司的股票就一跌再跌,今年年初也开始部门架构调整,工作任务压力一下变大,到下半年的各种施压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', // 基础 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>
<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 联系我。祝你工作顺利,生活顺心。
(完)
没有回复内容