蔚来前端——人生中第一次面试
2024 年 5 月 10 日 星期五(已编辑)
/ ,
107
1
这篇文章上次修改于 2024 年 5 月 11 日 星期六,可能部分内容已经不适用,如有疑问可询问作者。
阅读此文章之前,你可能需要首先阅读以下的文章才能更好的理解上下文。
总结
第一次面试不得不说还是有点紧张,拿大厂练手了也是,难度还好基本上都是问的我简历上面的知识点,面试官比我想象的年轻,可惜不是一个女生,我觉得女面试官应该会更温柔一点哈哈哈,需要改进的地方还有很多,慢慢加油
1.三元组算法
给定一个数组num[-1,1,0,2,3,5]
如果满足i!=j i!=k j!=k 同时 nums[i]+nums[j]+nums[k]=0 则为一个三元组
输出[[-1,1,0]],重复不算
1.1 纯暴力写法
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int> > solove(vector<int> nums){
vector<vector<int> > v;
int len = nums.size();
for(int i=0;i<len;i++){
for(int j=i+1;j<len;j++){
for(int k=j+1;k<len;k++){
if(nums[i]+nums[j]+nums[k] == 0){
vector<int> n;
n.push_back(nums[i]);
n.push_back(nums[j]);
n.push_back(nums[k]);
v.push_back(n);
}
}
}
}
return v;
}
int main(){
vector<int> nums(6);
for(int i=0;i<6;i++){
cin>>nums[i];
}
vector<vector<int> > v = solove(nums);
cout<<'[';
for(int i=0;i<v.size();i++){
int count=1;
cout<<'[';
for(int j=0;j<v[i].size();j++){
cout<<v[i][j];
count++;
if(count<=3) cout<<',';
}
cout<<']';
}
cout<<']';
}
1.2 二分优化
#include <iostream>
#include <vector>
using namespace std;
vector<vector<int> > solove(vector<int> nums){
vector<vector<int> > v;
sort(nums.begin(),nums.end());
int len = nums.size();
int left=0,right=len-1;
for(int i = 0; i < len - 2; i++){
if (i > 0 && nums[i] == nums[i - 1]) // Skip duplicates
continue;
int left = i + 1;
int right = len - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
vector<int> triplet;
triplet.push_back(nums[i]);
triplet.push_back(nums[left]);
triplet.push_back(nums[right]);
v.push_back(triplet);
while (left < right && nums[left] == nums[left + 1])
left++;
while (left < right && nums[right] == nums[right - 1])
right--;
left++;
right--;
} else if (sum < 0) {
left++;
} else {
right--;
}
}
}
return v;
}
int main(){
vector<int> nums(6);
for(int i=0;i<6;i++){
cin>>nums[i];
}
vector<vector<int> > v = solove(nums);
cout<<'[';
for(int i=0;i<v.size();i++){
int count=1;
cout<<'[';
for(int j=0;j<v[i].size();j++){
cout<<v[i][j];
count++;
if(count<=3) cout<<',';
}
cout<<']';
}
cout<<']';
}
2.实现一个事件管理器
没写出来。。。
class EventManager {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, ...args) {
const eventCallbacks = this.events[event];
if (eventCallbacks) {
eventCallbacks.forEach(callback => {
callback(...args);
});
}
}
off(event, callbackToRemove) {
const eventCallbacks = this.events[event];
if (eventCallbacks) {
this.events[event] = eventCallbacks.filter(callback => callback !== callbackToRemove);
}
}
offAll(event) {
delete this.events[event];
}
}
const eventManager = new EventManager();
function handleClick1() {
console.log('Button 1 clicked');
}
function handleClick2() {
console.log('Button 2 clicked');
}
eventManager.on('click', handleClick1);
eventManager.on('click', handleClick2);
eventManager.emit('click');
eventManager.off('click', handleClick1);
eventManager.emit('click');
eventManager.offAll('click');
eventManager.emit('click');
3.问ts泛形
TypeScript 中的泛型允许编写可以在多种类型上重复使用的代码。泛型是在定义函数、类或接口时不指定具体类型,而是在使用时再指定具体类型的一种机制。泛型的主要优势在于它可以增加代码的灵活性和可重用性,同时提高了代码的类型安全性。
4.ES6新特性
- 箭头函数(Arrow Functions):提供了更简洁的函数定义方式,同时固定了函数内部的 this 指向。
- 常量声明(const)和块级作用域(let):引入了 const 和 let 关键字,允许声明块级作用域的变量,解决了 var 存在的一些问题。
- 模板字符串(Template Literals):使用反引号 ` 可以创建多行字符串和在字符串中嵌入变量,更加方便易读。
- 解构赋值(Destructuring Assignment):允许通过模式匹配的方式从数组或对象中提取数据,赋值给变量。
- 扩展运算符(Spread Operator)和剩余参数(Rest Parameters):通过 ... 运算符可以将数组展开为多个参数,也可以用于收集函数参数成为一个数组。
- 默认参数(Default Parameters):在函数定义中可以为参数设置默认值,简化了函数调用时的逻辑。
- 类(Class):引入了类和面向对象的概念,更加清晰地定义了对象的构造和行为。
- 模块化(Modules):提供了 import 和 export 关键字,使得 JavaScript 可以更好地组织和管理代码。
- Promise:提供了一种更优雅的方式来处理异步操作,解决了回调地狱的问题。
- 迭代器和生成器(Iterators and Generators):引入了用于自定义迭代行为的迭代器和生成器,简化了处理集合的操作。
- 新的数据结构:包括 Map、Set、WeakMap 和 WeakSet,提供了更灵活和高效的数据结构选项。
- Symbol:引入了一种新的基本数据类型,用于创建唯一的对象属性。
- 新的方法:如 Array.prototype.includes()、Object.entries() 等,提供了更便捷的方法来操作数组和对象。
5.Promise常用方法
- then(onFulfilled, onRejected):then 方法用于指定 Promise 对象的成功(onFulfilled)和失败(onRejected)的回调函数。它返回一个新的 Promise 对象,可以进行链式调用。
- catch(onRejected):catch 方法用于指定当 Promise 对象状态变为 rejected 时的回调函数。它是 then 方法的一个特例,专门用于捕获异常。
- finally(onFinally):finally 方法用于指定当 Promise 对象状态变为 fulfilled 或 rejected 时都会执行的回调函数,无论 Promise 的状态如何都会执行。
- Promise.resolve(value):Promise.resolve 方法返回一个以给定值解析后的 Promise 对象。如果传入的值是一个 Promise 对象,则直接返回这个对象;否则返回一个新的以该值解析后的 Promise 对象。
- Promise.reject(reason):Promise.reject 方法返回一个状态为 rejected 的 Promise 对象,并将给定的 reason 作为该 Promise 对象的失败原因。
- Promise.all(iterable):Promise.all 方法接收一个可迭代对象(比如数组),并返回一个新的 Promise 对象,该对象在传入的所有 Promise 对象都成功解析后才会解析,如果其中一个 Promise 对象失败,则整个 Promise 对象将被拒绝。
- Promise.race(iterable):Promise.race 方法接收一个可迭代对象(比如数组),并返回一个新的 Promise 对象,该对象在传入的 Promise 对象中有一个状态改变时,它的状态就跟着改变,第一个解析或拒绝的 Promise 对象的结果会传递给返回的 Promise 对象。
6.问登录逻辑
- 在前端登录页面收集登录所需的账号密码,POST提交至后端
- 后端检查数据库是否存在对应的一个账号密码,如果存在则用JWT生成一个Token返回
- 前端在收到Token后会在Localstorage和Redux各存一份,然后跳转至后台
- 后台的路由用HOC(高阶组件)包裹,高阶组件会检查是否携带Token,如果有则跳转至后台页面,没有就返回
5.问防抖节流
没有要实现代码,就是大概实现逻辑
// 定义一个防抖函数
function debounce(fn,wait) {
// 创建一个定时器并初始化为null
var timer = null;
return function () {
// 保存函数执行上下文和参数
var context = this,
args = [...arguments];
// 如果定时器存在则清除定时器
if(timer){
clearTimeout(timer);
timer = null;
}
// 重新定时
timer = setTimeout(() => {
// fn.apply(context, args) 是 JavaScript 中的一种函数调用方式,
// 它的作用是在指定的上下文(context)中调用函数(fn),并且以数组形式传递参数(args)给该函数。
fn.apply(context,args);
},wait)
}
}
function throttle(fn,wait){
var preTime = Date.now()
return function(){
var context = this,
args = [...arguments],
nowTime = Date.now()
if(nowTime - preTime >= wait) {
preTime = Date.now()
return fn.apply(context,args)
}
}
}
7.项目有没有自定义Hooks
回答的封装的防抖节流的Hooks
8.问项目
主要聊了一下怎么优化的,我说得在客户端层面就是懒加载,打包层面就是代码拆分,打包压缩,如何就是服务器方面Nginx开启缓存和Gzip,然后就是在请求后端数据时候是怎么去调用的(这段没有答好,有点不记得了,好像是挑选了一个最重的页面问的请求)
9.闲聊反问
问了一下这是社招还是校招,好像投错成社招了🤣,还好面试官说是校招,还有就是有没有需要学习的地方,面试官回答要多刷题