2
头图

前言

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

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

心路历程

坐标天津,从事互联网前端开发工作,具体的公司就不提了哈,从去年开始公司的股票就一跌再跌,今年年初也开始部门架构调整,工作任务压力一下变大,到下半年的各种施压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秒钟只执行第一次触发函数)

案例:按钮高频率点击

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

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

function throttle(func, limit) {
    let inThrottle = false;
    return function(...args) {
        //保存当前的this上下文
        const context = this;
        if(!inThrottle) {
            //传入执行的函数
            func.apply(context, args);
            //标记正在节流
            inThrottle = true;   
            //使用闭包和setTimeout来在指定的延迟后重置inThrottle
            setTimeout(() => {
                //重置节流状态
                inTrottle = false;            
            }, limit) 
        }    
    }
}

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 联系我。祝你工作顺利,生活顺心。

(完)


echeverra
44 声望14 粉丝